PowerBI: How to create *.pbiviz file, Custom Visualization

Well, you may be thinking in my last post I have already mentioned that running the following command will churn out the *.pbiviz files for the power BI.

gulp package

Which in turn is true, that in this post its nothing new on that command (mentioned above), but its more about how do I setup my project to build new *.pbiviz file for it to be used in Power BI portal.

So as discussed in the last post, you have now set up your Power BI GIT solution, its playground is working as desired and all the remainder commands are working fine.

And with the instructions provided over the GIT wiki portal, only can take you upto adding new visual in playground but i wasnt succesful to find any instructions to build your own *.pbiviz file.

So heres the recipie…

In your solution navigate to the following (for this operation, Windows file explorer is fine to avoid the complexity)

CropperCapture[1]

Path: ||Path||\PowerBI-visuals-master\src\Clients\CustomVisuals\visuals

After this took a copy of “wordCloud” folder as is and re-named to “HelloIVisual1”

CropperCapture[2]

Post this, navigate back to you opened VS code editor, and edit the following files:

bundle.json

{
    "dateAdded": "2015-11-10T01:40:27.000Z",
    "dateLastUpdated": "2016-06-01T16:08:49.000Z",
    "videoUrl": "https://www.youtube.com/watch?v=AblTenl9fqo",
    "package": {
        "name": "HelloIVisual1",
        "displayName": "HelloIVisual1",
        "version": "1.2.0",
        "guid": "HelloIVisual11447959067750",
        "description": "HelloIVisual1 is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.",
        "build": "13.0.1100.210",
        "author": {
            "name": "Microsoft",
            "email": "pbicvsupport@microsoft.com"
        },
        "licenseTerms": "http://go.microsoft.com/fwlink/?LinkID=627540",
        "privacyTerms": "http://go.microsoft.com/fwlink/?LinkID=627541",
        "supportUrl": "http://community.powerbi.com",
        "gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals",
        "images": {
            "icon": "icon.svg",
            "screenshots": ["1.jpg"],
            "thumbnail": "thumb.png"
        },
        "code": {
            "typeScript": "HelloIVisual1.ts",
            "javaScript": "HelloIVisual1.js",
            "css": "HelloIVisual1.css"
        }
    }
}

Rename the files:


wordCloud.ts ->HelloIVisual1.ts

styles/wordCloud.less -> HelloIVisual1.less

Change the content of HelloIVisual1.ts

module powerbi.visuals.samples {
    import SelectionManager = utility.SelectionManager;
    export interface HelloViewModel1 {
        text: string;
        color: string;
        size: number;
        selector: SelectionId;
        toolTipInfo: TooltipDataItem[];
    }

    export class HelloIVisual1 implements IVisual {
        public static capabilities: VisualCapabilities = {
            dataRoles: [{
                displayName: 'Values',
                name: 'Values',
                kind: VisualDataRoleKind.GroupingOrMeasure
            }],
            dataViewMappings: [{
                table: {
                    rows: {
                        for: { in: 'Values' },
                        dataReductionAlgorithm: { window: { count: 100 } }
                    },
                    rowCount: { preferred: { min: 1 } }
                },
            }],
            objects: {
                general: {
                    displayName: data.createDisplayNameGetter('Visual_General'),
                    properties: {
                        fill: {
                            type: { fill: { solid: { color: true } } },
                            displayName: 'Fill'
                        },
                        size: {
                            type: { numeric: true },
                            displayName: 'Size'
                        }
                    },
                }
            },
        };

        private static DefaultText = 'Invalid DV';
        private root: D3.Selection;
        private svgText: D3.Selection;
        private dataView: DataView;
        private selectiionManager: SelectionManager;

        public static converter(dataView: DataView): HelloViewModel {
            var viewModel: HelloViewModel = {
                size: HelloIVisual1.getSize(dataView),
                color: HelloIVisual1.getFill(dataView).solid.color,
                text: HelloIVisual1.DefaultText,
                toolTipInfo: [{
                    displayName: 'Test',
                    value: '1...2....3... can you see me? I am sending random strings to the tooltip',
                }],
                selector: SelectionId.createNull()
            };
            var table = dataView.table;
            if (!table) return viewModel;

            viewModel.text = 'Amol Sigma'; // table.rows[0][1];
            if (dataView.categorical) {
                viewModel.selector = dataView.categorical.categories[0].identity
                    ? SelectionId.createWithId(dataView.categorical.categories[0].identity[0])
                    : SelectionId.createNull();
            }

            return viewModel;
        }

        public init(options: VisualInitOptions): void {
            this.root = d3.select(options.element.get(0))
                .append('button')
                .text("This is a button");
                //.attr('src','http://amolpandey.com/');
                //.append('svg')
                //.classed('hello', true);

            //this.svgText = this.root
            //.append('foreignObject')
            //.attr('width','100%')
            //.attr('height','100%')
             //   .append('iframe')
               // .attr('src','https://www.google.com.au');
                //.append('text')
                //.style('cursor', 'pointer')
                //.style('stroke', 'green')
                //.style('stroke-width', '0px')
                //.attr('text-anchor', 'middle');

            this.selectiionManager = new SelectionManager({ hostServices: options.host });
        }

        public update(options: VisualUpdateOptions) {
            if (!options.dataViews && !options.dataViews[0]) return;
            var dataView = this.dataView = options.dataViews[0];
            var viewport = options.viewport;
            var viewModel: HelloViewModel = HelloIVisual.converter(dataView);

            this.root.attr({
                'height': viewport.height,
                'width': viewport.width
            });

            var textProperties = {
                fontFamily: 'tahoma',
                fontSize: viewModel.size + 'px',
                text: viewModel.text
            };
            var textHeight = TextMeasurementService.estimateSvgTextHeight(textProperties);
            var selectionManager = this.selectiionManager;

            this.svgText.style({
                'fill': viewModel.color,
                'font-size': textProperties.fontSize,
                'font-family': textProperties.fontFamily,
            }).attr({
                'y': viewport.height / 2 + textHeight / 3 + 'px',
                'x': viewport.width / 2,
            }).text(viewModel.text)
                .on('click', function () {
                    selectionManager
                        .select(viewModel.selector)
                        .then(ids => d3.select(this).style('stroke-width', ids.length > 0 ? '2px' : '0px'));
                })
                .data([viewModel]);

            TooltipManager.addTooltip(this.svgText, (tooltipEvent: TooltipEvent) => tooltipEvent.data.toolTipInfo);
        }

        private static getFill(dataView: DataView): Fill {
            if (dataView) {
                var objects = dataView.metadata.objects;
                if (objects) {
                    var general = objects['general'];
                    if (general) {
                        var fill = <Fill>general['fill'];
                        if (fill)
                            return fill;
                    }
                }
            }
            return { solid: { color: 'brown' } };
        }

        private static getSize(dataView: DataView): number {
            if (dataView) {
                var objects = dataView.metadata.objects;
                if (objects) {
                    var general = objects['general'];
                    if (general) {
                        var size = <number>general['size'];
                        if (size)
                            return size;
                    }
                }
            }
            return 100;
        }

        public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] {
            var instances: VisualObjectInstance[] = [];
            var dataView = this.dataView;
            switch (options.objectName) {
                case 'general':
                    var general: VisualObjectInstance = {
                        objectName: 'general',
                        displayName: 'General',
                        selector: null,
                        properties: {
                            fill: HelloIVisual1.getFill(dataView),
                            size: HelloIVisual1.getSize(dataView)
                        }
                    };
                    instances.push(general);
                    break;
            }

            return instances;
        }

        public destroy(): void {
            this.root = null;
        }
    }
}

Note: Most of the code in the above visual, is not essential, as I have copied the above from the samples folder provided in the playground. I have not done the clean up, and my intent with the above was just to build the *.pbiviz file

Hence the real logic of visualization rendered in the above code is quite small, and rest of the code needs to be cleaned up

Notice the edit is only for the init function

Post this navigate to following and edit as follows

package/package.json

{
	"build": "13.0.1100.210",
	"version": "1.2.0",
	"author": {
		"name": "Microsoft",
		"email": "pbicvsupport@microsoft.com"
	},
	"visual": {
		"name": "HelloIVisual1",
		"version": "1.2.0",
		"displayName": "HelloIVisual1",
		"guid": "HelloIVisual11447959067750",
		"description": "HelloIVisual1 is a visual representation of word frequency and value. Use it to get instant insight into the most important terms in a set.",
		"supportUrl": "http://community.powerbi.com",
		"gitHubUrl": "https://github.com/Microsoft/PowerBI-visuals"
	},
	"resources": [
		{
			"resourceId": "rId0",
			"sourceType": 5,
			"file": "resources/HelloIVisual1.ts"
		},
		{
			"resourceId": "rId1",
			"sourceType": 0,
			"file": "resources/HelloIVisual1.js"
		},
		{
			"resourceId": "rId2",
			"sourceType": 1,
			"file": "resources/HelloIVisual1.css"
		},
		{
			"resourceId": "rId3",
			"sourceType": 3,
			"file": "resources/icon.svg"
		},
		{
			"resourceId": "rId4",
			"sourceType": 6,
			"file": "resources/thumb.png"
		},
		{
			"resourceId": "rId5",
			"sourceType": 2,
			"file": "resources/1.jpg"
		}
	],
	"images": {
		"icon": {
			"resourceId": "rId3"
		},
		"screenshots": [
			{
				"resourceId": "rId5"
			}
		],
		"thumbnail": {
			"resourceId": "rId4"
		}
	},
	"code": {
		"typeScript": {
			"resourceId": "rId0"
		},
		"javaScript": {
			"resourceId": "rId1"
		},
		"css": {
			"resourceId": "rId2"
		}
	},
	"licenseTerms": "http://go.microsoft.com/fwlink/?LinkID=627540",
	"privacyTerms": "http://go.microsoft.com/fwlink/?LinkID=627541"
}

And finally your solution structure should like as below:

CropperCapture[3]

And you are all set, just fire the following command

gulp package

And you will see in the logs, your package is compiled as others and you have the *.pbiviz file.

CropperCapture[4]

CropperCapture[5]

Finally resulting in my sample Power BI report:

CropperCapture[8]

Attached along with this post, is my zipped custom visualization which you can dump in you solution path and run the package command to see it in action !!!

||Path||\PowerBI-visuals-master\src\Clients\CustomVisuals\visuals

Power BI HelloIVisual1 Download

Hope it helps.

Leave a Reply

Your email address will not be published. Required fields are marked *

*