The ultimate Fabric, Power BI, SQL, and AI community-led learning event. Save €200 with code FABCOMM.
Get registeredCompete to become Power BI Data Viz World Champion! First round ends August 18th. Get started.
I am new to custom visuals and I am trying to make a simple pie chart to start with. This code gives me a white visual and I dont understand why. Can anyone help me?
private svg; private container; private pie; private data = { a: 9, b: 20, c: 30, d: 8, e: 12 }; private color = d3 .scaleOrdinal() .domain(['a', 'b', 'c', 'd', 'e']) .range(['#98abc5', '#8a89a6', '#7b6888', '#6b486b', '#a05d56']); private visualSettings: VisualSettings; constructor(options: VisualConstructorOptions) { console.log('Visual constructor', options); this.svg = d3 .select(options.element) .append('svg') .classed('pieChart', true); this.container = this.svg.append('g').classed('container', true); this.pie = this.container.append('pie').classed('pie', true); } public update(options: VisualUpdateOptions) { let dataView: DataView = options.dataViews[0]; this.visualSettings = VisualSettings.parse<VisualSettings>(dataView); console.log('Visual update', options); let width: number = options.viewport.width; let height: number = options.viewport.height; let radius: number = Math.min(width, height) / 2; this.svg.attr('width', width); this.svg.attr('height', height); let data_ready = this.pie(d3.entries(this.data)); this.pie .selectAll('whatever') .data(data_ready) .enter() .append('path') .attr( 'd', d3 .arc() .innerRadius(0) .outerRadius(radius) ) .attr('fill', function(d) { return this.color(d.data.key); }) .attr('stroke', 'black') .style('stroke-width', '2px') .style('opacity', 0.7); }
Solved! Go to Solution.
Hi @skutovicsakos,
I took your section of code and ran it.
Your code is erroring out midway and can't complete and this is why your visual is white. The custom visual SDK suppresses errors in the browser console, so it can be quite tricky to diagnose.
The code erroring is this line:
let data_ready = this.pie(d3.entries(this.data));
You are calling this.pie as a function, so if I track back where this is instantiated, I see this in the constructor:
this.pie = this.container.append('pie').classed('pie', true);
From my knowledge of d3, this.pie is instantiated as a HTML element rather than a function, so it probably won't do much here. This also creates a SVG element called pie in the browser DOM:
This is also not a valid SVG element type according to the SVG element reference.
From looking at your code, you are working off an existing example (which is a great way to start), and a quick web search leads me to one that looks like this. I'll work on the assumption that this is correct and I'll rework your code to render correctly.
One thing I often do is add some console.log() statements so that I can track where things might be going wrong. You'll see these in the code I'm going to include below, so if you don't see them in your browser console when running the visual then you can trace back to the previous part of the code to investigate why it doesn't work.
Here's the complete listing for the visual.ts I have working, so you should be able to copy/paste. I've added a lot of comments into the code to hopefully explain what I've done and why things are different from your example. Some decisions have been made to make it easier when you work on trying to get data out of your data model, so for instance, the data variable you were using looks a bit different from your original code.
'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 VisualObjectInstance = powerbi.VisualObjectInstance; import DataView = powerbi.DataView; import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject; import { VisualSettings } from './settings'; import * as d3 from 'd3'; export class Visual implements IVisual { private svg: d3.Selection<any, any, any, any>; /** I've added type declarations to your properties so that TypeScript will help you more when coding */ private visualSettings: VisualSettings; constructor(options: VisualConstructorOptions) { console.log('Visual constructor', options); /** Add the SVG element for the pie chart */ this.svg = d3 .select(options.element) .append('svg') .classed('pieChart', true); } public update(options: VisualUpdateOptions) { let dataView: DataView = options.dataViews[0]; this.visualSettings = VisualSettings.parse<VisualSettings>(dataView); console.log('Visual update', options); /** Clear the svg content, as it'll keep overwriting on every update otherwise */ console.log('Removing elements...'); this.svg.selectAll('*').remove(); /** Resolve dimensions based on viewport */ console.log('Resolving dimensions...'); let width = options.viewport.width, height = options.viewport.height, radius = Math.min(width, height) / 2; /** Apply width & height to main element, then add the group (g) element for the pie */ console.log('Setting and appending SVG elements...'); this.svg .attr('width', width) .attr('height', height); let container = this.svg .append('g') .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); /** Create dummy data - you were previously doing this as a class property but this will * trip you up when you try to bind to your data model, so if we do it here, we can swap * this out later on. * * I've also changed the 'shape' of this a bit so that this is easier to work with and * will be more portable if you try to work out mapping from the Power BI data model * you're passing in. I've also added the colour as a property of each entry so that we * can map this from a single place further down. * * By using an interface declaration to set this 'shape', we can validate assignment as * we code, and we can also use it in the d3.pie call below to enforce the type of data * we expect when we work with it, which gives us code completion and validation etc. in * TypeScript. */ interface IData { category: string; value: number; color: string; } /** We'll now assign the category/value/colour into this interface. Note that the data variable * is specified as an array of our IData interface from above. */ console.log('Assigning data...'); let data: IData[] = [ { category: 'a', value: 9, color: '#98abc5' }, { category: 'b', value: 20, color: '#8a89a6' }, { category: 'c', value: 30, color: '#7b6888'}, { category: 'd', value: 8, color: '#6b486b' }, { category: 'e', value: 12, color: '#a05d56' } ]; /** Compute the position of each group on the pie. Note that we're specifying the type after d3.pie * so that we can access the properties correctly and TypeScript will validate our code if we get it * wrong :) */ console.log('Creating pie function...'); let pie = d3.pie<IData>() .value((d) => d.value); /** Now, we'll build the pie chart */ console.log('Drawing chart...'); container .selectAll('*') .data(pie(data)) .enter() .append('path') .attr('d', d3.arc<d3.PieArcDatum<IData>>() .innerRadius(0) .outerRadius(radius) ) .attr('fill', (d) => d.data.color) .attr('stroke', 'black') .style('stroke-width', '2px') .style('opacity', 0.7); /** All finished */ console.log('Rendered!'); } 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. * */ public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject { return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options); } }
Here's wht it look like in Power BI - hopefully it's what you expect:
Hopefully this provides you with some guidance and how you might be able to start extending the code later on.
Good luck!
Daniel
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Hi @skutovicsakos,
I took your section of code and ran it.
Your code is erroring out midway and can't complete and this is why your visual is white. The custom visual SDK suppresses errors in the browser console, so it can be quite tricky to diagnose.
The code erroring is this line:
let data_ready = this.pie(d3.entries(this.data));
You are calling this.pie as a function, so if I track back where this is instantiated, I see this in the constructor:
this.pie = this.container.append('pie').classed('pie', true);
From my knowledge of d3, this.pie is instantiated as a HTML element rather than a function, so it probably won't do much here. This also creates a SVG element called pie in the browser DOM:
This is also not a valid SVG element type according to the SVG element reference.
From looking at your code, you are working off an existing example (which is a great way to start), and a quick web search leads me to one that looks like this. I'll work on the assumption that this is correct and I'll rework your code to render correctly.
One thing I often do is add some console.log() statements so that I can track where things might be going wrong. You'll see these in the code I'm going to include below, so if you don't see them in your browser console when running the visual then you can trace back to the previous part of the code to investigate why it doesn't work.
Here's the complete listing for the visual.ts I have working, so you should be able to copy/paste. I've added a lot of comments into the code to hopefully explain what I've done and why things are different from your example. Some decisions have been made to make it easier when you work on trying to get data out of your data model, so for instance, the data variable you were using looks a bit different from your original code.
'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 VisualObjectInstance = powerbi.VisualObjectInstance; import DataView = powerbi.DataView; import VisualObjectInstanceEnumerationObject = powerbi.VisualObjectInstanceEnumerationObject; import { VisualSettings } from './settings'; import * as d3 from 'd3'; export class Visual implements IVisual { private svg: d3.Selection<any, any, any, any>; /** I've added type declarations to your properties so that TypeScript will help you more when coding */ private visualSettings: VisualSettings; constructor(options: VisualConstructorOptions) { console.log('Visual constructor', options); /** Add the SVG element for the pie chart */ this.svg = d3 .select(options.element) .append('svg') .classed('pieChart', true); } public update(options: VisualUpdateOptions) { let dataView: DataView = options.dataViews[0]; this.visualSettings = VisualSettings.parse<VisualSettings>(dataView); console.log('Visual update', options); /** Clear the svg content, as it'll keep overwriting on every update otherwise */ console.log('Removing elements...'); this.svg.selectAll('*').remove(); /** Resolve dimensions based on viewport */ console.log('Resolving dimensions...'); let width = options.viewport.width, height = options.viewport.height, radius = Math.min(width, height) / 2; /** Apply width & height to main element, then add the group (g) element for the pie */ console.log('Setting and appending SVG elements...'); this.svg .attr('width', width) .attr('height', height); let container = this.svg .append('g') .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); /** Create dummy data - you were previously doing this as a class property but this will * trip you up when you try to bind to your data model, so if we do it here, we can swap * this out later on. * * I've also changed the 'shape' of this a bit so that this is easier to work with and * will be more portable if you try to work out mapping from the Power BI data model * you're passing in. I've also added the colour as a property of each entry so that we * can map this from a single place further down. * * By using an interface declaration to set this 'shape', we can validate assignment as * we code, and we can also use it in the d3.pie call below to enforce the type of data * we expect when we work with it, which gives us code completion and validation etc. in * TypeScript. */ interface IData { category: string; value: number; color: string; } /** We'll now assign the category/value/colour into this interface. Note that the data variable * is specified as an array of our IData interface from above. */ console.log('Assigning data...'); let data: IData[] = [ { category: 'a', value: 9, color: '#98abc5' }, { category: 'b', value: 20, color: '#8a89a6' }, { category: 'c', value: 30, color: '#7b6888'}, { category: 'd', value: 8, color: '#6b486b' }, { category: 'e', value: 12, color: '#a05d56' } ]; /** Compute the position of each group on the pie. Note that we're specifying the type after d3.pie * so that we can access the properties correctly and TypeScript will validate our code if we get it * wrong :) */ console.log('Creating pie function...'); let pie = d3.pie<IData>() .value((d) => d.value); /** Now, we'll build the pie chart */ console.log('Drawing chart...'); container .selectAll('*') .data(pie(data)) .enter() .append('path') .attr('d', d3.arc<d3.PieArcDatum<IData>>() .innerRadius(0) .outerRadius(radius) ) .attr('fill', (d) => d.data.color) .attr('stroke', 'black') .style('stroke-width', '2px') .style('opacity', 0.7); /** All finished */ console.log('Rendered!'); } 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. * */ public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject { return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options); } }
Here's wht it look like in Power BI - hopefully it's what you expect:
Hopefully this provides you with some guidance and how you might be able to start extending the code later on.
Good luck!
Daniel
Proud to be a Super User!
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Sorry for commenting on this old thread.
But it is almost what I am doing.
The piechart is auto sorting base on the values, but I want it to sort base on the original order of categories.
Is it possible?
User | Count |
---|---|
5 | |
3 | |
3 | |
2 | |
1 |
User | Count |
---|---|
11 | |
7 | |
5 | |
5 | |
4 |