skip to Main Content

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

codesandbox

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


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

    const fixedUnexpectedCode = (selection) => {
      selection
        .append("g")
        .selectAll("circle")
        .data(dots)
        .join((enter) => {
          const circle = enter
            .append("circle")
            .style("cursor", "pointer")
            .attr("id", (d) => "circle" + d)
            .attr("cx", (d) => d * 32)
            .attr("cy", 20)
            .attr("r", (d) => d * 5);
    
          // Attach click event directly to the circle selection
          circle.on("click", (e, d) => {
            console.log("d", d);
            d3.select(`#circle${d}`)
              .transition()
              .attr("fill", "red")
              .transition()
              .attr("fill", "black");
          });
        });
    };
    
    Login or Signup to reply.
  2. I’m not sure I follow your code and event binding. A more canonical way to do this with d3 would be:

    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)
            .on("click", function() {
              d3.select(this)
                .transition()
                .attr("fill", "red")
                .transition()
                .attr("fill", "black");
            })
        });
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search