D3: Responsive and Dynamic Visualizations for Data and Other Easy Recipes
By: Griffin Poole
Tags: Coding, Programming, JavaScript, D3js, Software Development

Binding Data

First and foremost D3 allows you to bind data (arrays) to DOM element selections for later dynamic rendering. What does that mean exactly?

To start, you would make a selection of elements from the DOM. This is where some of D3’s streamlined traversal comes in handy. D3 provides an example of a messy traversal on their homepage, with code to change all p tag colors to blue:

This is not fun to write, so D3 provides us with a way to streamline this selection alongside the attribute change. The above has the same functionality as the following:

d3.selectAll("p").style("color", "blue");

The selectAll method returns a selection of all relevant elements that are downstream of the provided node. If you provide d3.selectAll() , you get everything on the document. Running namedDivVariable.selectAll() selects everything downstream of the div in question. This makes DOM selection and attribute change much simpler — thanks D3! Now that we have ways to quickly retrieve “selections” of elements, we can bind our data to them. Let’s provide an example.

Say we have three arrays with different numbers of elements: three, two and one.

let threeElement = [true, false, true] let twoElements= [true, false] let oneElement= [true]

On the DOM we also have two circle SVGs. Don’t worry about the correct SVG syntax for now — just assume they’re proper elements. These circles also have a possible attribute: crossed; true — Ø, false — O. We can go ahead and select these and store them in a circles variable.

circle1 = O, circle2 = O let circles = d3.selectAll(“circle”)

Now we can bind this array data to our selection of elements. What this means is that each value in our data array gets connected to each element in our selection by D3 magic. These data values can later be accessed functionally to dynamically render our elements. But we’ll get back to that later. The way that we bind data is through the .data() method. So we can bind our array of two values to our two circles like so: circles.data(twoElements) . Now, the first circle is joined to our first data element ‘true’, and our second circle is bound to the second element ‘false’. This is called the update selection — when the number of data values matches the elements in the selection.

Update Selection: circles.data(twoElements) //Gives back all elements and matching data bound circles.data(twoElements).attr(‘crossed’, function(d){return d}) Would result in something like // circle1 = Ø, circle2 = O

In the code above, you’ll notice some mess in the .attr() method where I’m setting the circle to be crossed or not. Don’t worry about that for now. Just know that crossed is being set to the value of the data element that has been connected to each circle.

The update selection is all well and good when we have an element to match each data value, but what if the number of data values and elements in the selection don’t match? D3 also supports two other types of selections that incorporate overflow in either direction: .enter() and .exit() . Here’s how they work.

With the .enter() selection, you receive any DATA values that did not have a matching element. These unmatched values can be used to render new elements corresponding to them with .append() .

Enter Selection: circles.data(threeElements).enter() //Gives back all Data Values that don’t have a matching circle //e.g. [true] will be unmatched circles.data(threeElements).enter().append(‘circle’).attr(‘crossed’, function(d){return d}) Would result in something like // circle1 = O, circle2 = O, circle3 = Ø

Here, we see that a three-element array is being bound to our two circles. Circle1 gets true , circle2 gets false , and our last true is floating. When we call .enter() , that floating element is now what we have selected. For each of these floating data points, we can .append() a new circle and set its crossed attribute to the now assigned data point true . By the end, we haven’t changed any of our original elements, we’ve only entered the floating data points in as new circles.

This concept was tricky for me to grasp at first, but once you understand the enter selection, you gain access to one of the most powerful tools of D3. Say, for example, I selected something I know to not exist. I then have an empty selection. I can bind any data array to it, then use .enter() to generate as many elements as I have data, all corresponding to a single data point. This is one of the most useful tools for D3 chart generation and a powerful one to master.

Finally, we have the .exit() selection. It is the opposite of the enter selection. The .exit() method will return all the elements that do not have matching data values.

Exit Selection: circles.data(oneElement).exit() //Gives back all elements without Data to bind to //e.g. [circle2] will be unmatched circles.data(oneElement).exit().remove() Would result in something like // circle1= O

What happens here is that the one element array of data is bound to our two element selection of circles. The first element gets bound to true , the second one does not get bound. Then, with .exit() we can access any elements that don’t have a data element bound to it (e.g. circle2 ). Calling .remove() on it will delete that circle from our DOM, leaving us with only an unchanged circle1 .

This is a lot to consume at once, but these three functionalities are the meat of D3’s DOM traversal and data binding functionalities. They take some practice and some debugging, but once you’re used to them they’re remarkably flexible. Now that we are able to traverse the DOM and bind data to our selected elements, we reach the second critical component of D3: dynamic rendering.