First data is ignored without using selectAll in d3.js

Snippet 1

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>D3 Test</title>
  <script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>

<body>
  <script type="text/javascript">
    var data = [];
    for (i = 0; i < 3; i += 1) {
      data.push(i);
    }
    d3.select('body')
      // .selectAll('p')
      .data(data)
      .enter()
      .append('p')
      .text(function(d) {
        return d;
      });
  </script>
</body>
</html>

Snippet 2

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>D3 Test</title>
  <script type="text/javascript" src="http://d3js.org/d3.v3.js"></script>
</head>

<body>
  <script type="text/javascript">
    var data = [];
    for (i = 0; i < 3; i += 1) {
      data.push(i);
    }
    d3.select('body')
      .selectAll('p')
      .data(data)
      .enter()
      .append('p')
      .text(function(d) {
        return d;
      });
  </script>
</body>
</html>

The code .selectAll('p') is commented in the first snippet. I could not figure why the first data is ignored due to that reason.

I am a newbie to d3.js and what I understood ,as I don’t have a p tag in my HTML the .selectAll('p') returns an empty selection and p are appended as per my data count.

Thanks in advance.

Answer

To understand what happened, you have to understand what is an “enter” selection. The example at the bottom of this post briefly explains both the “enter” selection and why you have to selectAll("p") in your second snippet, even if you don’t have any <p> in the document.

Now, let’s see your snippets:

Your first snippet:

In your first snippet, data is [0, 1, 2]. So, you have 3 elements. When you select the body, there is clearly a body in the DOM. So, you associate the first datum (0) to this element (body). Now, you have 2 data not associated to any DOM element: 1 and 2. These two data are your “enter” selection. When you append the <p>, your enter selection has only those two numbers.

Your second snippet

In your second snippet, data is again [0, 1, 2]. The difference is that now you select all <p>… but there is none. This is the “placeholder” in the example I linked. As there is no <p> in the DOM to associate with the data, your enter selection has all 3 data: 0, 1 and 2.

As I wrote in the example:

If in your “enter” selection you select something that doesn’t exist, your “enter” selection will always contain all your data.


The role of placeholders in “enter” selections

What is an enter selection?

In D3.js, when one binds data to DOM elements, three situations are possible:

  1. The number of elements and the number of data points are the same;
  2. There are more elements than data points;
  3. There are more data points than elements;

In the situation #3, all the data points without a corresponding DOM element belong to the enter selection. Thus, In D3.js, enter selections are selections that, after joining elements to the data, contains all the data that don’t match any DOM element. If we use an append function in an enter selection, D3 will create new elements binding that data for us.

This is a Venn diagram explaining the possible situations regarding number of data points/number of DOM elements:

enter image description here

As we can see, the enter selection is the blue area at the left: data points without corresponding DOM elements.

The structure of the enter selection

Typically, an enter selection has these 4 steps:

  1. selectAll: Select elements in the DOM;
  2. data: Counts and parses the data;
  3. enter: Comparing the selection with the data, creates new elements;
  4. append: Append the actual elements in the DOM;

This is a very basic example (look at the 4 steps in the var divs):

var data = [40, 80, 150, 160, 230, 260];

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

var divs = body.selectAll("div")
    .data(data)
    .enter()
    .append("div");

divs.style("width", function(d) { return d + "px"; })
    .attr("class", "divchart")
    .text(function(d) { return d; });

And this is the result (jsfiddle here):

enter image description here

Notice that, in this case, we used selectAll("div") as the first line in our “enter” selection variable. We have a dataset with 6 values, and D3 created 6 divs for us.

The role of placeholders

But suppose that we already have a div in our document, something like <div>This is my chart</div> at the top. In that case, when we write:

body.selectAll("div")

we are selecting that existent div. So, our enter selection will have only 5 datum without matching elements. For instance, in this jsfiddle, where there is already a div in the HTML (“This is my chart”), this will be the outcome:

enter image description here

We don’t see the value “40” anymore: our first “bar” disappeared, and the reason for that is that our “enter” selection now has only 5 elements.

What we have to understand here is that in the first line of our enter selection variable, selectAll("div"), those divs are just placeholders. We don’t have to select all the divs if we are appending divs, or all the circle if we are appending circle. We can select different things. And, if we don’t plan to have an “update” or an “exit” selection, we can select anything:

var divs = body.selectAll(".foo")//this class doesn't exist, and never will!
    .data(data)
    .enter()
    .append("div");

Doing this way, we are selecting all the “.foo”. Here, “foo” is a class that not only doesn’t exist, but also it’s never created anywhere else in the code! But it doesn’t matter, this is only a placeholder. The logic is this:

If in your “enter” selection you select something that doesn’t exist, your “enter” selection will always contain all your data.

Now, selecting .foo, our “enter” selection have 6 elements, even if we already have a div in the document:

enter image description here

And here is the corresponding jsfiddle.

Selecting null

By far, the best way to guarantee that you are selecting nothing is selecting null. Not only that, but this alternative is way faster than any other.

Thus, for an enter selection, just do:

selection.selectAll(null)
    .data(data)
    .enter()
    .append(element);

Here is a demo fiddle: https://jsfiddle.net/gerardofurtado/th6s160p/

Conclusion

When dealing with “enter” selections, take extra care to do not select something that already exists. You can use anything in your selectAll, even things that don’t exist and will never exist (if you don’t plan to have an “update” or an “exit” selection).

The code in the examples is based on this code by Mike Bostock: https://bl.ocks.org/mbostock/7322386

Leave a Reply

Your email address will not be published. Required fields are marked *