Introduction

Binding data to DOM elements is one of the most important features in d3.js. It makes data visualization possible and easy. But what happens when the amount of data items does not match the amount of DOM elements? In this article we explore how enter() and exit() solve those situations in d3.js.

Source code and example

This article will go through some examples, where the source code is available code here and the working example is available here.

Regular Example

The first situation we'll talk about is a simple case where enter() and exit() are not required. We will be working with 3 data items:

var data = [120, 150, 95];

And 3 DOM elements to match that data:

<div id="regular-chart">
    <div class="bar"></div>
    <div class="bar"></div>
    <div class="bar"></div>
</div>
 

Our goal here is to bind each of the data items to each DOM element, we can do this with .data(). After doing that we want to change the width of each .bar based on data item. So the first .bar should be 120px long, and so on.

In this case what we need to do is use basic d3 functions to select all of the DOM elements, bind the data to each element, and apply a style() function to change the width (to match the data). The following javascript will be able to do this:

 d3.select("#regular-chart").selectAll("div")
    .data(data)
    .style("width", function(d) {
        return d + "px";
    })
    .text(function(d) {
        return d;
    });

As expected, the width of each bar will match the corresponding data item (I should also mention I have styles for each .bar in styles.css, the only thing not set yet is the width):

regular example with no enter() or exit()

More data items then elements

Now, we'll explore a situation where we need to use enter() because there are more data items than DOM elements. In this imaginary example the data is the same as above, but we only have 2 DOM elements, like so:

<div id="regular-chart">
    <div class="bar"></div>
    <div class="bar"></div>
</div>

The problem is that with the example above only two bars would be shown, but we want three, because we have 3 data items. We can solve this by first calling data() like we did above, to bind our data to the current selection. At this point it's easy to see that there are data items which cannot be bound to an element (in this case the last data item). So the next thing we can do is call enter(), and this will select all of the data items which are not bound to an element. This is called a "subselection" and we can perform actions to append or insert the missing data, and then carry on with that selection as normal. This is how that code would look:

 d3.select("#undersized-chart").selectAll("div")
    .data(data)
    .enter().append("div")
    .attr("class", "bar")
    .style("width", function(d) {
        return d + "px";
    })
    .text(function(d) {
        return d;
    });

The only thing different about this code from the first example is the addition of .enter().append("div"). As mentioned above, this will select the data items not bound to DOM elements. So in the output, only the third bar will have it's width changed accordingly. When we call .style() this is only performing that function on our current selection, which is the newly appended element. This is how that would look.

example with not enough DOM elements

If we wanted to change the width of all of the bars, we would have to write .style() for each selection. If that seems inefficient, you're right! This is how it's usually done:

Starting with no DOM elements

Typically, most data visualizations in d3.js will start with no DOM elements to match the data. Most data visualizations use .enter().append() to manually create each element. This way, we only have one selection to work with: This is how the html looks just for clarity:

<div id="empty-chart">
</div>

Now the code we use will be exactly the same as above, except for the id of the chart:

d3.select("#empty-chart").selectAll("div")
    .data(data)
    .enter().append("div")
    .attr("class", "bar")
    .style("width", function(d) {
        return d + "px";
    })
    .text(function(d) {
        return d;
    });

And our result will look as expected, with 3 bars of the correct width:

example with not no DOM elements

Sidenote: you may have noticed that in the code above, we say .select("#empty-chart").selectAll("div"), or select all div elements, but how can it do that when the chart starts off as empty? This is a tricky concept in d3 enter() and exit() functions that can be described as "time-travel" to know that you will be creating div elements. If we didn't have that line the statement wouldn't work because data() would try to bind data to the chart instead of the div elements that don't exist (yet!).

More elements than data items. Using exit()

Lastly, I haven't described what .exit() does yet. As you can imagine it is the exact opposite of .enter(). As we've seen .enter() will select all data items with no DOM element bound to them, but .exit() on the other hand will select all DOM elements not bound to any data.

In order to have a situation where DOM elements are not bound to data, we will have more DOM elements than data items. The data will be the same as in the last examples, but now we will have 5 DOM elements (it can be any number above 3). Our goal will be to remove any elements not bound to data. This is how the html will look:

<div id="regular-chart">
    <div class="bar"></div>
    <div class="bar"></div>
    <div class="bar"></div>
    <div class="bar"></div>
    <div class="bar"></div>
</div>

And of course, the javascript:

d3.select("#oversized-chart").selectAll("div")
    .data(data)
    .style("width", function(d) {
        return d + "px";
    })
    .text(function(d) {
        return d;
    })
    .exit().remove();

In the code above, we selected all the elements with .bar as a class, then changed the width and text accordingly. After doing so .exit() was called to select only the elements with no data tied to them. Calling .remove() removed every item from that subselection, and our data looks as expected:

example with more DOM elements that data items

That covers everything that I meant to explain about .enter() and .exit() in d3.js. I hope it was helpful!