Adding Data Binding To Your Power BI Custom Visual

Adding Data Binding To Your Power BI Custom Visual

This video explains how to add data binding functionality to your custom visual for Power BI. This is video #10 of the Power BI Custom Visual Development Fundamentals series, please check the playlist below for the set.

Video Transcript

In the last video, you wrote just enough code to render a basic bar chart, based on fixed data. However, a bar chart that doesn’t move is quite boring, and you might as well do that in excel. In this video, you’re going to make the chart respond to whatever data the user is selecting, and adapt accordingly.

First, what you need to do is to declare what field types this chart supports.

For any chart, you can see these fields types on the visualization right hand bar in Power BI. You can see the vanilla bar chart supports fields for various roles in the chart, such as the axis, the legends, the values and others.

In comparison, the field bar for this chart looks a bit poor, but you will improve it over time.

The place where you can declare these field types is in the capabilities.json file.

If you look at the content of this data roles element, you can see those field types right here. At the moment, this chart is claiming to support fields for category data and fields for measure data.

The displayname element in each data role refers to the visual description you see in the right hand bar.

The name is the internal name for this role and can be whatever you want.

The kind is the type of this role, and this can be a Grouping, Measure or both. This distinction helps Power BI place fields in the right boxes when the user is selecting them.

Now this is all fine by itself, but let’s still change the display names of these field types, so they align a bit more with the vanilla charts.

Another thing you need to do, is to tell Power BI that the chart only supports having one field assigned for each role, at least for now. The vanilla bar chart can render multiple category series and multiple measures, however, it’s best to attack some other areas before going into that.

"dataViewMappings": [

        "conditions": [
                "category": {
                    "max": 1
                "measure": {
                    "max": 1

If you want to make sure users can’t add more than one field to each role, you can add this code to the data view mappings element.

This sets up some restrictions on what the category and measure roles can have.

Here, you’re telling it that the chart supports at most 1 category value and one measure value. Again, note that “category” and “measure” are not special names.

These refer to whatever role names you define in the data roles element. You can call them apples and oranges instead if you want, as long as you keep these internal name consistent, this will work just fine.

Now that the supported data fields are set up, the next step is to get that data from Power BI and plug it into the chart.

private getViewModel(options: VisualUpdateOptions): ViewModel {
    return null;

Let’s get back to the visual.ts file and create a new function called getViewModel.

Now, remember in the last video, where I said the view model was kind of an interface between the data grabbing code and the rendering code.

Well, this is where you’re going to make use of that concept.

This function is going to query power bi for whatever data the user is requesting to view, bring it back and convert it into something that fits into that view model.

In this way, you’re only making changes to data grabbing code and you won’t have to fiddle with the rendering code at all, it will keep working as-is.

That’s the neatness of using a view model interface.

Now as you can see, this function is asking for a VisualUpdateOptions object so it can return a view model.

The VisualUpdateOptions is an object that Power BI makes available with every call to the update function.

You already saw that this object contains the width and height of the viewport but it contains other useful things as well.

let dv = options.dataViews;

One of them is the dataviews property.

Power BI makes its queried data available under different shapes, in order to reduce the amount of data manipulation you have to do, regardless of the type of chart you’re trying to create.

These shapes include category and value pairs, tabular data, matrix data and hierarchical tree like data shapes.

For creating a bar chart, it would be useful if Power BI could give you some pre-baked matching arrays of categories and values, so you could just plug them into your chart.

Well, guess what, Power BI can do just that, as long as you ask it to.

Let’s jump back to the capabilities.json file.

Inside the dataview mappings block, you will see this block called categorical.

This is the block is the one that comes with the custom visual template.

What this configuration code is doing here, is, it is defining the behaviour for the categorical view that power bi will make available to your chart.

You’re going to access that view in a bit, but for now, stick with me while we review this configuration.

So the categorical view makes two arrays available to your chart, a categories array and a values array.

PowerBI doesn’t assume what data field you want to populate this array with, so we have to define it here.

This for-in-category block is telling power bi to fill this array with elements from whatever data field gets mapped to the category data role.

This is the same data role you just saw up here, which just happens to be named category.

So when the user picks a field and uses it as the chart’s category field, power bi will take all of the distinct values in the field and use them to populate the category array in the data view.

Power bi knows that it should take distinct values because you’re defining this data role as a Grouping so even that gets sorted for you.

Going back down to the data view definition, you can also see this data reduction algorithm block.

You can use this to tell power bi what to do when there are more values than those you can render.

For example, if you could limit the number of categories returned to a certain number or use a sample of the data instead.

Don’t worry too much about this yet though, we will return to this once we manage to break the visual.

Now, for the values array in the data view, the definition looks a bit different.

What you want here is for power bi to aggregate the measure data, using whatever criteria the user has selected, and then bind those resulting aggregations to the values array.

That’s exactly what this code ask power bi to do over your “measure” measure.

Again, this is the same measure you saw up there in the data roles.

And again, note that the kind of this data role is measure.

Since you also have a grouping field defined, power bi can now infer that you want to aggregate whatever field is bound to your measure role, while grouping by whatever field is bound your category role.

So with that out-of-the-way, let’s get back to the getViewModel function and make use of this automatic view.

First, let’s initialize an empty view model so you have something to return in case there is no data to show at all.

let viewModel: ViewModel = {
    dataPoints: [],
    maxValue: 0

This creates a brand new viewmodel object, ready for filling up with juicy data or give right back in case we’re out of luck.

if (!dv
    || !dv[0]
    || !dv[0].categorical
    || !dv[0].categorical.categories
    || !dv[0].categorical.categories[0].source
    || !dv[0].categorical.values)
    return viewModel;

Now, you’re gonna write a safety check to ensure all the data you need is present, before you even try to render the chart.

If the user is in the middle of bringing data field to the right hand pane or for some reason, there are no categories to show, this code is pick it up and return an empty view model straight away.

This tells the rendering code that is no data to render, so it can take appropriate action, for example, showing an empty canvas.

If the data survives this check, then you have something to work with, so let’s grab some references so you need to type less afterwards.

let view = dv[0].categorical;

First, let’s grab a reference to the categorical view itself.

let categories = view.categories[0];

Now let’s grab a reference to the category array.

You may be noticing that index zero at the end here.

Well, there’s something I didn’t tell you.

The categorical view supports multiple series, so you can in fact have multiple category arrays, once for each series.

If the user was allowed to map multiple category fields to your chart, you would an array for each one here.

However, this is not the case and this chart does not yet support multiple series, so you only care about the first and only category array.

let values = view.values[0];

The same principle applies to the values array.

If the user had mapped multiple value fields to your value role, you’d get multiple arrays here.

However, you explicitly disallowed, so you only need to care about the very first array.

for (let i = 0, len = Math.max(categories.values.length, values.values.length); i  d.value);

Let’s finish the viewmodel by populating the maxvalue property.

As you did in the static data code before, you just need to grab the max value from all the data points and stuff it here.

return viewModel;

And that’s it, the viewmodel is complete, all that’s left is to return it to the caller.

Now let’s back to the update function.

This is where you can appreciate the usefulness of a view model and the separation of concerns it brings.

As the only thing you did was to redefine how to the viewmodel is populated, you now have to make zero changes to the rendering code.

let viewModel = this.getViewModel(options);

All you need to do is to remove the static data you added before and replace the viewmodel creation code with a single line.

And that’s it, all done.

Save the file, let pbiviz do its thing and then let’s take a look at your achievement.

First of all, notice the error, it’s now complaining that there are too many measures in the chart. That’s because you’re limiting them to 1.

Let’s take them out until you’re left with just the one. And there it is, your new chart has just woken up.

Now try to add other categories and measures. As you can see, the user is locked out of adding more than one to start with.

You can of course, replace the existing axis and measure with new ones and your chart will update accordingly.

Now another thing you can do right now is to break the chart. Just drag the name field to the axis and see what happens. Now where did those columns go? Now you may be thinking that power bi just broke but this is not the case. If you remember, each column’s width is roughly calculated by dividing the total viewport width by the number the categories and then subtracting some padding.

In this particular instance, if you drill down into the html from you browser’s F12 debugging window, you will easily find that the chart was indeed rendered properly, depending on how you define properly. There are exactly 1000 columns here, each with a not-so-surprising width of 0.

Death by too much data.

There are a couple of ways of handling this, and all revolve around limiting the number of categories your chart can render, removing column padding after a certain number or warning the user he’s trying to render too much stuff to begin width. However, this is a battle for another day. What I want you to take from here is that magical number of 1000. That’s the current hard limit for the number of data points that power bi will provide to your code and you must keep this mind whenever you are building a new chart.

Close debug, drag genre to the axis

So that’s it for today. Your chart now reacts to user data and redraws itself instantly. Not too bad for now, but you’ll keep improving it as we go.

In the next video, you’ll add some color to this chart to make it look a bit more snappy.

Thanks for watching and see you next time.

Jorge Candeias's Picture

About Jorge Candeias

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

London, United Kingdom