Register now to learn Fabric in free live sessions led by the best Microsoft experts. From Apr 16 to May 9, in English and Spanish.
I am following the video tutorial (https://channel9.msdn.com/Blogs/Azure/Building-Power-BI-custom-visuals-that-meet-your-app-needs) to create a bar chart (custom visual) within Power BI.
Code snippet (in the video at 13.40):
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
bars.enter().append('rect').classed('bar', true);
bars
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
The above code is written in update method of visual.ts file.
As per my understanding, the enter() method will create `rect` elements with class `bar` based on the viewModel.dataPoints data.
The problem is I am not able to get it to work upon initial load, when the visual is loaded, this works only when the visual is resized. I cannot see any attributes getting applied on the `rect` elements upon load, but its getting applied only when a resize is made on the visual.
If I changed the code to:
bars.enter().append('rect').classed('bar', true)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.transition()
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
The above code works fine, The attributes are getting applied for both initial load and resize.
Additional Info (Constructor code)
let svg = this.svg = d3.select(options.element).append('svg').classed('barChart', true);
this.barContainer = svg.append('g').classed('barContainer', true);
this.xAxis = svg.append('g').classed('xAxis', true);
Is this the way its supposed to work in the latest version (D3 Js 5.0)? or I am doing anything wrong?
Solved! Go to Solution.
Found a solution, got it from: https://github.com/microsoft/PowerBI-visuals-sampleBarChart/blob/master/src/barChart.ts
Updated code:
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
const a = bars.enter().append('rect').merge(<any>bars);
a.classed('bar', true);
// bars = this.barContainer.selectAll('.bar');
a
.transition().duration(50)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
This is achieved using the function .merge() - making it work on initial load and update
Ref: https://stackoverflow.com/questions/47066905/d3-merge-function
I have an issue realted to D3.js Custom Visual for Power BI - I am trying to make a scatter plot with data points replaced by images. The images are coming up as broken images , is this because power bi itself is limiting this or something is not right in the code. Could you please help me out with this issue:
Here is my code:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = pbi.width - margin.left - margin.right, // ALTER: Changed fixed width with the 'pbi.width' variable
height = pbi.height - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], 0.1, 0.2);
var y = d3.scale.linear()
.range([height, 0]);
// append the svg object to the body of the page
var svg = d3.select("#chart")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// ALTER: Replaced the d3.tsv function with the pbi variant: pbi.dsv
pbi.dsv(type, function(letters) {
x.domain(letters.map(function(d) { return d.name; }));
y.domain([0, d3.max(letters,function(d) { return d.rollnumber; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
svg.append("g")
.selectAll(".mypoint")
.data(letters)
.enter()
.append("image")
.attr("xlink:href", function(d){ return "C:\Users\UditJoshi\Desktop\Onemda ORS\Emojees\Learning.png"})
.attr("x", function(d){ return d.name })
.attr("y", function(d){ return d.rollnumber })
.attr("width", 16)
.attr("height", 16);
});
function type(d) {
d.rollnumber= +d.rollnumber;
return d;
}
Below is the graph:
Hi @hari_krishnan,
On reload, the constructor fires, then update will only fire if the dataViewMappings are correct. From your explanation, it sounds like they are, as a resize event will cause the visual to respond as you expect.
Once the visual is established and then change a measure or a field (without reloading, so only the update method executes), does your chart render correctly?
My suspicion is that for the initial (first) run of the update method, or for subsequent changes, the data might not be mapped in the correct order. This is hard to confirm with only a few snippets of the file.
Are you able to share your code for the entire visual.ts file so I can trace its workflow?
If you're pasting the code into your reply, please use the Insert/edit code sample button in the toolbar with the language set to Javascript, as this will preserve special characters
Thanks,
Daniel
Proud to be a Super User!
My course: Introduction to Developing Power BI Visuals
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Found a solution, got it from: https://github.com/microsoft/PowerBI-visuals-sampleBarChart/blob/master/src/barChart.ts
Updated code:
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
const a = bars.enter().append('rect').merge(<any>bars);
a.classed('bar', true);
// bars = this.barContainer.selectAll('.bar');
a
.transition().duration(50)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
bars.exit().remove();
This is achieved using the function .merge() - making it work on initial load and update
Ref: https://stackoverflow.com/questions/47066905/d3-merge-function
Hi @hari_krishnan,
I was just working through after getting your initial response, but if you're all sorted, that's good too! Excellent work 🙂
Daniel
Proud to be a Super User!
My course: Introduction to Developing Power BI Visuals
On how to ask a technical question, if you really want an answer (courtesy of SQLBI)
Hi @dm-p ,
After lots of trial and error, I found out that the code can be modified as:
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
bars.enter().append('rect').classed('bar', true)
bars = this.barContainer.selectAll('.bar');
bars
.transition().duration(50)
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
The above code is working fine for both initial load and update.
But I am sure that this is not the correct approach and cause performance issues in the future. Please help me with this.
Hi @dm-p ,
I have been trying to reply you, but the system is flagging my message as Spam.
The answer for your question (Once the visual is established and then change a measure or a field (without reloading, so only the update method executes), does your chart render correctly?) is Yes, It loads up fine when field is reselected or changed without reloading visual.
Since posting code is getting flagged as Spam, I am pasting a link to the code to the visual.ts file: https://pastebin.com/KhxyCYpS
Hi, Thanks for the reply.
Once the visual is established and then change a measure or a field (without reloading, so only the update method executes), does your chart render correctly?
Answer: Yes, If I reselect the same field or if I select another field, the visual loads up fine without reloading the visual.
Visual.ts file content:
"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';
interface DataPoint {
value: number;
category: string
}
interface ViewModel {
dataPoints: DataPoint[];
dataMax: number
}
let testData: DataPoint[] = [
{
value: 10,
category: 'a'
},
{
value: 15,
category: 'b'
},
{
value: 2,
category: 'c'
},
{
value: 25,
category: 'd'
},
{
value: 8,
category: 'e'
}
]
export class Visual implements IVisual {
settings: VisualSettings;
private svg: d3.Selection<SVGElement, any, any, any>;
private host: powerbi.extensibility.IVisualHost;
private barChartContainer: d3.Selection<SVGElement, any, any, any>;
private barContainer: d3.Selection<SVGElement, any, any, any>;
private xAxis: d3.Selection<SVGElement, any, any, any>;
private bars: d3.Selection<SVGElement, any, any, any>;
constructor(options: VisualConstructorOptions) {
console.log('constructor called');
this.host = options.host;
let svg = this.svg = d3.select(options.element).append('svg').classed('barChart', true);
this.barContainer = svg.append('g').classed('barContainer', true);
this.xAxis = svg.append('g').classed('xAxis', true);
}
public update(options: VisualUpdateOptions) {
console.log('update called');
let width = options.viewport.width;
let height = options.viewport.height;
let margin = {
top: 0,
bottom: 25,
left: 0,
right: 0
};
let viewModel: ViewModel = {
dataPoints: testData,
dataMax: d3.max(testData.map((dataPoint) => dataPoint.value))
};
this.svg.attr('width', width);
this.svg.attr('height', height);
height -= margin.bottom;
let yScale = d3.scaleLinear()
.domain([0, viewModel.dataMax])
.range([height, 0]);
let xScale = d3.scaleBand()
.domain(viewModel.dataPoints.map(d => d.category))
.rangeRound([0, width])
.padding(0.1);
let xAxis = d3.axisBottom(xScale);
this.xAxis.attr('transform', 'translate(0, ' + height + ')').call(xAxis);
let bars = this.barContainer.selectAll('.bar').data(viewModel.dataPoints);
try {
bars.enter().append('rect').classed('bar', true);
// .attr('width', xScale.bandwidth()) // need to uncomment for showing the char
// upon initial load
// .attr('height', d => height - yScale(d.value))
// .attr('y', d => yScale(d.value))
// .attr('x', d => xScale(d.category));
bars.transition()
.attr('width', xScale.bandwidth())
.attr('height', d => height - yScale(d.value))
.attr('y', d => yScale(d.value))
.attr('x', d => xScale(d.category));
} catch (e) {
console.log(e);
}
bars.exit().remove();
}
public enumerateObjectInstances(options: EnumerateVisualObjectInstancesOptions): VisualObjectInstance[] | VisualObjectInstanceEnumerationObject {
return VisualSettings.enumerateObjectInstances(this.settings || VisualSettings.getDefault(), options);
}
}
Covering the world! 9:00-10:30 AM Sydney, 4:00-5:30 PM CET (Paris/Berlin), 7:00-8:30 PM Mexico City
Check out the April 2024 Power BI update to learn about new features.
User | Count |
---|---|
14 | |
2 | |
2 | |
1 | |
1 |