D3 selections

The core datatype in the D3 Javascript library is the selection. But what exactly is a selection? What is its type and how do we work with it? It’s possible to use D3 without really understanding what selections are, but it makes a lot more sense if you understand the core type.

D3 selection type

A D3 selection is an array of arrays of DOM elements. That top level array object also has extra methods defined on it, the D3 selection methods like append(), attr(), etc.  Both select() and selectAll() return a 2d array; the difference is that select() only returns the first of the matching nodes. Most of the time your Javascript code won’t ever iterate inside these arrays; D3’s methods themselves implicitly loop over the elements.

Sample document

The rest of these notes will work on a simple sample HTML document that contains two tables each of size 2×2 for a total of 8 td nodes.

<table id="table1">
  <tr id="tr1-1">
    <td id="td1-1-1">1-1-1</td>
    <td id="td1-1-2">1-1-2</td>
  </tr>
  <tr id="tr1-2">
    <td id="td1-2-1">1-2-1</td>
    <td id="td1-2-2">1-2-2</td>
  </tr>
</table>
<table id="table2">
  <tr id="tr2-1">
    <td id="td2-1-1">2-1-1</td>
    <td id="td2-1-2">2-1-2</td>
  </tr>
  <tr id="tr2-2">
    <td id="td2-2-1">2-2-1</td>
    <td id="td2-2-2">2-2-2</td>
  </tr>
</table>

A note on developer tools

D3 selections are basically just arrays and if you look at a D3 selection in a developer tool like the Google Chrome console it only shows the array. See line 2 below. But don’t be confused! The selection doesn’t show up as having a special type or representation, but it is special. It has extra methods assigned on it and you can inspect and call them:

>>> d3.selectAll("table")
[ Array[2] ]
>>> typeof d3.select("table").attr
"function"
>>> d3.select("table").attr("id")
"table1"

Getting a D3 selection

Where does a D3 selection object come from? The select() and selectAll() methods. Those methods return a selection matching the input parameter. Often the developer selects for strings, using the selectors we know and love from the CSS specification. Selectors are a powerful mini-language worth reviewing to get the most out of D3.

>>> d3.selectAll("table")
[ Array[2] ]
>>> d3.selectAll("td")
[ Array[8] ]
>>> d3.selectAll("#td1-1-1")
[ Array[1] ]
>>> d3.selectAll("#broken")
[ Array[0] ]

In addition to strings, select() and selectAll() also work with ordinary DOM elements returned by other Javascript APIs. This capability can also be useful inside D3: inside an iterator function like each() you can use it to elevate the DOM element passed in the this parameter back into a selection object so you can then modify it with D3 methods.

>>> document.getElementById("td1-1-1")
​1-1-1​
​
>>> d3.selectAll(document.getElementById("td1-1-1"))
[ Array[0] ]

>>> d3.selectAll("td").each(function() { this.style("font-weight", "bold") })
TypeError: Property 'style' of object is not a function

>>> d3.selectAll("td").each(function() { d3.select(this).style("font-weight", "bold") })
[ Array[8] ]

Using a D3 selection

Most of your work with D3 is getting a selection and then invoking methods on it. All selection methods except call are implicitly loops, they will operate once per element in the selection.  The methods attr(), style(), property(), text(), and html() allow modification of the selected elements. each() allows you to call arbitrary functions for each element; methods like attr() are implemented by calling each(). append(), insert(), and remove() allow elements to be added to the DOM. on() allows events to be bound to the selected elements.

Subselections

D3 allows you to create a subselection within a selection, to work on a subset of elements from an initial selection. Subselections allow like elements to be grouped so you can process your data in a structured fashion. (This also explains why selections are 2d arrays; they support the nesting). I haven’t needed to use subselections yet: the scatterplot matrix example shows their use.

>>> d3.selectAll("td")
[ Array[8] ]
>>> d3.selectAll("td")[0][0].id
"td1-1-1"
>>> d3.selectAll("table").selectAll("td")
[ Array[4], Array[4] ]
>>> d3.selectAll("table").selectAll("td")[0][0].id
"td1-1-1"
>>> d3.selectAll("table").selectAll("td")[1][0].id
"td2-1-1"

Data

The data() method allows data to be stored for each element in the selection. Data is stored in a __data__ property right on the DOM node. You should normally not access this property directly, but it’s helpful to understand how D3 keeps data associated with your document elements.

>>> d3.selectAll("td").data([1, 2, 3, 4, 5, 6, 7, 8])
>>> d3.selectAll("td").each(function(d) { console.log(d); })
1 2 3 4 5 6 7 8
>>> d3.selectAll("#td2-1-1").each(function(d) { console.log(d); })
5
>>> document.getElementById("td2-1-1").__data__
5

data() returns the updating selection; you can immediately use attr(), each(), etc after calling data().

>>> d3.selectAll("td").data([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>>    .each(function(d) { console.log(d); })
1 2 3 4 5 6 7 8
[ Array[10] ]

Note how the array returned by each() is length 10? We bound 10 data items to the selection, but we only had 8 matching td elements. The extra two new elements implied by the data are accessible as the enter selection. This enter selection only has append() and insert() methods defined on it; you cannot call attr(), etc until you create DOM nodes. There is a symmetric exit selection that has the full D3 selection methods, typically used for removing nodes from the DOM.