Building a bar chart using PowerBI

A learning document to build a bar chart using PowerBI

It’s important to define the bar chart view model first, and iterate on what’s exposed to your visual as you build it.

TypeScriptCopy

/**

 * Interface for BarCharts viewmodel.

 *

 * @interface

 * @property {BarChartDataPoint[]} dataPoints – Set of data points the visual will render.

 * @property {number} dataMax                 – Maximum data value in the set of data points.

 */

interface BarChartViewModel {

    dataPoints: BarChartDataPoint[];

    dataMax: number;

};

/**

 * Interface for BarChart data points.

 *

 * @interface

 * @property {number} value    – Data value for the point.

 * @property {string} category – Corresponding category of the data value.

 */

interface BarChartDataPoint {

    value: number;

    category: string;

};

Use static data

Using static data is a great way to test your visual without data binding. Your view model won’t change, even after you add data binding in a later step.

TypeScriptCopy

let testData: BarChartDataPoint[] = [

    {

        value: 10,

        category: ‘a’

    },

    {

        value: 20,

        category: ‘b’

    },

    {

        value: 1,

        category: ‘c’

    },

    {

        value: 100,

        category: ‘d’

    },

    {

        value: 500,

        category: ‘e’

    }];

let viewModel: BarChartViewModel = {

    dataPoints: testData,

    dataMax: d3.max(testData.map((dataPoint) => dataPoint.value))

};

Data binding

You add data binding by defining your visual capabilities in capabilities.json. The sample code already has a schema for you to use.

Data binding acts on a Field well in Power BI.

Data binding in a Field well

Add data roles

The sample code already has data roles, but you can customize them.

  • displayName is the name shown in the Field well.
  • name is the internal name used to refer to the data role.
  • kind is for the kind of field. Grouping fields (0) have discrete values. Measure fields (1) have numeric data values.

JSONCopy

“dataRoles”: [

    {

        “displayName”: “Category Data”,

        “name”: “category”,

        “kind”: 0

    },

    {

        “displayName”: “Measure Data”,

        “name”: “measure”,

        “kind”: 1

    }

],

For more information, see Data Roles       

Add conditions to DataViewMapping

Define conditions within your dataViewMappings to set how many fields each field well can bind. Use the data role’s internal name to refer to each field.

JSONCopy

    “dataViewMappings”: [

        {

            “conditions”: [

                {

                    “category”: {

                        “max”: 1

                    },

                    “measure”: {

                        “max”: 1

                    }

                }

            ],

        }

    ]

For more information, see Data View Mapping.

Define and use visualTransform

The DataView is the structure that Power BI provides to your visual, which contains the queried data to be visualized. However, DataView can provide data in different forms, such as categorical and tabular. To build a categorical visual like a bar chart, you only need to use the categorical property on the DataView. Defining visualTransform lets you convert DataView into a view model your visual will use.

To assign colors and select them when defining individual data points, you use IVisualHost.

TypeScriptCopy

/**

 * Function that converts queried data into a view model that will be used by the visual

 *

 * @function

 * @param {VisualUpdateOptions} options – Contains references to the size of the container

 *                                        and the dataView which contains all the data

 *                                        the visual had queried.

 * @param {IVisualHost} host            – Contains references to the host which contains services

 */

function visualTransform(options: VisualUpdateOptions, host: IVisualHost): BarChartViewModel {

    /*Convert dataView to your viewModel*/

}

Color

Color is exposed as one of the services available on IVisualHost.

Add color to data points

Each data point is represented by a different color. You add color to the BarChartDataPoint interface.

TypeScriptCopy

/**

 * Interface for BarChart data points.

 *

 * @interface

 * @property {number} value    – Data value for the point.

 * @property {string} category – Corresponding category of the data value.

 * @property {string} color    – Color corresponding to the data point.

 */

interface BarChartDataPoint {

    value: number;

    category: string;

    color: string;

};

The colorPalette service

The colorPalette service manages the colors used in your visual. Its instance is available on IVisualHost.

Assign color to data points

You defined visualTransform as a construct to convert dataView to a view model that a bar chart can use. Because you iterate through the data points in visualTransform, it’s also the ideal place to assign colors.

TypeScriptCopy

let colorPalette: IColorPalette = host.colorPalette; // host: IVisualHost

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {

    barChartDataPoints.push({

        category: category.values[i],

        value: dataValue.values[i],

        color: colorPalette.getColor(category.values[i]).value,

    });

}

Selection and interactions

Selection lets the user interact both with your visual and other visuals.

Add selection to each data point

Since each data point is unique, add selection to each data point. You add the selection property on the BarChartDataPoint interface.

TypeScriptCopy

/**

 * Interface for BarChart data points.

 *

 * @interface

 * @property {number} value             – Data value for the point.

 * @property {string} category          – Corresponding category of data value.

 * @property {string} color             – Color corresponding to data point.

 * @property {ISelectionId} selectionId – Id assigned to data point for cross filtering

 *                                        and visual interaction.

 */

interface BarChartDataPoint {

    value: number;

    category: string;

    color: string;

    selectionId: ISelectionId;

};

Assign selection IDs to each data point

Since you iterate through the data points in visualTransform, it’s also the ideal place to create selection IDs. The host variable is an IVisualHost, which contains services that the visual may use, such as color and selection builder.

Use the createSelectionIdBuilder factory method on IVisualHost to create a new selection ID. Create a new selection builder for each data point.

Since you’re making selections based only on the category, you only need to define selections withCategory.

TypeScriptCopy

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {

    barChartDataPoints.push({

        category: category.values[i],

        value: dataValue.values[i],

        color: colorPalette.getColor(category.values[i]).value,

        selectionId: host.createSelectionIdBuilder()

            .withCategory(category, i)

            .createSelectionId()

    });

}

For more information, see Create an instance of the selection builder.

Interact with data points

You can interact with each bar of the bar chart once a selection ID is assigned to the data point. The bar chart listens to click events.

Use the selectionManager factory method on IVisualHost to create a selection manager for cross filtering and clearing selections.

TypeScriptCopy

let selectionManager = this.selectionManager;

//This must be an anonymous function instead of a lambda because

//d3 uses ‘this’ as the reference to the element that was clicked.

bars.on(‘click’, function(d) {

    selectionManager.select(d.selectionId).then((ids: ISelectionId[]) => {

        bars.attr({

            ‘fill-opacity’: ids.length > 0 ? BarChart.Config.transparentOpacity : BarChart.Config.solidOpacity

        });

        d3.select(this).attr({

            ‘fill-opacity’: BarChart.Config.solidOpacity

        });

    });

    (<Event>d3.event).stopPropagation();

});

For more information, see How to use Selection Manager.

Static objects

You can add objects to the Property pane to further customize the visual. These customizations can be user interface changes, or changes related to the data that was queried. The sample uses static objects to render the X-axis for the bar chart.

You can toggle objects on or off in the Property pane.

Objects in the Property pane

Define objects in capabilities

Define an objects property inside your capabilities.json file for objects to display in the Property pane.

  • enableAxis is the internal name that the dataView references.
  • displayName is the name shown on the Property pane.
  • bool is a primitive value that is typically used with static objects, such as text boxes or switches.
  • show is a special property on properties that enables the show switch on the object. Since show is a switch, it is typed as a bool.
Object show switch

TypeScriptCopy

“objects”: {

    “enableAxis”: {

        “displayName”: “Enable Axis”,

        “properties”: {

            “show”: {

                “displayName”: “Enable Axis”,

                “type”: { “bool”: true }

            }

        }

    }

}

For more information, see Objects.

Define property settings

The following sections describe the basic principles of defining property settings. You can also use the utility classes defined in the powerbi-visuals-utils-dataviewutils package for defining property settings. For more information, see the documentation and samples for the DataViewObjectsParser class.

Although optional, it’s best to localize most settings onto a single object for easy reference.

TypeScriptCopy

/**

 * Interface for BarCharts viewmodel.

 *

 * @interface

 * @property {BarChartDataPoint[]} dataPoints – Set of data points the visual will render.

 * @property {number} dataMax                 – Maximum data value in the set of data points.

 * @property {BarChartSettings} settings      – Object property settings

 */

interface BarChartViewModel {

    dataPoints: BarChartDataPoint[];

    dataMax: number;

    settings: BarChartSettings;

};

/**

 * Interface for BarChart settings.

 *

 * @interface

 * @property “show” enableAxis – Object property that allows axis to be enabled.

 */

interface BarChartSettings {

    enableAxis: {

        show: boolean;

    };

}

Define and use ObjectEnumerationUtility

Object property values are available as metadata on the dataView, but there’s no service to help retrieve these properties. ObjectEnumerationUtility is a set of static functions you can use to retrieve object values from the dataView, and for other visual projects. The ObjectEnumerationUtility is optional, but is great for iterating through the dataView to retrieve object properties.

TypeScriptCopy

/**

 * Gets property value for a particular object.

 *

 * @function

 * @param {DataViewObjects} objects – Map of defined objects.

 * @param {string} objectName       – Name of desired object.

 * @param {string} propertyName     – Name of desired property.

 * @param {T} defaultValue          – Default value of desired property.

 */

export function getValue<T>(objects: DataViewObjects, objectName: string, propertyName: string, defaultValue: T ): T {

    if(objects) {

        let object = objects[objectName];

        if(object) {

            let property: T = object[propertyName];

            if(property !== undefined) {

                return property;

            }

        }

    }

    return defaultValue;

}

Retrieve property values from dataView

The visualTransform is the ideal place to manipulate the visual’s view model. To continue this pattern, retrieve the object properties from the dataView.

Define the default state of the property, and use getValue to retrieve the property from the dataView.

TypeScriptCopy

let defaultSettings: BarChartSettings = {

    enableAxis: {

        show: false,

    }

};

let barChartSettings: BarChartSettings = {

    enableAxis: {

        show: getValue<boolean>(objects, ‘enableAxis’, ‘show’, defaultSettings.enableAxis.show),

    }

}

Populate Property pane with enumerateObjectInstances

The enumerateObjectInstances optional method on IVisual enumerates through all objects and places them within the Property pane. Each object is called with enumerateObjectInstances. The object’s name is available on EnumerateVisualObjectInstancesOptions.

For each object, define the property with its current state.

TypeScriptCopy

/**

 * Enumerates through the objects defined in the capabilities and adds the properties to the format pane

 *

 * @function

 * @param {EnumerateVisualObjectInstancesOptions} options – Map of defined objects

 */

public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {

    let objectName = options.objectName;

    let objectEnumeration: VisualObjectInstance[] = [];

    switch(objectName) {

        case ‘enableAxis’:

            objectEnumeration.push({

                objectName: objectName,

                properties: {

                    show: this.barChartSettings.enableAxis.show,

                },

                selector: null

            });

    };

    return objectEnumeration;

}

Control property update logic

Once an object is added to the Property pane, each toggle triggers an update. Add specific object logic in if blocks:

TypeScriptCopy

if(settings.enableAxis.show) {

    let margins = BarChart.Config.margins;

    height -= margins.bottom;

}

Databound objects

Databound objects are similar to static objects, but typically deal with data selection. For example, you can change the color associated with the data point.

Databound object properties

Define object in capabilities

Similar to static objects, define another object in the capabilities.json.

  • colorSelector is the internal name that the dataView references.
  • displayName is the name shown on the Property pane.
  • fill is a structural object value not associated with a primitive type.

TypeScriptCopy

“colorSelector”: {

    “displayName”: “Data Colors”,

    “properties”: {

        “fill”: {

            “displayName”: “Color”,

            “type”: {

                “fill”: {

                    “solid”: {

                        “color”: true

                    }

                }

            }

        }

    }

}

Use ObjectEnumerationUtility

As with static objects, you need to retrieve object details from the dataView. However, instead of the object values being within metadata, the object values are associated with each category.

TypeScriptCopy

/**

 * Gets property value for a particular object in a category.

 *

 * @function

 * @param {DataViewCategoryColumn} category – List of category objects.

 * @param {number} index                    – Index of category object.

 * @param {string} objectName               – Name of desired object.

 * @param {string} propertyName             – Name of desired property.

 * @param {T} defaultValue                  – Default value of desired property.

 */

export function getCategoricalObjectValue<T>(category: DataViewCategoryColumn, index: number, objectName: string, propertyName: string, defaultValue: T): T {

    let categoryObjects = category.objects;

    if(categoryObjects) {

        let categoryObject: DataViewObject = categoryObjects[index];

        if(categoryObject) {

            let object = categoryObject[objectName];

            if(object) {

                let property: T = object[propertyName];

                if(property !== undefined) {

                    return property;

                }

            }

        }

    }

    return defaultValue;

}

Define default color and retrieve categorical object from dataView

Each color is now associated with each category inside dataView. You can set each data point to its corresponding color.

TypeScriptCopy

for (let i = 0, len = Math.max(category.values.length, dataValue.values.length); i < len; i++) {

    let defaultColor: Fill = {

        solid: {

            color: colorPalette.getColor(category.values[i]).value

        }

    }

    barChartDataPoints.push({

        category: category.values[i],

        value: dataValue.values[i],

        color: getCategoricalObjectValue<Fill>(category, i, ‘colorSelector’, ‘fill’, defaultColor).solid.color,

        selectionId: host.createSelectionIdBuilder()

            .withCategory(category, i)

            .createSelectionId()

    });

}

Populate Property pane with enumerateObjectInstances

Use enumerateObjectInstances to populate the Property pane with objects.

For this instance, add a color picker to render each category on the Property pane. To do this, add an additional case to the switch statement for colorSelector, and iterate through each data point with the associated color.

Selection is required to associate the color with the data point.

TypeScriptCopy

/**

 * Enumerates through the objects defined in the capabilities and adds the properties to the format pane

 *

 * @function

 * @param {EnumerateVisualObjectInstancesOptions} options – Map of defined objects

 */

public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {

    let objectName = options.objectName;

    let objectEnumeration: VisualObjectInstance[] = [];

    switch(objectName) {

        case ‘enableAxis’:

            objectEnumeration.push({

                objectName: objectName,

                properties: {

                    show: this.barChartSettings.enableAxis.show,

                },

                selector: null

            });

            break;

        case ‘colorSelector’:

            for(let barDataPoint of this.barDataPoints) {

                objectEnumeration.push({

                    objectName: objectName,

                    displayName: barDataPoint.category,

                    properties: {

                        fill: {

                            solid: {

                                color: barDataPoint.color

                            }

                        }

                    },

                    selector: barDataPoint.selectionId.getSelector()

                });

            }

            break;

    };

    return objectEnumeration;

}

After providing a selector for each property, you get the following dataView object array:

Databound properties in source

Each item in the array dataViews[0].categorical.categories[0].objects corresponds to the concrete category of the dataset.

The function getCategoricalObjectValue just provides a convenient way of accessing properties by their category index. You must provide an objectName and propertyName that match the object and property in capabilities.json.

Other features

You can add a slider control or tooltips to the bar chart. For the code to add, see the commits at Add a property pane slider to control opacity and Add support for tooltips. For more information about tooltips, see Tooltips in PowerBI visuals.

Packaging

Before you can load your visual into Power BI Desktop or share it with the community in the Power BI Visual Gallery, you must package it. Navigate to the root folder of your visual project, which contains the file pbiviz.json, and use the following command to generate a pbiviz file:

BashCopy

pbiviz package

This command creates a pbiviz file in the dist/ directory of your visual project, and overwrites any pbiviz file from previous package operations.

Share this article on
Facebook
Twitter
LinkedIn
WhatsApp
Print

Thank you!

Here is your guide to “Azure for Retail”!

Thank you!

Here is your guide to “Azure for Manufacturing”!

Thank you!

Here is your guide to “Microsoft Dynamics 365 Business Central Capability Guide”!

Thank you!

Here is your guide to “The Developer’s 7-Step Guide to Low-Code App Development”!