3

My question is very similar to this. The solution there was that there was a <div> messing things up. There are no <divs> in mine.

I have this CoffeeScript code:

data = [0, 1, 2, 3, 4]
d3.select("body")
  .data(data)
  .enter()
  .each((d, i) =>
    console.log(i, d)
  )

With the desired console output being:

0 0
1 1
2 2
3 3
4 4

The actual console output is:

1 1
2 2
3 3
4 4

I can get the desired output with this code:

data = [0, 1, 2, 3, 4]
d3.select("body")
  .data(data)
  .each((d, i) =>
    console.log(i, d)
  ).enter()
   .each((d, i) =>
    console.log(i, d)
  )

But it just feels wrong to have two .each() calls.

Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
Luke Stuemke
  • 545
  • 1
  • 5
  • 12

2 Answers2

6

d3.each() does begin at index 0. What you're seeing in your code is the expected behaviour, given what you have in your code.

The issue here is simple: there is a <body> element in the page, of course. Your data array has 5 elements, and one of them is being bound to the <body>.

Let's show this. Have a look at the size of the "enter" selection:

data = [0, 1, 2, 3, 4]
var foo = d3.select("body")
  .data(data)
  .enter();

console.log("Size of enter selection: " + foo.size())
<script src="https://d3js.org/d3.v4.js"></script>

We can also show that the first element in the array was bound to the <body>:

data = [0, 1, 2, 3, 4]
var foo = d3.select("body")
  .data(data)
  .enter();

console.log("Data of body: " + d3.select("body").data())
<script src="https://d3js.org/d3.v4.js"></script>

Yet another way for showing this is using the third argument (technically speaking, parameter), which is the current group:

data = [0, 1, 2, 3, 4]
d3.select("body")
  .data(data)
  .enter()
  .each((d, i, p) =>
  //           ^---- this is the third argument
    console.log(p)
  )

Here I cannot provide a working Stack snippet, because it crashes if we try to log a D3 selection. But the result will be this:

[undefined × 1, EnterNode, EnterNode, EnterNode, EnterNode]

That undefined is the "update" selection (the body), and the 4 EnterNodes are the "enter" selection. And that brings us to the explanation of why each() behaves that way in your code.

If you have a look at the source code...

function(callback) {

    for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
        for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
            if (node = group[i]) callback.call(node, node.__data__, i, group);
        }
    }

    return this;
}

You're gonna see that it works comparing the nodes to the group, and your group contains both the "update" selection and the "enter" selection. The update selection corresponds to index 0 and the enter selection corresponds to indices 1, 2, 3, and 4.


Solution:

This is what you want, pay attention to selectAll and null:

data = [0, 1, 2, 3, 4]
d3.select("body")
  .selectAll(null)
  .data(data)
  .enter()
  .each((d, i) =>
    console.log(i, d)
  )
<script src="https://d3js.org/d3.v4.js"></script>

Thus, as you can see, selecting null assure us that our "enter" selection always have all the elements in the data array.


Bonus: select and selectAll behave differently. Most people think that the only difference is that the former selects only 1 element and the latter selects all the elements. But there are more subtle differences. Have a look at this table:

Method select() selectAll()
Selection selects the first element that matches the selector string selects all elements that match the selector string
Grouping Does not affect grouping Affects grouping
Data propagation Propagates data Doesn't propagate data
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • +1 nice and complete! I didn't know about `select(null)`. Does that mean that the `body` won't have the data attached to it? Are there any caveats that you are aware of when using `select(null)`? – mkaran Jul 06 '17 at 14:06
  • 1
    No. `select(null)` and `selectAll(null)` are the safer and, the most important, the faster way to make sure that the data method compares the data with an empty selection. – Gerardo Furtado Jul 06 '17 at 14:07
  • Very informative. I appreciate your time, and that the solution worked for me :) – Luke Stuemke Jul 06 '17 at 14:25
0

Take a look at this and basically this: https://stackoverflow.com/a/19726610/3433323

Quoting: You need to select the non-existent elements as well for the selections to work properly. ... At the moment, the selection you're matching data against contains only the one element Which means you are selecting against the body only.

In short:

data = [0, 1, 2, 3, 4]
d3.selectAll("body")
  .selectAll('div') // add this
  .data(data)
  .enter()
  .each((d, i) =>
    console.log(i, d)
  )

jsfiddle

About selectAll

mkaran
  • 2,528
  • 20
  • 23