Back

Custom SVG Card

Power BI Visual

Node.js - Typescript - JSON

PowerBI Usage Instructions

Simply add a 32x32 SVG as a DAX Measure

Set properties to Image URL

Add data fields and custom colors

visual.ts

    

import powerbi from "powerbi-visuals-api";
import IVisual = powerbi.extensibility.visual. IVisual;
import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructorOptions; import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions;
import DataView = powerbi.DataView;
import EnumerateVisualObjectInstancesOptions = powerbi.EnumerateVisualObjectInstancesOptions; import VisualObjectInstance = powerbi.VisualObjectInstance;
import {valueFormatter} from "powerbi-visuals-utils-formattingutils";
import {svg} from "d3";
    export class Visual implements IVisual {
            private target: HTMLElement;
        private svgContBackgroundColor: string;
        private svgColor: string;
        private textColor: string;
        private labelColor: string;
        constructor(options: VisualConstructorOptions) { this.target = options.element;
    }


    public update(options: VisualUpdateOptions) {
        const dataView: DataView = options.dataViews[0];
        const measure = dataView.categorical.values[0].values[0] as number;
        const svgMeasure = dataView.categorical.values[1]?.values[0] as string;
       
        // Ensure we get the correct label for the measure
        const measureColumn = dataView.metadata.columns.find(col => col.roles["measure"]); const label = measureColumn? measureColumn.displayName: "Measure"; const measureFormat = measureColumn? measureColumn.format: undefined;
        const objects = dataView.metadata.objects;
       
       // Set defaults
        this.svgContBackgroundColor = objects && objects.svgSettings && objects.svgSettings.svgContBackgroundColor? (objects.svgSettings.svgContBackgroundColor as powerbi.Fill).solid.color: "#000000";
        this.svgColor= objects && objects.svgSettings && objects.svgSettings.svgColor? (objects.svgSettings.svgColor as powerbi.Fill).solid.color: "#fff";
        this.textColor = objects && objects.svgSettings && objects.svgSettings.textColor? (objects.svgSettings.textColor as powerbi.Fill).solid.color: "#000000";
        this.labelColor = objects && objects.svgSettings && objects.svgSettings.labelColor? (objects.svgSettings.labelColor as powerbi.Fill).solid.color: "#808080";
       
       
       //Create canvas
        this.target.innerHTML = '';
        this.target.style.display = 'flex';
        this.target.style.flexWrap = 'nowrap';
        this.target.style.alignItems = 'center';
        this.target.style.justifyContent = 'center';
        this.target.style.height='100%';
        const card = document.createElement('div');
        card.style.display = 'flex';
        card.style.flexDirection = 'row';
        card.style.alignItems = 'center'; card.style.padding="20px";
        card.style.height="100%";
        card.style.width = "100%";
        card.style.gap = "25px";

        //Label Container
        const labelCont = document.createElement('div'); // labelCont.style.backgroundColor = "red";
        labelCont.style.display = "flex";
        labelCont.style.height="100px";
        labelCont.style.flexDirection = "column";
        labelCont.style.justifyContent="center"; labelCont.style.gap = "2.5px"; labelCont.style.fontSize = "16px";
        
        
        //svg Container
        const svgCont = document.createElement('div'); //svgCont.style.backgroundColor = "orange"; svgCont.style.height = "50px";
        svgCont.style.width = "50px";
        svgCont.style.display = "flex";
        svgCont.style.justifyContent="center";
        svgCont.style.alignItems = "center";
        svgCont.style.borderRadius = "10px";
        svgCont.style.backgroundColor = this.svgContBackgroundColor;
       
       
       
       // Label
        const labelElement = document.createElement('div'); labelElement.textContent = label.toUpperCase();
        labelElement.style.letterSpacing="2px"; labelElement.style.fontSize="10.5px";
        labelElement.style.color = this.labelColor;


        // Measure
        const measureElement = document.createElement('div');
        measureElement.textContent = this.formatMeasure(measure, measureFormat); measureElement.style.color = this.textColor; measureElement.style.fontWeight = "bold";
      
        // SVG

        const svgElement = document.createElement('div'); svgElement.innerHTML = svgMeasure;
        svgElement.style.display = "flex";
        svgElement.style.justifyContent = "center"; svgElement.style.alignItems = "center";

        // Apply the color to all SVG elements
        const svgInner = svgElement.querySelector('svg'); if (svgInner) {
        const elements = svgInner.querySelectorAll('*'); elements.forEach(element => {
        const currentFill = element.getAttribute('fill');
        const currentStroke = element.getAttribute('stroke');


        if (currentFill && currentFill !== 'none' && currentFill !== 'transparent') {
            element.setAttribute('fill', this.svgColor);
        }
        
        if (currentStroke && currentStroke !== 'none' && currentStroke !== 'transparent') { 
            element.setAttribute('stroke', this.svgColor);
        }
        });

        }

        labelCont.appendChild(labelElement);
        labelCont.appendChild(measureElement);
        svgCont.appendChild(svgElement);
        card.appendChild(svgCont);
        card.appendChild(labelCont);
        this.target.appendChild(card);

        }

        private formatMeasure(measure: number, format: string): string{
        const formatter = valueFormatter.create({ format });
        return formatter.format(measure);
        }

    public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] { const instances: VisualObjectInstance[] = [];
        
        switch (options.objectName) {
            case 'svgSettings':
                instances.push({
                    
                    objectName: 'svgSettings',
                    properties: {
                        svgColor: this.svgColor,
                        textColor: this.textColor,
                        labelColor: this.labelColor,
                        svgContBackgroundColor: this.svgContBackgroundColor,
                    },
            selector: null
            });
            break;
        }
        return instances;
        }
    }
        

capabilities.json

    



{
    "dataRoles":[
        {
        "name": "measure",
        "kind": "Measure",
        "displayName": "Measure"
        },
        {
        "name": "svgMeasure",
        "kind": "Measure",
        "displayName": "SVG Measure"
        }
    ],
        "dataViewMappings": [
        {
            "conditions": [
                {
                "measure": { "max":1},
                "svgMeasure": { "max":1}
                }
    ],
        "categorical": {
        "values": {
            "select":[
            {"bind": { "to": "measure"}},
            {"bind": { "to": "svgMeasure"}}
        
            ]
        }
        }
    }
    ],
    "objects": {
    "general": {
    "displayName": "General",
    "properties":{
    "label":{
    "displayName": "Label",
    "type":{"text": true }
    }
    }
    },
    "svgSettings": {
    "displayName": "SVG Settings",
    "properties":{
    "svgContBackgroundColor": {
    "displayName": "SVG Background Color",
    "type":{"fill": { "solid": { "color": true }}}
    },
    "svgColor": {
    "displayName": "SVG Color",
    "type":{"fill": { "solid": { "color": true }}}
    },
    "textColor": {
        "displayName":"Text Color",
        "type":{"fill": { "solid": { "color": true }}}
    },
        "labelColor": {
        "displayName": "Label Color",
        "type": { "fill": { "solid": { "color": true }}}

        }
    }
}

        },

        "privileges": []
    }