Search code examples
powerbipowerbi-custom-visuals

Number Formatting Issue with Custom Power BI Visual


Visual Picture

Above is the card from the tutorial at https://learn.microsoft.com/en-us/power-bi/developer/custom-visual-develop-tutorial. Here is the related code for the visual.

I would like the number to be shown as a percentage value, i.e. 15.4% which is how it is formatting in the measure.

I have searched github, microsoft, google, etc. for a solution to this issue. I also skimmed all the recommended questions on Stack Overflow before posting. Any help get the number formatting to match the measures formatting would be greatly appreciated.

Here is the related code for the visual. (sorry for the length)

visual.ts

"use strict";

import "core-js/stable";
import "./../style/visual.less";
import powerbi from "powerbi-visuals-api";
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions;
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import IVisual = powerbi.extensibility.visual.IVisual;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions;
import DataView = powerbi.DataView;
import * as d3 from "d3";
import VisualObjectInstanceEnumeration = powerbi.VisualObjectInstanceEnumeration;

import { VisualSettings } from "./settings";
import { rgb, RGBColor, Color } from "d3";
export class Visual implements IVisual {
    private visualSettings: VisualSettings;
    private svg: d3.Selection<SVGElement, any, any, any>;
    private container: d3.Selection<SVGElement, any, any, any>;
    private circle: d3.Selection<SVGElement, any, any, any>;
    private textLabel: d3.Selection<SVGElement, any, any, any>;
    private textValue: d3.Selection<SVGElement, any, any, any>;
    private settings: VisualSettings;

    constructor(options: VisualConstructorOptions) {
        console.log('Visual constructor', options);
        this.svg = d3.select(options.element)
            .append('svg')
            .classed('circlecard', true);
        this.container = this.svg.append("g")
            .classed('container', true);
        this.circle = this.container.append("circle")
            .classed('circle', true);
        this.textValue = this.container.append("text")
            .classed("textValue", true);
        this.textLabel = this.container.append("text")
            .classed("textLabel", true);
    }
    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstanceEnumeration {
        const settings: VisualSettings = this.visualSettings ||
            VisualSettings.getDefault() as VisualSettings;
        return VisualSettings.enumerateObjectInstances(settings, options);
    }

    public update(options: VisualUpdateOptions) {
        this.settings = Visual.parseSettings(options && options.dataViews && options.dataViews[0]);
        let dataView: DataView = options.dataViews[0];
        let width: number = options.viewport.width;
        let height: number = options.viewport.height;
        this.svg.attr("width", width)
            .attr("height", height);
        let radius: number = Math.min(width, height) / 2.2;
        this.visualSettings = VisualSettings.parse<VisualSettings>(dataView);

        this.visualSettings.circle.circleThickness = Math.max(0, this.visualSettings.circle.circleThickness);

        this.visualSettings.circle.circleThickness = Math.min(10, this.visualSettings.circle.circleThickness);
        this.circle
            .style("fill", this.visualSettings.circle.circleColor)
            .style("stroke", this.visualSettings.circle.circleColor)
            .style("stroke-width",this.visualSettings.circle.circleThickness)
            .attr("r", radius)
            .attr("cx", width / 2)
            .attr("cy", height / 2);
        let fontSizeValue: number = Math.min(width, height) / 5;
        this.textValue
            .text(dataView.single.value as string)
            .attr("x", "50%")
            .attr("y", "50%")
            .attr("dy", "0.35em")
            .attr("text-anchor", "middle")
            .style("font-size", fontSizeValue + "px");
        let fontSizeLabel: number = fontSizeValue / 4;
        this.textLabel
            .text(dataView.metadata.columns[0].displayName)
            .attr("x", "50%")
            .attr("y", height / 2)
            .attr("dy", fontSizeValue / 1.2)
            .attr("text-anchor", "middle")
            .style("font-size", fontSizeLabel + "px");
    }

    private static parseSettings(dataView: DataView): VisualSettings {
        return VisualSettings.parse(dataView) as VisualSettings;
    }

    /**
     * This function gets called for each of the objects defined in the capabilities files and allows you to select which of the
     * objects and properties you want to expose to the users in the property pane.
     *
     */
}

capabilities.json

{
    "dataRoles": [
        {
            "displayName": "Measure",
            "name": "measure",
            "kind": "Measure"
           }
    ],
    "objects": {
        "circle": {
            "displayName": "Circle",
            "properties": {
                "circleColor": {
                    "displayName": "Color",
                    "description": "The fill color of the circle.",
                    "type": {
                        "fill": {
                            "solid": {
                                "color": true
                            }
                        }
                    }
                },
                "circleThickness": {
                    "displayName": "Thickness",
                    "description": "The circle thickness.",
                    "type": {
                        "numeric": true
                        }
                    }
                }
            }
        },
    "dataViewMappings": [
        {
            "conditions": [
                { "measure": { "max": 1 } }
            ],
            "single": {
                "role": "measure"
            }
        }
    ]
}

settings.ts

"use strict";

import { dataViewObjectsParser } from "powerbi-visuals-utils-dataviewutils";
import DataViewObjectsParser = dataViewObjectsParser.DataViewObjectsParser;

export class CircleSettings {
  public circleColor: string = "white";
  public circleThickness: number = 2;
 }
 export class VisualSettings extends DataViewObjectsParser {
  public circle: CircleSettings = new CircleSettings();
 }

Thank you for viewing this.


Solution

  • One way I always handle my formats (when not possible via the interface) is creating custom Measures that contain the format I want.

    I created 2 cards to show unformatted and formatted values:

    Measure formula : Fmt_value = FORMAT(SUM('Table'[Column1]),"#,##0.0%;(#,##0.0%)")

    see pictures:

    Formatting Numeric values as %

    Adding a Measure for Formatting

    Hope this helps.