I'm new to d3.js and I'm struggling to fix a bug on a simple plot. I'm basically trying to create an animated lollipop plot that can be updated by clicking on a button. This is the code I wrote:
// !preview r2d3 data = structure(list(Name = c("A", "B", "C", "D"), Posterior = c(0.75, 0.45, 0.25, 0.1), Prior = c(0.5, 0.5, 0.55, 0.01)), class = "data.frame", row.names = c(NA, -3L)), dependencies = "d3-jetpack"
// set up constants used throughout script
const margin = { top: 80, right: 100, bottom: 40, left: 60 };
const plotWidth = 800 - margin.left - margin.right;
const plotHeight = 400 - margin.top - margin.bottom;
const lineWidth = 4;
const mediumText = 18;
const bigText = 28;
// set width and height of svg element (plot + margin)
svg.attr("width", plotWidth + margin.left + margin.right)
.attr("height", plotHeight + margin.top + margin.bottom);
// create plot group and move it
let plotGroup = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// x-axis
// x-axis goes from 0 to width of plot
let xAxis = d3.scaleLinear()
.domain([0, 1])
.range([0, plotWidth]);
// y-axis
// y-axis goes from height of plot to 0
let yAxis = d3.scaleBand()
.domain(data.map(function(d) { return d.Name; }))
.padding(1)
.range([plotHeight, 0]);
// add x-axis to plot
// move x axis to bottom of plot (height)
// format tick values as date (no comma in e.g. 2,001)
// set stroke width and font size
plotGroup.append("g")
.attr("transform", "translate(0," + plotHeight + ")")
.call(d3.axisBottom(xAxis).tickFormat(d3.format(".0%"))) // Format ticks as percentage
.attr("stroke-width", lineWidth)
.attr("font-size", mediumText);
// add y-axis to plot
// set stroke width and font size
plotGroup.append("g")
.call(d3.axisLeft(yAxis))
.attr("stroke-width", lineWidth)
.attr("font-size", mediumText);
// Lines
plotGroup.selectAll("myline")
.data(data)
.enter()
.append("line")
.attr("x1", xAxis(0.5))
.attr("x2", function(d) { return xAxis(d.Prior); })
.attr("y1", function(d) { return yAxis(d.Name); })
.attr("y2", function(d) { return yAxis(d.Name); })
.attr("stroke", "grey");
// Circles -> start at priors
plotGroup.selectAll("mycircle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return xAxis(d.Prior); })
.attr("cy", function(d) { return yAxis(d.Name); })
.attr("r", "7")
.style("fill", "#69b3a2")
.attr("stroke", "black");
// Change the X coordinates of line and circle
plotGroup.selectAll("circle")
.transition()
.duration(2000)
.attr("cx", function(d) { return xAxis(d.Posterior); });
plotGroup.selectAll("line")
.transition()
.duration(2000)
.attr("x2", function(d) { return xAxis(d.Posterior); });
// Add a dashed vertical line at x = 0.5
plotGroup.append("line")
.attr("x1", xAxis(0.5))
.attr("y1", 0)
.attr("x2", xAxis(0.5))
.attr("y2", plotHeight)
.attr("stroke", "black")
.attr("stroke-dasharray", "4"); // Make the line dashed
// Add title starting from the left
svg.append("text")
.attr("x", margin.left) // Start from the left margin
.attr("y", margin.top / 2)
.attr("text-anchor", "start") // Align to the start (left)
.attr("font-size", bigText)
.attr("font-family", "Roboto, sans-serif")
.attr("font-weight", "bold")
.text("Probability of a positive impact");
// Add a button
const buttonWidth = 100;
const buttonHeight = 30;
let buttonText = "Posterior"; // Initial text
const button = svg.append("rect")
.attr("x", plotWidth + margin.left - buttonWidth)
.attr("y", margin.top / 2 - buttonHeight / 2)
.attr("width", buttonWidth)
.attr("height", buttonHeight)
.attr("rx", 5) // rounded corners
.attr("ry", 5)
.attr("fill", "blue") // Button color
.attr("cursor", "pointer") // Change cursor on hover
.on("click", buttonClick);
const buttonTextElement = svg.append("text")
.attr("x", plotWidth + margin.left - buttonWidth / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "Roboto, sans-serif")
.attr("font-size", mediumText)
.attr("fill", "white")
.attr("cursor", "pointer")
.text(buttonText)
.on("click", buttonClick);
// Click event handler for the button
function buttonClick() {
// Toggle between "Posterior" and "Prior"
buttonText = (buttonText === "Posterior") ? "Prior" : "Posterior";
buttonTextElement.text(buttonText);
// Update x-coordinates of circles based on the button text
plotGroup.selectAll("circle")
.transition()
.duration(2000)
.attr("cx", function(d) { return xAxis(buttonText === "Posterior" ? d.Posterior : d.Prior); });
// Update x2-coordinates of lines based on the button text
plotGroup.selectAll("line")
.transition()
.duration(2000)
.attr("x2", function(d) { return xAxis(buttonText === "Posterior" ? d.Posterior : d.Prior); });
}
As you can see in the image bellow, the circles are moving correctly but I cannot figure out how to make the lines move with them.
The problem comes from your definition of line, you have to add a class to notify the type of items you want to select (select only line for data and no ticks or other lines). I have added a class line to specific line:
// Lines
plotGroup.selectAll("myline")
.data(data)
.enter()
.append("line")
.attr("class", "line")
.attr("x1", xAxis(0.5))
.attr("x2", function(d) { return xAxis(d.Prior); })
.attr("y1", function(d) { return yAxis(d.Name); })
.attr("y2", function(d) { return yAxis(d.Name); })
.attr("stroke", "grey");
and in the event click i call the class:
// Update x2-coordinates of lines based on the button text
plotGroup.selectAll("line.line")
.transition()
.duration(2000)
.attr("x2", function(d) {return buttonText === "Posterior" ? xAxis(d.Posterior) : xAxis(d.Prior); });
see my code for the result.
const data = [{"Name": "A", "Prior": 0.5, "Posterior": 0.75},{"Name": "B", "Prior":0.5, "Posterior": 0.45},{"Name": "C", "Prior":0.55, "Posterior": 0.25},{"Name": "D", "Prior":0.01, "Posterior": 0.1}]
// set up constants used throughout script
const margin = { top: 80, right: 100, bottom: 40, left: 60 };
const plotWidth = 800 - margin.left - margin.right;
const plotHeight = 400 - margin.top - margin.bottom;
const lineWidth = 4;
const mediumText = 18;
const bigText = 28;
// set width and height of svg element (plot + margin)
var svg = d3.select("#my_dataviz")
.append("svg")
.attr("width", plotWidth + margin.left + margin.right)
.attr("height", plotHeight + margin.top + margin.bottom);
// create plot group and move it
let plotGroup = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// x-axis
// x-axis goes from 0 to width of plot
let xAxis = d3.scaleLinear()
.domain([0, 1])
.range([0, plotWidth]);
// y-axis
// y-axis goes from height of plot to 0
let yAxis = d3.scaleBand()
.domain(data.map(function(d) { return d.Name; }))
.padding(1)
.range([plotHeight, 0]);
// add x-axis to plot
// move x axis to bottom of plot (height)
// format tick values as date (no comma in e.g. 2,001)
// set stroke width and font size
plotGroup.append("g")
.attr("transform", "translate(0," + plotHeight + ")")
.call(d3.axisBottom(xAxis).tickFormat(d3.format(".0%"))) // Format ticks as percentage
.attr("stroke-width", lineWidth)
.attr("font-size", mediumText);
// add y-axis to plot
// set stroke width and font size
plotGroup.append("g")
.call(d3.axisLeft(yAxis))
.attr("stroke-width", lineWidth)
.attr("font-size", mediumText);
// Lines
plotGroup.selectAll("myline")
.data(data)
.enter()
.append("line")
.attr("class", "line")
.attr("x1", xAxis(0.5))
.attr("x2", function(d) { return xAxis(d.Prior); })
.attr("y1", function(d) { return yAxis(d.Name); })
.attr("y2", function(d) { return yAxis(d.Name); })
.attr("stroke", "grey");
// Circles -> start at priors
plotGroup.selectAll("mycircle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return xAxis(d.Prior); })
.attr("cy", function(d) { return yAxis(d.Name); })
.attr("r", "7")
.style("fill", "#69b3a2")
.attr("stroke", "black");
// Change the X coordinates of line and circle
plotGroup.selectAll("circle")
.transition()
.duration(2000)
.attr("cx", function(d) { return xAxis(d.Posterior); });
plotGroup.selectAll("line")
.transition()
.duration(2000)
.attr("x2", function(d) { return xAxis(d.Posterior); });
// Add a dashed vertical line at x = 0.5
plotGroup.append("line")
.attr("x1", xAxis(0.5))
.attr("y1", 0)
.attr("x2", xAxis(0.5))
.attr("y2", plotHeight)
.attr("stroke", "black")
.attr("stroke-dasharray", "4"); // Make the line dashed
// Add title starting from the left
svg.append("text")
.attr("x", margin.left) // Start from the left margin
.attr("y", margin.top / 2)
.attr("text-anchor", "start") // Align to the start (left)
.attr("font-size", bigText)
.attr("font-family", "Roboto, sans-serif")
.attr("font-weight", "bold")
.text("Probability of a positive impact");
// Add a button
const buttonWidth = 100;
const buttonHeight = 30;
let buttonText = "Posterior"; // Initial text
const button = svg.append("rect")
.attr("x", plotWidth + margin.left - buttonWidth)
.attr("y", margin.top / 2 - buttonHeight / 2)
.attr("width", buttonWidth)
.attr("height", buttonHeight)
.attr("rx", 5) // rounded corners
.attr("ry", 5)
.attr("fill", "blue") // Button color
.attr("cursor", "pointer") // Change cursor on hover
.on("click", buttonClick);
const buttonTextElement = svg.append("text")
.attr("x", plotWidth + margin.left - buttonWidth / 2)
.attr("y", margin.top / 2)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "Roboto, sans-serif")
.attr("font-size", mediumText)
.attr("fill", "white")
.attr("cursor", "pointer")
.text(buttonText)
.on("click", buttonClick);
// Click event handler for the button
function buttonClick() {
// Toggle between "Posterior" and "Prior"
buttonText = (buttonText === "Posterior") ? "Prior" : "Posterior";
buttonTextElement.text(buttonText);
// Update x-coordinates of circles based on the button text
plotGroup.selectAll("circle")
.transition()
.duration(2000)
.attr("cx", function(d) {return buttonText === "Posterior" ? xAxis(d.Posterior) : xAxis(d.Prior); });
// Update x2-coordinates of lines based on the button text
plotGroup.selectAll("line.line")
.transition()
.duration(2000)
.attr("x2", function(d) {return buttonText === "Posterior" ? xAxis(d.Posterior) : xAxis(d.Prior); });
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div id="my_dataviz"></div>