Power BI is turning 10, and we’re marking the occasion with a special community challenge. Use your creativity to tell a story, uncover trends, or highlight something unexpected.
Get startedJoin us for an expert-led overview of the tools and concepts you'll need to become a Certified Power BI Data Analyst and pass exam PL-300. Register now.
Hi all,
I am new to the D3.js visual in PowerBI and can't seem to get it to work. I have followed the tutorial the best I know how without any luck. Here is the tutorial I am referring to: https://azurebi-docs.jppp.org/powerbi-visuals/d3js.html?tabs=docs%2Cdocs-open
I have an animated multiple line chart: the lines are the different ids in my dataset, the animation is based off of a timestamp column in which the datapoints (and the connecting lines) display based on each datapoint's timestamp. There is a replay button to replay the animation.
My data has the following columns: id, left, top, timestamp. One id may have multiple rows. y-axis is left, x-axis is top.
I also have a background image for the plotArea.
This is what i have so far in the D3.JS editor:
var margin = {top: 20, right: 30, bottom: 50, left: 40},
width = pbi.width - margin.left - margin.right,
height = pbi.height - margin.top - margin.bottom;
var x = d3.scaleLinear()
.domain([0, 1800])
.range([margin.left, width - margin.right]).clamp(true);
var y = d3.scaleLinear()
.domain([0, 1200])
.range([margin.top, height - margin.bottom]).clamp(true);
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var plotAreaWidth = pbi.width - margin.left - margin.right;
var plotAreaHeight = pbi.height - margin.top - margin.bottom;
var plotArea = svg.append("g")
.attr("class", "plot-area")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
plotArea.append("image")
.attr("xlink:href", "backdrop.png")
.attr("width", plotAreaWidth)
.attr("height", plotAreaHeight);
svg.append("g")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).ticks(4).tickSizeOuter(0))
.call(function(g) { g.select(".domain").remove(); })
.call(function(g) { g.selectAll(".tick line").clone()
.attr("y2", -height)
.attr("stroke-opacity", 0.1); })
.append("text")
.attr("x", width - 4)
.attr("y", -4)
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.attr("fill", "currentColor")
.text("Top");
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y).ticks(6).tickSizeOuter(0))
.call(function(g) { g.select(".domain").remove(); })
.call(function(g) { g.selectAll(".tick line").clone()
.attr("x2", width)
.attr("stroke-opacity", 0.1); })
.append("text")
.attr("x", 4)
.attr("y", margin.top - 10)
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.attr("fill", "currentColor")
.text("Left");
var loadChart = function(){
pbi.dsv(function(data){
data.sort(function(a, b) { return new Date(a.timestamp) - new Date(b.timestamp); });
var ids = Array.from(new Set(data.map(function(d) { return d.id; })));
var color = d3.scaleOrdinal()
.domain(ids)
.range(d3.schemeCategory10);
ids.forEach(function(id) {
var idData = data.filter(function(d) { return d.id === id; });
var lineData = idData; // Include all data points for the line
plotArea.append("path")
.datum(lineData)
.attr("fill", "none")
.attr("stroke", color(id))
.attr("stroke-width", 2.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", d3.line()
.x(function(d) { return x(d.top); })
.y(function(d) { return y(d.left); }));
});
data.forEach(function(d, i) {
setTimeout(function() {
plotArea.append("circle")
.attr("cx", x(d.top))
.attr("cy", y(d.left))
.attr("r", 4)
.attr("fill", "steelblue")
.on("mouseover", function() {
d3.select(this.nextSibling).style("visibility", "visible");
})
.on("mouseout", function() {
d3.select(this.nextSibling).style("visibility", "hidden");
});
plotArea.append("text")
.attr("x", x(d.top))
.attr("y", y(d.left) - 10)
.attr("text-anchor", "middle")
.text(d.timestamp.toLocaleString())
.attr("fill", "black")
.style("visibility", "hidden");
ids.forEach(function(id) {
var idData = data.filter(function(item) { return item.id === id && item.timestamp <= d.timestamp; });
var lineData = idData; // Include all data points for the line
plotArea.select('path[stroke="' + color(id) + '"]')
.datum(lineData)
.attr("d", d3.line()
.x(function(d) { return x(d.top); })
.y(function(d) { return y(d.left); }));
});
}, i * 100);
});
});
}
loadChart();
var replayButton = d3.select("body").append("button").attr("id", "replay-button").text("Replay");
replayButton.on("click", function() {
d3.select("#chart").selectAll("*").remove(); // Clear the chart
drawConnectedScatterplot(data);
});
I have tried different variations and have tried simplifying it by focusing on simply getting a line chart to display but i can't get it to work, nothing gets generated at all - all i see is a blank placeholder.
This is the html version that works as expected:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connected Scatterplot</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<div id="chart"></div>
<script>
function drawConnectedScatterplot(data) {
// Sort the data based on the timestamp
data.sort(function(a, b) { return a.timestamp - b.timestamp; });
var width = 600;
var height = 400;
var margin = { top: 20, right: 30, bottom: 50, left: 40 };
var x = d3.scaleLinear()
.domain([0, 1800])
.range([margin.left, width - margin.right]).clamp(true);
var y = d3.scaleLinear()
.domain([0, 1200])
.range([margin.top, height - margin.bottom]).clamp(true);
var svg = d3.select("#chart").append("svg")
.attr("width", width)
.attr("height", height);
var plotAreaWidth = width - margin.left - margin.right;
var plotAreaHeight = height - margin.top - margin.bottom;
var plotArea = svg.append("g")
.attr("class", "plot-area")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Append background image to the plot area
plotArea.append("image")
.attr("xlink:href", "backdrop.png")
.attr("width", plotAreaWidth)
.attr("height", plotAreaHeight);
svg.append("g")
.attr("transform", "translate(0," + (height - margin.bottom) + ")")
.call(d3.axisBottom(x).ticks(4).tickSizeOuter(0))
.call(function(g) { g.select(".domain").remove(); })
.call(function(g) { g.selectAll(".tick line").clone()
.attr("y2", -height)
.attr("stroke-opacity", 0.1); })
.append("text")
.attr("x", width - 4)
.attr("y", -4)
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.attr("fill", "currentColor")
.text("Top");
svg.append("g")
.attr("transform", "translate(" + margin.left + ",0)")
.call(d3.axisLeft(y).ticks(6).tickSizeOuter(0))
.call(function(g) { g.select(".domain").remove(); })
.call(function(g) { g.selectAll(".tick line").clone()
.attr("x2", width)
.attr("stroke-opacity", 0.1); })
.append("text")
.attr("x", 4)
.attr("y", margin.top - 10)
.attr("font-weight", "bold")
.attr("text-anchor", "start")
.attr("fill", "currentColor")
.text("Left");
var ids = Array.from(new Set(data.map(function(d) { return d.id; })));
var color = d3.scaleOrdinal()
.domain(ids)
.range(d3.schemeCategory10);
ids.forEach(function(id) {
var idData = data.filter(function(d) { return d.id === id; });
var lineData = idData; // Include all data points for the line
plotArea.append("path")
.datum(lineData)
.attr("fill", "none")
.attr("stroke", color(id))
.attr("stroke-width", 2.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("d", d3.line()
.x(function(d) { return x(d.top); })
.y(function(d) { return y(d.left); }));
});
data.forEach(function(d, i) {
setTimeout(function() {
plotArea.append("circle")
.attr("cx", x(d.top))
.attr("cy", y(d.left))
.attr("r", 4)
.attr("fill", "steelblue")
.on("mouseover", function() {
d3.select(this.nextSibling).style("visibility", "visible");
})
.on("mouseout", function() {
d3.select(this.nextSibling).style("visibility", "hidden");
});
plotArea.append("text")
.attr("x", x(d.top))
.attr("y", y(d.left) - 10)
.attr("text-anchor", "middle")
.text(d.timestamp.toLocaleString())
.attr("fill", "black")
.style("visibility", "hidden");
ids.forEach(function(id) {
var idData = data.filter(function(item) { return item.id === id && item.timestamp <= d.timestamp; });
var lineData = idData; // Include all data points for the line
plotArea.select('path[stroke="' + color(id) + '"]')
.datum(lineData)
.attr("d", d3.line()
.x(function(d) { return x(d.top); })
.y(function(d) { return y(d.left); }));
});
}, i * 100);
});
}
var data = [
{ id: "ID1", left: 744, top: 987, timestamp: new Date("2024-01-01T05:00:00") },
{ id: "ID1", left: 828, top: 938, timestamp: new Date("2024-01-01T10:00:00") },
{ id: "ID1", left: 886, top: 883, timestamp: new Date("2024-01-01T11:00:00") },
{ id: "ID1", left: 940, top: 734, timestamp: new Date("2024-01-01T12:00:00") },
{ id: "ID1", left: 997, top: 638, timestamp: new Date("2024-01-01T13:00:00") },
{ id: "ID1", left: 1056, top: 577, timestamp: new Date("2024-01-01T14:00:00") },
{ id: "ID1", left: 1101, top: 516, timestamp: new Date("2024-01-01T15:00:00") },
{ id: "ID1", left: 1146, top: 473, timestamp: new Date("2024-01-01T16:00:00") },
{ id: "ID1", left: 1179, top: 411, timestamp: new Date("2024-01-01T17:00:00") },
{ id: "ID1", left: 1212, top: 372, timestamp: new Date("2024-01-01T18:00:00") },
{ id: "ID2", left: 1417, top: 312, timestamp: new Date("2024-01-01T06:00:00") },
{ id: "ID2", left: 1436, top: 330, timestamp: new Date("2024-01-01T11:30:00") },
{ id: "ID2", left: 1448, top: 355, timestamp: new Date("2024-01-01T12:45:00") },
{ id: "ID2", left: 1445, top: 386, timestamp: new Date("2024-01-01T13:30:00") },
{ id: "ID2", left: 1444, top: 420, timestamp: new Date("2024-01-01T14:15:00") },
{ id: "ID2", left: 1430, top: 467, timestamp: new Date("2024-01-01T15:00:00") },
{ id: "ID2", left: 1414, top: 512, timestamp: new Date("2024-01-01T15:45:00") },
{ id: "ID2", left: 1392, top: 573, timestamp: new Date("2024-01-01T16:30:00") },
{ id: "ID2", left: 1363, top: 628, timestamp: new Date("2024-01-01T17:15:00") },
{ id: "ID2", left: 1318, top: 699, timestamp: new Date("2024-01-01T18:00:00") }
];
drawConnectedScatterplot(data);
var replayButton = d3.select("body").append("button").attr("id", "replay-button").text("Replay");
replayButton.on("click", function() {
d3.select("#chart").selectAll("*").remove(); // Clear the chart
drawConnectedScatterplot(data);
});
</script>
</body>
</html>
Since i am in powerbi desktop im finding it excessively difficult to find the issue/s since i can't debug as i would in the browser.
Any guidance on how to get it to work is much appeciated!
Solved! Go to Solution.
HI @Anonymous,
I'd like to suggest you to check the d3.js visual official support page to know the usage and the limitations at the first:
Power BI D3.js Visual - Azure BI (jppp.org)
Regards,
Xiaoxin Sheng
HI @Anonymous,
I'd like to suggest you to check the d3.js visual official support page to know the usage and the limitations at the first:
Power BI D3.js Visual - Azure BI (jppp.org)
Regards,
Xiaoxin Sheng
This is your chance to engage directly with the engineering team behind Fabric and Power BI. Share your experiences and shape the future.
Check out the June 2025 Power BI update to learn about new features.
User | Count |
---|---|
4 | |
4 | |
3 | |
3 | |
2 |