skt_fans_boy
skt_fans_boy

Reputation: 73

Pass the current parameter to function in a loop

I'm using D3 to print multiple rect out. Now I'hope the rect could allow user click on it and the active some function.

For example, there are 3 rectangles, "Tom", "Mary" and "Ben". When user click on different rect, it will pass the current value. Like when I click on "Tom" it will pass "Tom" to call the function.

However, I found that after finish print out the rect, no matter I click on which rect, they both return the least value of the dataset.

In my example, even I click on "Tom" or "Mary", both return "Ben".

for (var i = 0; i < ward_set.length; i++) {
  var ward_id = ward_set[i];
  legend.append("rect")
    .attr("x", legend_x + 180 + 100 * n)
    .attr("y", legend_y)
    .attr("width", 18)
    .attr("height", 18)
    .attr("fill", colors[count])
    .attr("class", "legend" + ward_set[i])
    .on("click", function() {
      console.log(ward_id);
    });
}

Upvotes: 1

Views: 38

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102188

Your question perfectly illustrates a very important principle, a rule of thumb if you like, when writing a D3 code: never use a loop to append elements. Use the enter/update/exit pattern instead.

What happens when you use a loop (be it for, while, forEach etc...) is that not only there is no data binding, but also you experience this strange outcome you described (getting always the last value), which is explained here: JavaScript closure inside loops – simple practical example

Therefore, the solution using a D3 idiomatic enter selection would be:

const data = ["Tom", "Mary", "Ben"];

const svg = d3.select("svg");

const rects = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  //etc...

Then, in the click listener, you get the first argument, which is the datum:

.on("click", function(d) {
    console.log(d)
})

Here is a demo:

const data = ["Tom", "Mary", "Ben"];

const svg = d3.select("svg");

const rects = svg.selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("width", 50)
  .attr("height", 150)
  .attr("x", (_, i) => i * 60)
  .on("click", function(d) {
    console.log(d)
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Upvotes: 1

Related Questions