D3, Conceptually

Lesson 1: Introductory Elements

originally published 26 november, 2012


1.1 Beginnings
A simple example of basic d3 syntax is as good a starting point as any:
<div class='section'> First </div> <div class='section'> Second </div> d3.select('.section') .text('D3 was here');
This code can be broken down into two parts:
  1. a selection, d3.select('.section'). This takes a selector and returns a d3.selection. This can also take an element if you have one lying around.
  2. an operation, text('D3 was here'). The d3.selection returned by the previous call supports a number of these. The one we called sets the textContent of the selection to the argument.
For convenience, d3 is nice and returns the selection itself from the operation. So, instead of writing this code: var section = d3.select('.section'); section.text('D3 was here'); section.style('color', '#f0f'); // Magenta to make sure pedestrians see my graffiti! d3 lets you write it all in one statement: d3.select('.section') .text('D3 was here'); .style('color', '#f0f'); // Magenta to make sure pedestrians see my graffiti!
1.2 One node? I've got an army!
So, you now have the tools to vandalize websites everywhere with magenta text letting people know how cool you are. But it would be pretty tedious to do so, one node at a time. Plus, as you saw from the first demo, the selection from d3.select only got us the first matching node. Luckily, we have just the tool:
<div class='section'> First </div> <div class='section'> Second </div> d3.selectAll('.section') .text('D3 was here');
Now, no div is safe from your marker.
1.3 Happy little snowflakes
You now have the tools to modify either the first node matching a selection, or all the nodes matching a selection. Sometimes, though, it would be nice to modify each node based on some property. Maybe you want the even ones to say 'Even' and the odd ones to say 'Odd'. D3, in its infinite wisdom, makes that easy:
<div></div> <div></div> <div></div> <div></div> <div></div> d3.selectAll('div') .text(function() { var index = Array.prototype.indexOf.call(this.parentElement.children, this); return (index % 2) == 0 ? 'Even' : 'Odd'; });
Something of note - D3 defines this inside the function as "the node the attribute will be set on".
1.4 Playing God
All of this modification would be useless if not for the other side of the coin - creation! D3 provides an append method on selections for this purpose. It takes a single argument - the tag name of the element to create. This can be any valid tag, like div or table. Like most d3.selection methods, the return value is also a d3.selection. However, it contains the newly-created nodes, and not the original selection:
<div class='things'> <div>Sugar</div> </div> d3.select('.things') .append('div') .style('color', 'red') .text('Spice');
Like all selection methods, append operates on all selected nodes. If multiple nodes are selected, multiple nodes will be created (and returned):
<div class='things'> <div>Sugar</div> <div>Spice</div> </div> d3.selectAll('.things div') .append('div') .style('display', 'inline-block') .style('color', 'green') .text('\u2713');
If you've played around with jQuery, or other JavaScript libraries, you may notice similarities. The select/modify idiom in D3 is a very simple abstraction that proves shockingly effective at getting things done.
The adventurous or deranged reader might see D3 as a lightweight alternative to jQuery or even a JavaScript templating engine like mustache.js or Google Closure Templates. Yes, you could do it. If you examine the source of this page, you might even notice it being done. But don't say I told you to.
1.5 But where's the data?
You, the reader, might be confused at this point. You might have read, on the D3 website, "D3.js is a JavaScript library for manipulating documents based on data." But we haven't seen a single mention of data yet. Meet our new friend, the data operator (a card-carrying member of the d3.selection club). It binds some data to a selection.
<div class='slayer'></div> <div class='slayer'></div> <div class='slayer'></div> var slayerNames = [ 'Buffy', 'Kendra', 'Faith' ]; d3.selectAll('.slayer').data(slayerNames);
The argument to data is an Array, the elements of which can have any type. By itself, binding data to elements doesn't accomplish anything without the ability to access it. You can get this Array back out by calling the method with no arguments: var slayerNames = d3.selectAll('.slayers').data();
Most 'setter' operations in D3 support this idiom - instead of providing a value, you can simply leave it blank. D3 will return the first value of the first node in the selection. For instance, getting the text content of a div is as easy as var textContent = d3.select(theDiv).text()
Still, though, that isn't much help. Without an easy way of getting data associated with a node and modifying the node in some way, one might as well not use D3. Going back to the snowflakes example, we saw a function passed as an argument to a d3.selection method. What I didn't mention is that function is called with several arguments - the bound datum of the current element, and its index within the selection. By convention, we call these d and i. For example:
<div class='slayer'></div> <div class='slayer'></div> <div class='slayer'></div> var slayerNames = [ 'Buffy', 'Kendra', 'Faith' ]; d3.selectAll('.slayer').data(slayerNames) .text(function(d, i) { return 'Slayer #' + (i + 1) + ': ' + d; // i is 0-based. });
It turns out most methods of selection that set some property of each node in the selection support this idiom: arguments can either be functions (called with datum and index, and a this of the node), or a literal value. They both come in handy - sometimes, you want every node to have a font-style of bold, and sometimes you want each node to get special treatment.
And what if the length of data doesn't match the length of the selection? To our neophyte eyes, not much. The selection is only as many nodes as existent, and the remainder of the data (if any) is simply ignored.
<div class='captain'></div> <div class='captain'></div> var captainNames = [ 'Kirk', 'Picard', 'Sisko', 'Janeway', 'Archer' ]; d3.selectAll('.captain').data(captainNames) .text(function(d, i) { return 'Captain #' + (i + 1) + ': ' + d; // i is 0-based. });
Everything is more complex than this, but we'll get to that when the time comes. Starting right now, actually.
1.6 Two nodes enter, no nodes leave
The data method, mentioned previously, doesn't just bind data to elements. It computes a data join between the nodes in the document and the data provided. If two elements are selected and five datum provided, D3 notes three more datum were provided than elements and stores this in the enter selection, accessible via the enter method on the return value of a call to data. As there are no actual nodes to select, enter doesn't support most d3.selection methods. It does, however, allow append:
<div class='captains'> <div class='captain'></div> <div class='captain'></div> </div> var captainNames = [ 'Kirk', 'Picard', 'Sisko', 'Janeway', 'Archer' ]; var parent = d3.select('.captains'); var captains = parent.selectAll('.captain').data(captainNames); captains.enter().append('div') .style('color', 'green') .attr('class', 'captain') // attr is like style or text, but sets node-level attributes. .text(function(d, i) { return 'Captain #' + (i + 1) + ': ' + d; }); captains .text(function(d, i) { return 'Captain #' + (i + 1) + ': ' + d; });
All captains in a line, now. Calling append operated on the parent of our selection, appending one node per additional data element - in this case creating three new divs as the last children of div class='captains'.
The enter selection also has another trick up its sleeve - nodes appended with it are automatically inserted in the proper place in the original selection. So, the following code still is equivalent to, and less error-prone than, the example: ... captains.enter().append('div') .style('color', 'green') .attr('class', 'captain'); // captains now has the newly-appended nodes in its selection, // so those nodes will be included in all method calls. captains .text(function(d, i) { return 'Captain #' + (i + 1) + ': ' + d; }); If the text call was before the append, only the existing nodes would get text. As such, it is usually best to deal with the enter selection first. You don't even need any nodes to begin with; you can have an empty selection and append to it, as long as it has a valid parent:
<div class='lucky-numbers'></div> var parent = d3.select('.lucky-numbers'); // We are selecting all of the .number nodes - which don't exist! var numbers = parent.selectAll('.number').data([7, 8, 21, 888]); numbers.enter().append('div') .text(function(d, i) { return d; });
While the above code works, it is actually missing one crucial detail. When appending elements to a selection, it is good practice to ensure the new elements would match the selection. This isn't enforced, but not doing so leads to an unhappy coder. So the correct, ever-shorter, version of the code would be: d3.select('.lucky-numbers').selectAll('.number') .data([7, 8, 21, 888]) .enter().append('div') .attr('class', 'number') .text(function(d, i) { return d; }); The reason for this is not readily apparent. But all those cool things you've seen in D3? The swooshes and floops and schwarps? Well, the idiom is for them.
There are many different coding styles to use when writing D3. This comes down to personal taste, but the example set by the D3 documentation is not a bad one:
  • Multiple select operations can be on one line
  • Modifying operations on a selection are indented four spaces and get their own line
  • Pair enter and append together on one line with a two-space indent
    This makes it obvious operations are on a new set of nodes, not the original selection
  • Only have one selectAll per statement
1.7 SVG? For me?
You've made it this far and haven't seen any colorful graphics. Which I'm sure is the only reason you want to use D3 - to impress friends, family, and crushes with your data-visualizing prowess. Well, the time is now. Kind of. Future lessons will expand on not only the capabilities of SVG, but also additional functionality of D3. Let's make some squares!
<div id='chart'></div> var root = d3.select('#chart').append('svg') .attr('width', 200) .attr('height', 200) .style('border', '1px solid black'); root.selectAll('rect') .data([5, 25, 80]).enter() .append('rect') .attr('x', Object) .attr('y', Object) .attr('width', 15) .attr('height', 10) .attr('fill', '#c63') .attr('stroke', 'black');
There is a lot going on in that sample, much of it new. First off is SVG - which has a W3C Recommendation. Or, in other words, a descriptive manual. It works; though for now we will mainly concern ourselves with the basic shapes. SVG can be embedded within HTML. D3 makes this simple; you need only append an svg element to part of the DOM. This element functions much like the body element in a webpage - it is the root onto which all other elements must be added. Relevant code: var root = d3.select('#chart').append('svg') .attr('width', 200) .attr('height', 200) .style('border', '1px solid black'); It is a very good idea to explicitly set the width and height of the root svg element as they default to 100%.
You'll note the dimensions here, and elsewhere for SVG elements, can be raw numbers with no attached unit. When specifying values for attributes on SVG nodes (that is, using .attr(...);), the units are assumed to be in the "current user coordinate system". For now, pretend that means 'pixels'. You can always add a CSS2 unit as well, such as .attr('x', '53px') or .attr('y', '2em') but it tends to be easier to simply ignore the unit unless necessary. The whole spec is available here for the curious or the curiously demented that enjoy reading standards documents.
The next new bit is our select - we used element type selection instead of classes. That is, we wrote .selectAll('rect') instead of .selectAll('.some-class'). Why? Well, for one thing, it saves a line of code in the example (we no longer need to add a class attribute that matches the selection). It also seemed like a good idea to mix it up. Keep you on your toes.
Moving on, we have these curious lines: .append('rect') .attr('x', Object) .attr('y', Object) The first one should be straightforward - we are no longer creating HTML elements. Now, we are in the brave new world of SVG elements. rect is a simple one to start with. The other two lines are quite mysterious. Somewhat magic, one might say. I'd agree - you will see, on occaison, String or Object used as a value. Remember that values can either be literals (like a string or number), or a function that is evaluated once per node/datum? Well, String and Object are just functions. So the line .attr('x', Object) is equivalent to .attr('x', function(d, i) { return Object(d, i); }) and also to .attr('x', function(d, i) { return d; }) Object, then, is essentially the identity function (and, luckily, it ignores every argument other than the first). Remember, you are free to use any function as a value argument - it need not be defined inline, or even defined by you.
The coordinate system in SVG uses the top-left corner as the origin; x increases right-wards and y increases down-wards.

The final two lines are just setting up our visuals: .attr('fill', '#c63') .attr('stroke', 'black'); Those should be self-explanatory, but they aren't the only way of styling SVG elements. You can also put it in your CSS (or embed a style element in the SVG element, an advanced topic I'm going to pretend I didn't mention because I don't want to go into it). There are many, many style attributes and a legion of values to set. You can read all about them from the horse's mouth, or you can just pick up bits and pieces from the examples here. Your call.
There is one final example to offer, provided without commentary. You should be able to figure it out. Just remember - datum can be anything.
<div id='chart'></div> var root = d3.select('#chart').append('svg') .attr('width', 600) .attr('height', 600) .style('border', '1px solid black'); var rects = [ {x: -5, y: -5, w: 150, h: 195}, {x: -5, y: 200, w: 150, h: 230}, {x: 155, y: 435, w: 395, h: 175}, {x: 555, y: 435, w: 55, h: 80}, {x: 155, y: -5, w: 450, h: 435, fill: '#731000'}, {x: -5, y: 435, w: 150, h: 175, fill: '#0b183b'}, {x: 555, y: 520, w: 55, h: 90, fill: '#d49800'} ]; root.selectAll('rect') .data(rects).enter() .append('rect') .attr('x', function(d) { return d.x; }) .attr('y', function(d) { return d.y; }) .attr('width', function(d) { return d.w; }) .attr('height', function(d) { return d.h; }) .attr('fill', function(d) { return d.fill || '#eef2d1'; }) .attr('stroke-width', 10) .attr('stroke', 'black');
The next lesson will cover more of the D3 library, specifically those parts focused on making drawing easier, as well as a whole spread of SVG elements yet to be seen. And, don't worry, animations will come soon after (so you can get your zwoops and schwedaddles and really impress your friends). There will be a detour through nested selections on the way, so don't get too excited.