Enter, Update, Exit

An Introduction to D3.js, The Web’s Most Popular Visualization Toolkit

Christian Behrens
10 min readApr 15, 2014

Update 8 Jul 2017: I’ve created a GitHub repository, where you can check out the code example below. The actual demo can be found here.

Over the past couple of years, D3, the groundbreaking JavaScript library for data-driven document manipulation developed by Mike Bostock, has become the Swiss Army knife of web-based data visualization. However, talking to other designers or developers who use D3 in their projects, I noticed that one of the core concepts of it remains somewhat obscure and is often referred to as »D3’s magic«: Data joins and selections.

Given a solid command of basic JavaScript, this article should help you to wrap your head around these two fundamental concepts and get you started using D3 for your dataviz projects.

From Data to Visualization

Data visualization translates quantitative data into graphic representations. In its most basic form, a data set is a series of numerical values. Each data item (or datum) corresponds to a visual element, for instance a bar in a bar chart, a point in a coordinate system or an area on a map. If we pick an appropriate graphic attribute for the display of the corresponding data set (the height of a bar, the vertical position of a point or the color of a map area), the datum’s value defines the appearance of this attributes for the visual element it is connected to.

Our data set: The numbers.

As an example, let’s take a look at a set of four integer values. We can translate this set into a bar chart of four bars, and choose the bars’ height as the graphic attribute that should represent the data:

Each value in the set maps to one element in the chart.

Depending on the characteristics of the data, we could display the data in a large variety of ways; if the dataset represented a continuous series of values over time, we could also draw a line chart. Now each datum refers to a point, and the point’s vertical position becomes the graphic attribute. If our data points had a geographic reference and each item represented the value of, say, a country, we could also show them on a map; in this case, the brightness or the color of a country shape would correspond to the value of the linked datum.
Although examples of very different types of visualizations, they all follow the same principle of mapping data: Data items are represented by visual companions and more specifically their graphic attributes. This is the core principle of D3’s magic called »data joins«. Before we get there, let’s grab some elements!

Selections

Similar to jQuery, D3 allows us to select elements from the DOM based on CSS selectors, for instance by ID, class attribute or tag name. The result of a select operation is an array of selected elements, the selection.

Suppose we have a simple bar chart component with the following markup and styling:

<div id="chart">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
.bar {
float: left;
width: 30px;
margin-right: 20px;
background-color: #F4F5F7;
border: 1px solid #C5C5C5;
}

Using select(), we select a single element from the DOM:

// Select the first child of #chart
var selection = d3.select("#chart").select(".bar");

If we want to create a selection of multiple elements that match a criterion, we use selectAll() instead:

// Select all .bar children of #chart
var selection = d3.select("#chart").selectAll(".bar");

Now that we have a selection of elements, we can apply operators to it in order to manipulate attributes, properties or styles of its members. In our example selection of bars, we can modify the height of each bar by adjusting the element’s CSS height property:

// Set the height of all bars to 40 pixels
selection.style("height", "40px");

This works well, and the great thing is that we don’t need to loop over our set of bars in order to adjust them. Instead, we apply the style operator to the selection, and D3 takes care of invoking it on every single element within. Still, this is kind of boring since we use a static value for the height of each bar in the chart.

What we need is some data.

Bring the Data!

The simplest form of a data set is a an array of values:

var numbers = [15, 8, 42, 4];

In this example we have a set of four data items that we’d like to display in our bar chart. In order to do that, we need to connect each datum to a bar that will represent it by its height. In D3, we can achieve this by applying the data() operator on our selection of bars:

// Select all bars within the bar chart
var bars = d3.select("#chart").selectAll(".bar");
// Join bars with data items
bars.data(numbers);

What happens here is the first part of D3’s magic: Using the data() operator with our dataset on a selection of DOM elements, we join both sets of items with each other item by item. In other words, the first bar DIV in the selection is now linked to the first element in the numbers array, the second DIV to the second array element and so on. In case you need proof this is really happening, just inspect the resulting selection in the console: You will find an array of div elements of class bar (our selection), and for each DIV element a property called __data__, which contains the value of the linked datum.

Similarly to jQuery, D3 makes heavy use of method chaining, which allows us to rewrite the two lines above in one line like this:

d3.selectAll(".bar").data(numbers);

Now, if we apply the CSS style operator from above to our data-enriched selection again, we can set the height of each bar depending on the value of the linked data value:

d3.select("#chart").selectAll(".bar").data(numbers)
.style("height", function(d){
return d + "px";
});

We’re now using the style() operator from above again in order to manipulate our elements’ height property, yet we replaced the static value by a so-called accessor function. This function is passed as an argument the value of the linked datum that we joined earlier with the corresponding visual element, and returns it to be used as the CSS height value in the style() operator.

Because we would like to align the bars to the bottom of the chart, we need to adjust their vertical position according to their height. This is easy since we can also chain several style() operators:

d3.select(“#chart”).selectAll(“.bar”).data(numbers)
.style(“height”, function(d){
return d + "px";
})
.style("margin-top", function(d){
return (100 - d) + "px";
})

Accessor functions in combination with operators for the manipulation of CSS styles and element properties offer us an almost unlimited range of possibilities to map a data set to a visual representation. What we will take a closer look at now is how to keep representations up to date when the underlying data change.

Enter, Update and Exit

In the above example, we created a simple visualization by joining a set of data items with an equally big set of DOM elements. So what happens, if we have more data items than elements? Or if we add or remove data items on the fly? This is where the second part of D3’s magic comes into play; the enter and exit selections.

The Update Selection

By applying the data() operator to our selection, we join data items and DOM elements with each other:

var selection = d3.select("#chart")
.selectAll(".bar").data(numbers);

The resulting selection is called the update selection:

var numbers = [15, 8, 42, 4];function update() {  // Update selection: Resize and position existing 
// DOM elements with data bound to them.
var selection = d3.select("#chart")
.selectAll(".bar").data(numbers)
.style("height", function(d){
return d + "px";
})
.style("margin-top", function(d){
return (100 - d) + "px";
});
};update();

Applying the data() operator to a selection returns this selection (now with data attached), so we can use style() operators for resizing and positioning of the bars as we’ve seen in the previous chapter.

Now it gets interesting: When we join a dataset with an element selection, the resulting selection also contains two references to special sub selections, called the enter and exit selections. These sub selections are probably the most important concept within D3: They allow us to deal with changes to our data set dynamically .

The Enter Selection

Consider the following modification to our data set: We add a fifth item to the array, yet our #chart div still contains only four bars to display the data:

var numbers = [15, 8, 42, 4, 32];

When our dataset contains more items than there are available DOM elements, the surplus data items are stored in a sub set of this selection called the enter selection.

Think of our visualization as a room with a certain number of chairs (our DOM elements) and a certain number of guests (our data items) sitting on these chairs (i.e., data joined with DOM elements). The enter selection can be described as a waiting area for data items that have just entered the room but cannot be seated yet, because there aren’t enough chairs. Bringing more chairs (or, in our case, creating new .bar DIVs and adding them to the DOM) is the job of operators provided by D3. Because we apply these directly to the enter selection, chairs are only brought for those guests within the waiting area, while the other guests won’t be bothered.

A fifth item has been added to the data set, although there are only four bars in the chart — yet.

We can access the enter selection using the operator enter() on our current selection:

var enterSelection = selection.enter();

With this sub selection at hand, we can apply DOM operators to create new div elements and attach them to the DOM, assign them the appropriate class name (“.bar”) and set their height and vertical positioning according to their linked datum’s value using the accessor function we saw above:

selection.enter().append("div").attr("class", "bar")
.style("height", function(d){
return d + "px";
})
.style(“margin-top”, function(d){
return (100 — d) + "px";
});

Now our update() function performs two different sets of actions on the elements of the selection, depending on the group the corresponding datum belongs to:

  1. For data items that are already represented by a bar in the bar chart (the update selection), this bar’s height is updated according to the linked datum’s value.
  2. For data items that don’t have a bar yet (the enter selection), the bar element is created and attached to the chart, and the height updated accordingly.
var numbers = [15, 8, 42, 4, 32];function update() {  var selection = d3.select("#chart")
.selectAll(".bar").data(numbers);
// Enter selection: Create new DOM elements for added
// data items, resize and position them.
selection.enter()
.append("div").attr("class", "bar")
.style("height", function(d){
return d + "px";
})
.style("margin-top", function(d){
return (100 - d) + "px";
});
};update();

As a result, our bar chart now has five bars:

The Exit Selection

We just saw how we could add new items to a data set dynamically and update the visualization on the fly. By the same token, we can remove items from the data set and let D3 deal with the corresponding DOM elements or, following our room/chair metaphor, take away those chairs that aren’t needed anymore because some guests decided to leave. This is taken care of by the exit selection.

Just as the enter selection is a waiting area for data items that have just arrived and need to be seated, the exit selection contains those data items that are about to leave the data set. For instance, we could attach a mouse event handler to our enter selection so that clicking on a bar removes the linked datum from the data set:

selection.enter()
.append(“div”).attr(“class”, “bar”)
.style(“height”, function(d){
return d + "px";
})
.style(“margin-top”, function(d){
return (100 — d) + "px";
})
.on(“click”, function(e, i){
numbers.splice(i, 1);
update();
});

The next time we call our update function, D3 will notice that the datum bound to the third bar chart element has been removed, and will add this DOM element to the exit selection:

Datum “42” is removed from the data set; it is now part of the exit selection.

Operators attached to the exit selection allow us to “clean up” the DOM elements they are leaving behind without affecting the rest of the chart:

selection.exit().remove();

In the above code snippet, we apply D3's remove() operator, a convenient method to remove elements from the DOM. Because it is applied on the previously defined exit selection, it affects only those DOM elements that don’t have a datum bound to them anymore.

The resulting bar chart is now up to date again — four bars representing four items in our data set.

All up to date again! Yay!

Wrapping it All Up

Our update function now performs three different types of actions depending on the type of selection the data items belong to: Enter, Update or Exit. Take at look at this live example to see it in action and check out the source code.

var numbers = [15, 8, 42, 4, 32];function update() {  var selection = d3.select("#chart")
.selectAll(".bar").data(numbers)
.style("height", function(d){
return d + "px";
})
.style("margin-top", function(d){
return (100 - d) + "px";
});
selection.enter()
.append("div").attr("class", "bar")
.style("height", function(d){
return d + "px";
})
.style("margin-top", function(d){
return (100 - d) + "px";
})
.on("click", function(e, i){
numbers.splice(i, 1);
update();
});
selection.exit().remove();};update();

What’s Next?

D3 is of course not limited to drawing bar charts. Quite the opposite, the great thing about this library is that it’s completely unbiased regarding the form of the resulting visualization or the environment in which it gets published. In our example, we drew a chart using CSS-styled HTML elements. Alternatively, you could also use SVG to create a visual output (and for more sophisticated visualizations you will definitely do that), or HTML5 Canvas.

In order to get an idea of D3’s flexibility, to look into way more sophisticated code examples, or just to get fresh inspiration from an extremely active and creative community, check out the example gallery on GitHub. Right next to it, you will also find a bunch of tutorials and a pretty extensive API documentation.

Happy visualizing!

--

--