issue
I encountered an issue while using D3.js. I have two functions, unexpectedCode and expectedCode, which are supposed to draw some circles in an SVG and change their color when clicked. The click event in expectedCode works fine, but in unexpectedCode, it only works the first time. Subsequent clicks only change the color of the last circle. I’m new to D3.js, and this is quite confusing for me. I would really appreciate it if you could help me analyze the problem!
core code
const unexpectedCode = (selection) => {
selection
.append("g")
.selectAll("circle")
.data(dots)
.join((enter) => {
enter
.append("circle")
.style("cursor", "pointer")
.attr("id", (d) => "circle" + d)
.attr("cx", (d) => d * 32)
.attr("cy", 20)
.attr("r", (d) => d * 5)
.call((circle) => {
circle.on("click", (e, d) => {
console.log("d", d);
enter
.select(`#circle` + d)
.transition()
.attr("fill", "red")
.transition()
.attr("fill", "black");
});
});
});
};
const expectedCode = (selection) => {
selection
.append("g")
.selectAll("circle")
.data(dots)
.join((enter) => {
enter
.append("circle")
.style("cursor", "pointer")
.attr("id", (d) => "circle" + d)
.attr("cx", (d) => d * 32)
.attr("cy", 20 + yOffset)
.attr("r", (d) => d * 5)
.each((datum) => {
enter.select(`#circle` + datum).call((circle) => {
circle.on("click", () => {
circle
.transition()
.attr("fill", "red")
.transition()
.attr("fill", "black");
});
});
});
});
};
reproduction
what I want
Explain the behavior or logic behind unexpectedCode.
new discovery
The issue seems to be with enter.select
. You can try using this demo, but I still don’t understand why. In my understanding, since I added elements to enter, I should be able to find these circles within enter, and it should be faster than using d3.select. However, in reality, using enter.select, my code doesn’t work correctly.
circle.on("click", (e, d) => {
console.log("d", d);
// use d3.select can work well
// d3.select(`#circle` + d)
// .transition()
// .attr("fill", "red")
// .transition()
// .attr("fill", "black");
enter
.select(`#circle` + d)
.transition()
.attr("fill", "red")
.transition()
.attr("fill", "black");
});
2
Answers
I would normally suggest delegation but in this case it is more readable to bind the event handler immediately after appending the circle elements without re-selecting or iterating and more efficient because it avoids unnecessary re-selections (enter.select() inside .each()) and re-iterations over the data.
codesandbox
I’m not sure I follow your code and event binding. A more canonical way to do this with
d3
would be: