Creating A Bar Chart Power BI Custom Visual

Creating A Bar Chart Power BI Custom Visual

This video explains how to start developing a custom bar chart visual in Power BI. It assumes you already have your development environment set up for the job. If not, check out the previous videos in this series. This is video #9 the Power BI Custom Visual Development Fundamentals series, please check the playlist below for the set.

Video Transcript

So now that the project is set up, you can finally start building your chart.

In this video, you’re going to create just enough scaffolding to show a bar chart using some static data.

You’re not going to do anything fancy just yet.

You’re just going to do the bare minimum to make the chart visible and the code clean.

Ok, let’s get back to the visual.ts file.

Before anything, let’s get rid of the template code so we can start fresh.

First, let’s remove all the member variables from the visual, you won’t need these for now.

Then let’s cleanup the constructor as well.

And let’s cleanup all the code from the update method too.

Finally let’s collapse the big copyright block, it’s nice and all but we don’t to see it all the time.

And there you go, we’re running barebones now, so we can start adding code.

Let’s start with the constructor.

Now before we can start painting anything, we need to prepare our canvas.

A brush isn’t enough, we need to put some paper on the easel before we can start splashing color.

That’s we do in the constructor, we setup that paper.

this.host = options.host;

First, we need to access the options parameter and grab a reference to the host object.

The host object gives you access to some environment functionality that we use later on.

This includes the color palette of the hosting report, amongst other things.

Now you can notice that the editor is complaining about this host property.

This is typescript safety at work here, saying that it can’t find this object anywhere.

private host: IVisualHost;

To fix this, we must declare this property as being a part of the Visual class, and state of what type it is.

In this case, this is of interface type IVisualHost, as you can see when you hover over the property name on the options object.

Now this was a simple step, but it is a very important pattern.

Grabbing this reference from the constructor options and keeping it as a member variable allows us to refer back to it afterwards, for example when we need to figure out what colours we should paint our canvas with.

Speaking of canvas, the next step is to create it.

private svg: d3.Selection;

Let’s add another member variable to hold that canvas.

This variable will contain live reference to an html SVG element that we’re going to create a bit.

SVG stands for scalable vector graphics and it’s one of two ways to draw interactive vector shapes with pure HTML.

The other way is the more recent HTML Canvas element, which we will not be using in this course.

Instead we’re going to use SVG as D3 has a far easier time handling data binding and interactions on SVG elements that on HTML canvas elements.

So now that we have a variable to hold that drawing board, let’s grab one from the shop.

this.svg = d3.select(options.element).append("svg").classed("my-little-bar-chart", true);

Let’s get back to the constructor and add this code in.

This is our first use of D3 and isn’t even for charting yet.

D3 has many helper functions to easily create html elements on the fly and manipulate them as needed.

This is exactly what we are doing here to create an SVG element in the right position.

First, we use D3 to grab a reference to the options.element object and prepare it for manipulation.

Remember, the options.element object contains a reference to the html container for the visual, and you’re free to put whatever you want inside this element.

Next, we tell D3 to create a new “svg” element inside the visual container by using the append function.

This will be our drawing board.

It’s here that we will be drawing all the bars and scales and colors in the chart.

The append also returns the newly created SVG element, ready for further manipulation by D3.

And with that, we then add a CSS class property to the SVG element, in this case “mylittlebarchart”.

We use this class later in the course on to help style the chart a bit.

This classed function also the returns SVG object it operated upon, again in a way that’s ready for further manipulation.

And that is exactly what we are assigning to the SVG member variable, which just happens to be of the D3 type SVGElement, the exact type we need.

Next, we need to prepare some sort of grouping container for the bars.

This is to allow us to easily find and recreate the bars without messing around with any other elements.

The pattern is the same.

private barGroup: d3.Selection;

First, let’s create another member variable to hold a reference to that bar group.

this.barGroup = this.svg.append("g").classed("bar-group", true);

So this is not too different from before.

Here we are starting with the SVG element we created before, and creating a new group element right inside it.

That’s the G element you see here.

A G element in SVG does not render anything by itself, it’s just a grouping element to put other stuff in.

So with that new G element created, we then add a CSS class of bar-group, so we can add some styles later.

And that’s all we need to do in the constructor for now.

Let’s move on the update function, where the bulk of rendering will happen.

Again, this function is called by Power BI every time the visual needs to refresh, for whatever reason.

It’s in this function, that we will draw and redraw the chart, as many times as needed.

Now, before we can draw anything, we need to have some data to draw from.

However, we don’t want to go into complex data binding just yet.

We want to make sure that the visual can render properly on its own, so we can exclude any bugs introduced by data binding code and any dodgy data.

That said, we also want to set up our code in a way that makes it easier to plug-in the data binding code afterwards.

With that in mind, we can create some helper types to makes things clearer.

interface DataPoint {
    category: string;
    value: number;
};

First, let’s create an interface called DataPoint.

We are going to use objects of this type to represent the different data points on a bar chart.

If you look at the bars on a bar chart, you notice that you can describe each data point, as in, each bar, by its category and by its value.

And that’s exactly what each object of this type will hold.

How exactly we create those objects, we’re gonna see it in a bit.

interface ViewModel {
    dataPoints: DataPoint[];
    maxValue: number;
};

Now, we’re going to add something called a view model.

If you’ve ever dabbled in MVVM, you probably know where this going.

If not, don’t worry too much, this is just an artefact to keep the code clean and resistant to bugs.

This interface is here to define a separator between the code that gets your data from Power BI and the code that renders that data in the visual.

Using a separator like this allows you to mess around with the data fetching code without affecting the rendering code and vice-versa.

This separation of concerns adds some resilience to your code and enables easier alterations afterwards.

Now, you don’t need to do this, of course, you can write all the code together, but doing so will help you in the long run.

Ok, so now that we’ve defined these helper types, let’s get back to the update function.

The first thing we need to render some data, is, well, some data.

let sample: DataPoint[] = [
    {
        category: "Apples",
        value: 10,
    },
    {
        category: "Bananas",
        value: 20,
    },
    {
        category: "Cherries",
        value: 30
    },
    {
        category: "Dates",
        value: 40
    },
    {
        category: "Elderberries",
        value: 50
    }];

Let’s create some sample data right here, using the types we just defined.

So what we have here is literally an array of data points, created on the fly using json syntax.

Each of these data points has a category and a value, just like we defined in the interface and you can probably imagine what a bar chart with these data points will look like.

let viewModel: ViewModel = {
    dataPoints: sample,
    maxValue: d3.max(sample, x => x.value)
};

Now with this in mind, let’s create that logical separator between the data and the rendering code.

So what we have here is the view model itself, the extra object you saw just a minute ago.

As you can see, this view model object is acting as a container for the data points, plus some additional data.

That maxvalue property there holds the highest value between all values in the dataset.

This will help with dynamically scaling the chart, as you want the chart to stretch upwards and fill the available space, regardless of the data points.

You only need to calculate this value once and it is data, even if not a datapoint per se, so it makes sense to store it in a view model.

Now, as I said before, you don’t need to create or use this view model pattern, if this is not your thing.

But doing so can make your code easier to test and troubleshoot in the long run.

let width = options.viewport.width;
let height = options.viewport.height;

Ok, so let’s start rending something.

First, we need to grab some information from the current graphical view, namely what’s the width and height allowance given to our visual in the report.

This information lets us dynamically scale the chart to fill up all the available space.

this.svg.attr({
    width: width,
    height: height
});

Add that’s exactly what we do with this bit of code here.

Here, we use the D3 attribute function to set some html attributes on the SVG container element we created back there.

This tells the SVG drawing board to fill up the entire space allowed for the visual.

Note the syntax here too.

Using a json object as a parameter for the attribute function, you can set multiple attributes at the same time.

This is one of the syntax sugars of D3 you will often come across.

Now, before we draw the bars on the chart, we need to define that chart’s horizontal and vertical scales.

let yScale = d3.scale.linear()
    .domain([0, viewModel.maxValue])
    .range([height, 0]);

Let’s take a look at how to define the vertical scale and understand why we need to do this.

Confusing code, isn’t it?

Let’s take a step back.

When you think of scales in a chart, you using think about a cartesian coordinate system.

In this coordinate system, you have point zero, zero in the middle and then you go left and right, up and down.

For most charts, you typically stay in this upper right area, where the x axis goes from left to right and y axis goes from bottom upwards.

And that’s how tend to plot your points, always with a sense that values in upper area are higher than values in the bottom, and value at right are higher than values to the left.

This may seem very basic and we often take this for granted.

Now there’s the catch.

SVG itself doesn’t care about charting

SVG was developed to cater for any vector graphics rendering, regardless of content and so, their developers went with screen coordinates instead.

This system is found is many low-level programming apis and looks like this.

As you can see, this is a bit different from the cartesian system.

While the horizontal axis still grows to the right, the vertical axis works in the opposite way, with values to the bottom being higher than values at the top.

This difference in coordinate systems introduces a problem when you’re trying to render stuff on the screen.

Think of a simple vertical bar chart.

Without a solution, you would need to constantly make funky calculations to make your high values render at lower coordinates and vice-versa.

Well, luckily for you, the D3 scale is that solution.

The function of the scale is to translate one coordinate system to another coordinate system.

Plus, as name suggests, it will scale values from one system to the other without you having to worry about it.

This means you can model your chart using whatever system is best for you, while you let D3 work out any funky calculations to screen coordinates in the background.

Of course, we still need to tell D3 what coordinate systems we want to use in the first place.

And that’s exactly what we are doing here with the scale function.

So what this code is doing is, first, it’s creating a linear scaling function, meaning there are no shenanigans regarding the distance between values, as opposed to say a logarithmic scale or an exponential scale.

Then we tell it the source scale, or what scale we want to convert from.

This is usually our domain, the range of values is our dataset.

And here’s were saying that our domain values can go from zero, to whatever is the maximum value in the data set, which we calculated earlier.

After this we tell D3 what scale we want to convert those value to.

This is usually the SVG surface range, though there are a few different ways of defining this.

For the y axis, we want to map those data value onto screen coordinates, meaning lower values must map to higher screen coordinates and vice-versa.

With that in mind, we define the target range with a minimum of value of height and a maximum of zero.

With this conversion, a zero in our dataset will get drawn at the bottom, while a high value will get drawn at the top area somewhere.

We may have to add some padding to this afterwards, but we’ll fight that battle when we get there.

let xScale = d3.scale.ordinal()
    .domain(viewModel.dataPoints.map(d => d.category))
    .rangeRoundBands([0, width], 0.1);

And that’s it for y axis, now let’s write some code for the x axis.

So what we’re doing here is a bit different.

If you think about the x axis on a bar chart, it doesn’t make sense to think of a Cartesian coordinate system.

The horizontal axis has distinct categories, not continues values, so your domain data for this axis has a completely different behaviour from the vertical axis.

Luckily again, d3 also understands categorical data and knows how to assign different categories to a different spot in screen space, or whatever other range you want to map to.

So the way we do this is by creating an ordinal scale.

This is a scale that will take categorical values and translate them to something else.

Then, just as before, we define the domain of our data.

In this case, our domain is the list of categories we want to render, meaning horizontal one data point per category.

Now the last bit is the fancy one.

The range round bands function takes each item in the domain and assigns it to a band in the target range.

Here, we are using it to automatically assign a horizontal slice of the screen coordinates to each category in our source domain, with each category getting a slice of equal size.

This is perfect for a bar chart, at least if you want all columns to have the size with.

Note that third parameter there.

That 0.1 controls the amount of padding between slices, or this case the columns.

There’s nothing special about the 0.1, it’s just there because it’s a tiny number and it works well with a particular behaviour of range round bands, namely the on round bit of that name.

That behaviour is to round-up whatever actual padding value you end up with to an integer number.

This is useful if your target range is in pixels, as this way, you won’t get dodgy effects in rendering.

Now note that all this code is defining two scale conversion functions, but we are not actually converting anything yet.

What we are doing here, is defining the behavior

private xPadding: number = 0.1;

Now that you know why this extra value is here, let’s move it somewhere safe. It’s good practice not to let magical numbers lost in the code.

let bars = this.barGroup.selectAll(".bar").data(viewModel.dataPoints);

OK, we’re almost there. We’ve setup almost everything in the code, the only missing bit is the D3 data binding code. The following three lines of blocks are what put data driven in the D3 name.

So first, let’s write this.

So, if you remember, in the constructor we created this barGroup variable. barGroup holds a reference to an SVG group element, as created by D3. As this element is wrapped by D3, we can use it to bind its contents to some data and dynamically create any children elements we need based on that.

That’s what we are preparing to by calling the selectAll function followed by the data function on it.

The selectAll function job is pretty self-explanatory.

What it does, is it returns a list of child elements that match a certain filter.

Here, we are telling it to go and return a list of all child elements of the group element that have a CSS class of “bar”.

Now when this runs the first time, there aren’t any yet and that’s fine.

You will still get a D3 object back, on which you can call this data function.

The data function is the core data binding mechanism in D3.

What this does is, it matches the list you call it on against an array of data you pass it as an argument.

This matching is done by index, so D3 will match the first element to the first data point, the second element to the second data point and so on.

Then, from that match, it gives access to three result sets, on which you can do some fun stuff.

The first result set represents html elements for which there is no matching data point.

So let’s say the chart is getting rendered for the first time.

At this point, you have no bars on the screen yet, so there are no elements with a CSS class of bar.

When you data bind this empty selection against an array of five data points, D3 will give you back a list representing five new elements, essentially the ones that need creating.

Now D3 does not append these new elements without your say so, you still have to tell it to do it.

bars.enter()
    .append("rect")
    .classed("bar", true);

And that’s precisely what we are doing with this bit of code here.

So this enter function you see here, that’s what gives you the list of new elements.

On that list, we call the append function and we give it element type, in this case SVG rectangle.

This defines all the elements as rectangle and then adds them as children of the bars element.

These are literally the bars in the bar chart.

We then apply a CSS class to each element, so that we can find them afterwards, next time this code runs.

So that’s the code to create new bars when we don’t have enough of them yet.

Now that we have all the bars created, we can apply some attributes to them.

bars.attr({
    width: xScale.rangeBand(),
    height: d => height - yScale(d.value),
    y: d => yScale(d.value),
    x: d => xScale(d.category)
});

And that’s what this code here does.

The D3 attribute function is a short hand for applying html attributes to all the elements in our selection, including all the new elements we just created, if any.

So what we’re doing here is defining what each bar will look like, but setting proper attributes on each rectangle element.

Now this code is very compact, but it’s doing quite a lot of stuff.

Let’s check what each what attribute means.

So first, we’re setting the width of the bar.

Now we want the bars to adjust dynamically to whatever space they have available on the horizontal axis, regardless of the number of bars.

Typically, this would involve getting the total width, dividing by the number of data points and adding padding for any other elements we’re drawing, such as axis, legends, etc.

Yet luckily for us, that horizontal scale we defined up there takes care of that for us.

Since we fed already fed it with our list of categories and other settings, it is now able to compute the bar width for us.

All we have to do is to call the rangeBand function and assign it the width of each bar and there it is.

Now we also have to define the height of the bar and this is a bit more quirky.

So while the bar widths are all the same, the height needs to represent the value of data point.

The bigger the data point, the higher the bar.

So in this case, we’re passing to D3 a function that a parameter named d and then returns something.

When D3 is evaluation this attribute function of all of the elements, it makes available each corresponding data point, that is the data point matched to each element.

And we can access it by simply asking for it, for example, using an anonymous function like this.

Now that we the data point, we can calculate the height of bar.

Luckily for us, the yscale we configured earlier helps us with that.

If pass it the domain value of our data point, the scale returns the correct vertical coordinate for that point in the SVG canvas.

However, to figure out the actual height, we have to subtract this from the SVG element height.

This quirk is, yet again, because the SVG vertical coordinates go in the opposite way of our domain values, and we have to compensate for that.

The y scale helps a lot here, but it can’t fix everything.

So now that we have a properly sized bar, we just need to place in on the canvas.

Luckily yet again, the scales take care of this for us.

We just need to feed them with the values and categories of each data point, and the scales will resolve those to their respective coordinates in the SVG canvas.

bars.exit()
    .remove();

And that’s it for creating the bars.

We’re almost, almost, ready to go.

There’s just one need we to consider, and that’s cleanup.

We’ve covered how to add new columns and how to update existing ones but what happens when a user makes a change to a chart, say by filtering data, and the new chart has fewer columns than the previous one?

Well, in that case we need to remove extra columns.

And that’s what the third data binding result set is for.

If we call the exit function here, D3 gives us the list of html elements which have no corresponding data point.

So for example, if we are matching existing bars to 3 data points, there will be two bars at the end without any data points to go with them.

The exit function gives you a list with those two elements.

On that list you can call the remove() function to remove them from the svg canvas.

bars.exit()
    .remove();

And that’s it, you’re done.

Take note of this data binding pattern though, as you’ll be seeing it a lot in D3.

Anyway, let’s go check it in Power BI and see how it looks like.

And there you go, one very basic bar chart.

Granted, five fixed black bars may not be of much use yet, but at least you now saw how to put some reasonably clean code together to start making it happen.

In the next video, you’ll see how to make this chart respond to some actual data and update on the fly.

Jorge Candeias's Picture

About Jorge Candeias

Jorge helps organizations build high-performing solutions on the Microsoft tech stack.

London, United Kingdom https://jorgecandeias.github.io