2

I'm trying to add an event to the bars in my graph. I tried this function bellow, but with this function it doesn't matter what bar I click, it will always return the last key in the array.

My guess would be this has to do with asynchronization, because it returns the last key value.

for (var key in data) {
  bar = bars.append('rect')
    .attr('class', 'bar')
    .attr('x', (dimensions.width / data.length) * currentId + 41)
    .attr('y', 100 - data[key] + 10).attr('height', data[key])
    .attr('width', dimensions.width / data.length)
    .attr('fill', '#4682B4')
    .on('click', (bar = key) => {
        console.log(key) //Always returns the same key
    });
  currentId++;
}

I have also tried to copy one of the keys the array contains and make a if-statement like this:

console.log(key === 1 ? true : false);

This will return true and false, exactly as it should. Another reason why I think this has to do with async.

My essential question is; How can I make a click-event on this bar which will return the correct key

FlorisdG
  • 754
  • 5
  • 11
  • 31

1 Answers1

2

Before anything else: this is not the idiomatic way of adding events in D3. As a general rule, when you write a D3 code, you normally don't need any kind of loop. Of course, we use loops sometimes, but in very specific situations and to solve very specific problems. Thus, 98.57% of the loops found in D3 codes are unnecessary (source: FakeData Inc.), be it a for...in, a for...of or a simple for loop.

That being said, let's see what's happening here.

Your real problem has nothing to do with D3 or with async code. Actually, your problem can be explained by this excellent answer: JavaScript closure inside loops – simple practical example (I'll avoid closing this as a duplicate, though).

After reading the answer in the link above, let's see two demos.

The first one, using var. Please click the circles:

var data = [{
  name: "foo",
  value: 1
}, {
  name: "bar",
  value: 2
}, {
  name: "baz",
  value: 3
}];

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

for (var key in data) {
  var foo = key;//look at the var here
  circle = svg.append("circle")
    .attr("cy", 50)
    .attr("fill", "teal")
    .attr("cx", d=> 20 + key*50)
    .attr("r", 15)
    .on('click', () => {
        console.log(foo)
    });
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Now another one, using let, please click the circles and compare the results:

var data = [{
  name: "foo",
  value: 1
}, {
  name: "bar",
  value: 2
}, {
  name: "baz",
  value: 3
}];

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

for (var key in data) {
  let foo = key;//look at the let here
  circle = svg.append("circle")
    .attr("cy", 50)
    .attr("fill", "teal")
    .attr("cx", d=> 20 + key*50)
    .attr("r", 15)
    .on('click', () => {
        console.log(foo)
    });
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Community
  • 1
  • 1
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • Could you maybe provide a link to the source of the statement "As a general rule, when you write a D3 code, you normally don't need any kind of loop."? – FlorisdG Apr 26 '17 at 09:16
  • The source of that statement is my professional experience and the experience of any seasoned D3 coder. You can see it in any tutorial, book or documentation about D3. This page written by Mike Bostock, D3 creator, is a good starting point: https://bost.ocks.org/mike/join/ – Gerardo Furtado Apr 26 '17 at 09:23
  • As he says, *"...Before you bust out a **for loop** and brute-force it, consider this mystifying sequence from one of D3’s examples..."* – Gerardo Furtado Apr 26 '17 at 09:25