Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

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.

Reply
hari_krishnan
Helper I
Helper I

Understanding the working of .enter() in D3 Js and Power BI

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?

 

 

1 ACCEPTED 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

View solution in original post

8 REPLIES 8
uditjoshi91
Frequent Visitor

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: 

 

Capture.PNG

dm-p
Super User
Super User

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

image.png

Thanks,

Daniel





Did I answer your question? Mark my post as a solution!

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





Did I answer your question? Mark my post as a solution!

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)




@dm-p  Thanks a lot for your help

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);
    }
}

 

 

 

Helpful resources

Announcements
Microsoft Fabric Learn Together

Microsoft Fabric Learn Together

Covering the world! 9:00-10:30 AM Sydney, 4:00-5:30 PM CET (Paris/Berlin), 7:00-8:30 PM Mexico City

PBI_APRIL_CAROUSEL1

Power BI Monthly Update - April 2024

Check out the April 2024 Power BI update to learn about new features.

April Fabric Community Update

Fabric Community Update - April 2024

Find out what's new and trending in the Fabric Community.

Top Kudoed Authors