I’ve been looking for ways to let users play with data on a web page. It was time to explore the D3 library for JavaScript. I’d previously used D3 to create a Voronoi Diagram of CaBi Stations, but that was written by modifying an existing piece of code. How hard would it be to create my own D3 program?

I had seen beautiful, interactive bar charts written in D3, and wanted to create my own. At d3js.org you’ll find many great examples.

Every D3 function is available as a method to the global “d3” object. JavaScript doesn’t actually have classes, so when I call something a method, it’s really an object property that happens to be a function. Most D3 methods return objects that themselves have methods available, and it’s typical to see these methods chained together. My beef with the D3 API Reference is that it doesn’t tell you what pseudo class a method returns, so it’s hard to learn without studying copious examples.

The general format of a D3 statement is to select a part of your document and match it against a set of data. The “document” is an array of elements from the DOM (document object model), and the “data” is an array of your choice. The two arrays may or may not have the same length. The arrays are paired up, creating three selections.

The data method accepts your data as a parameter and returns the “update” selection, containing all the matched pairs. If both arrays are the same length, this will be everything. If there are more data objects than document elements, the “enter” method will return those unmatched data objects. And if there are more document elements than data objects, the “exit” method will return those unmatched document elements.

D3 can manipulate any HTML element, but most of the cool examples you’ll see will be playing with the SVG element (scalable vector graphics), which was introduced in HTML5. D3 programs typically build SVG elements dynamically, so instead of seeing SVG elements in tags, you’ll build them and manipulate their attributes in methods, like the attr method.

redrectanglesTo wrap my head around the weird chaining syntax, I decided to write the smallest D3 program I could. You can see the effect at randomrectangles.html. It’s a simple bar chart where the three bars switch to random values every two seconds. Here’s the general format for the program:

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://d3js.org/d3.v2.min.js"></script>
</head>
<body> 
</body>
<script type="text/javascript"> ... </script>
</html> 

The first line (<!DOCTYPE html>) is the standard way to declare the file as HTML5. Then there’s a link to the external JavaScript file for D3, which is 119 KB. The document’s body is empty because we will be dynamically adding the SVG element, where the bars will be drawn.

Here is the code that goes in the lower SCRIPT tag:

function randomArray() {   
  return [Math.ceil(70*Math.random()),  
          Math.ceil(70*Math.random()), 
          Math.ceil(70*Math.random())];
  }
  
function changeData() {    
  d3.select("svg").selectAll("rect")
    .data(randomArray())
    .transition()
    .duration(800) 
    .attr("width", function(d) {return d;}); 
  }   
  
d3.select("body")
  .append("svg")
  .attr("width", 70)
  .attr("height", 70); 
d3.select("svg").selectAll("rect")
  .data(randomArray())
  .enter()
  .append("rect")
  .attr("width", function(d) {return d;})
  .attr("height", 20) 
  .attr("x", 0)
  .attr("y", function(d, i) {return i*25;})
  .attr("fill", "red"); 
setInterval(changeData, 2000);

The main section has only three JavaScript statements. The method chaining is typically written with each method on a new line, but you could just as easily write it in one line:

d3.select("body").append("svg").attr("width", 70).attr("height", 70); 

This statement creates a 70×70 SVG element, and stuffs it inside the BODY element. You could remove this line of code and instead just put this tag in the BODY section:

<svg width=70 height=70>

Exact same outcome.

To understand the method chaining in the D3 statement, you have to know what each function returns. d3 is the global variable that is the entry point for everything in the library. To enforce a namespace that does not conflict with any other variables you might be using, everything is contained within the d3 object. The select method returns an array of DOM elements, in this case just the BODY element. Append also returns an array of DOM elements, but this time the array holds the new SVG element that we added. The attr method does its business and then returns the exact same array, so you can keep changing attributes one after another.

The next statement has a similar structure but with new complexities. The task of this statement is to create three red rectangles on the screen. It starts with this chain:

d3.select("svg").selectAll("rect").data(randomArray()).enter().append("rect")

This time we skip selecting the body and go straight to the SVG element we created. The select method returns an array with a single DOM object. It’s followed by a selectAll method, which returns an array filled with as many matching elements as it finds. In this case we’re looking for any RECT elements that are children of the SVG element. But obviously, since we just created the SVG element, we’re not going to find any child nodes of any type, so this array will be empty. So why call a function when we know it’ll just return an empty array? Accept that as a peculiarity of D3: when you add data from an array, you must match it against an array of DOM elements. By using an array we know to be empty, we can use the “enter” method knowing that it’ll be called once for each data object we have. So the goal of selectAll in this case is just to return an empty array. We could just as easily call selectAll(“blorf”) and have it look for the nonexistent blorf element. But you need select(“svg”) because that affects where the new RECT elements will be appended.

The data method joins the empty array to the array that we send as a parameter. The randomArray() function will return an array of three integers. Remember that joining data gives us three selections to play with: enter, exit, and update. Because we selected zero DOM elements, the update and exit selections are empty. So we use the enter method to access the enter selection: all the data objects that didn’t get paired with an existing DOM element. The append method then adds a “rect” object for each item in the data array.

All those attr methods that follow will get applied to each new RECT element. But not all rectangles should be drawn the same. I want the width to equal the random value in the data array, and the bars should be stacked vertically. So for the width and y attributes, I need to access both the value of the array’s elements, and the index of the array. Here are those two lines:

  .attr("width", function(d) {return d;})
  .attr("y", function(d, i) {return i*25;}) 

Instead of assigning literal values, I use a function, where the first parameter will equal the data value, and the second parameter is the index. The convention is to call them d and i, but that isn’t required.

So now we’ve dynamically created the SVG element and added three rectangles. We could replace these two D3 statements with regular tags in the BODY, like this:

<svg width=70 height=70>
  <rect x=0 y=0  width=35 height=20 style="fill:red"/>
  <rect x=0 y=25 width=15 height=20 style="fill:red"/>
  <rect x=0 y=50 width=55 height=20 style="fill:red"/>
</svg> 

The only difference is the initial widths aren’t random, which would require JavaScript.

The fun part is using setInterval() to call my changeData() function every two seconds.

function changeData() {    
  d3.select("svg").selectAll("rect")
    .data(randomArray())
    .transition()
    .duration(800) 
    .attr("width", function(d) {return d;}); 
  }  

This time when we call selectAll(“rect”) we do in fact get something back: an array with the 3 RECT elements we created earlier. My randomArray() also returns an array with 3 items (the random integers), so things pair up in a perfect join. The transition method will animate any changes we make. If you got rid of the transition and the optional duration methods it would still work, but the changes would be instantaneous:

 
d3.select("svg").selectAll("rect").data(randomArray())
  .attr("width", function(d) {return d;});  

The best way to learn D3 is to study examples, so I hope this extremely simple randomrectangles.html example will help you learn how D3 works.

barchartMy first D3 interactive bar chart web page illustrates Bicycle & Pedestrian Counts per Hour, using some more advanced D3 and SVG features. I still have a ways to go before catching up with D3 inventor Mike Bostock‘s collection of samples. But I like D3 enough to continue working with it and hopefully improving.

Getting Started with D3 in JavaScript

Leave a Reply