D3

Home

These pages link to here:

spiderweb.html

Table of contents

  1. API reference
  2. Selections
  3. Data joins
    1. Joining 1 thing
    2. If the data's not exactly the right length?
  4. Enter/exit
    1. Enter()
    2. Exit()
    3. order()
    4. merge()
    5. join()
  5. Transformations
  6. Examples
    1. Simple bar chart
    2. Useful functions
  7. Scaling inputs
  8. Making shapes
  9. Layouts
  10. Interactions

D3 is a data visualisation library for HTML, SVGs, and CSS.

Generally, it works by:

  1. Selecting DOM elements
  2. Joining data to those elements
  3. Adding/removing elements from the page to match the data you've joined with
  4. Transforming the elements

Along with that, D3 also includes functions that let you easily

everything here will call functions like d3.select(), not select()

API reference#

Here

Normally (but not always), D3's methods return DOM elements, so you can chain them together:

d3.select("#treeSvg g.tree__nodes")
    .selectAll("circle.tree__node")
    .data(root.descendants())
    .enter()
    .append("g");

Selections#

First, you need to select HTML or SVG elements on the page - D3 works with both HTML and SVG - using CSS selector syntax:

d3.select("body").style("background-color", "black");   // selects just the <body>, and makes it black
d3.selectAll(".paragraph").style("color", "blue");      // selects all .paragraph's, and makes them all have blue text

Normally, you'll want to join your elements with some data afterwards:

Data joins#

You then attach/bind some data to your elements after you've selected them - the data must always be an array.
For example, this selects all the circles, then sets their radiuses to be each score:

<circle r="40" cx="100 />
<circle r="40" cx="120" />

const scores = [
    {
        "name": "Andy",
        "score": 25
    },
    {
      "name": "Beth",
     "score": 39
    }
];

d3.selectAll('circle')
    .data(scores)
    .attr(
        'r', 
        function(d, i) {
            return d.score;
        }
    );

d3.attr() sets an attribute on each circle, here the radius.

The function here is called once for each element in scores, and implicitly passes in each score object, and its index - 0 to 5 here.

When D3 joins data to elements, it adds a _data_ attribute to each DOM element with the joined data.

Joining 1 thing#

You can also join a single thing (like 1 objet by itself), with .datum():

const featureCollection = {type: 'FeatureCollection', features: features};
d3.select('path#my-map')
    .datum(featureCollection);

If the data's not exactly the right length?#

The previous examples assume that your data is exactly the right length to fit whatever DOM elements you already have on the page - if there aren't enough (e.g. if there aren't any), or there are too many, matching your selection, you'll need to use enter() or exit()

Enter/exit#

If there aren't enough DOM elements matching selection (e.g. if there aren't any), or there are too many, you need to use D3's enter() or exit() methods.

Enter()#

d3.enter() returns placeholder nodes for each DOM element in a selection after joining data to the selection, filling in any missing ones - so, if there were 5 things in the data array, but no DOM elements matching the selection, it'll create 5 dummy nodes, if there's 1 element it'll make 4 nodes, etc.
What it returns is called the "enter selection" - you can also think of it as placeholder nodes for each datum that had no corresponding DOM element in the selection.

After entering, you'll normally want to call d3.append() to actually append the new nodes to the page; enter() doesn't actually create new elements by itself.

This will add 6 new DIVs to the body:

const div = d3.select("body")
    .selectAll("div")
    .data([4, 8, 15, 16, 23, 42])
    .enter()
    .append("div")
    .text(d => d);
    
// result:
<div>4</div>
<div>8</div>
<div>15</div>
<div>16</div>
<div>23</div>
<div>42</div>

Exit()#

Similarly, exit() and remove() do the reverse:
exit() returns the "exit selection", the DOM elements that need to be removed for the earlier selection to match the data - you can also think of it as DOM elements which don't have a matching thing in the data.

Taking the same DOM elements as above,

const div = d3.select("body")
  .selectAll("div")
  .data([1, 2, 4, 8, 16, 32], d => d);

Since a key function was specified (as the identity function), and the new data contains the numbers [4, 8, 16] which match existing elements in the document, the update selection contains three DIV elements. Leaving those elements as-is, we can append new elements for [1, 2, 32] using the enter selection:

div.enter().append("div").text(d => d);

// to remove the exiting elements [15, 23, 42]:
div.exit().remove();

// result:
<div>1</div>
<div>2</div>
<div>4</div>
<div>8</div>
<div>16</div>
<div>32</div>

order()#

You might need to call selection.order() to re-order data.

merge()#

selection.merge(otherSelection) merges two different selections together

join()#

There's also the selection.join() method that's shorthand for calling selection.enter, selection.exit, selection.append, selection.remove, and selection.order - it appends, removes and reorders elements to match a previous d3.data():

svg.selectAll("circle")
    .data(data)
    .join("circle")
        .attr("fill", "none")
        .attr("stroke", "black");
        
// is equivalent to
svg.selectAll("circle")
    .data(data)
    .join(
        enter => enter.append("circle"),
        update => update,
        exit => exit.remove()
    )
        .attr("fill", "none")
        .attr("stroke", "black");

Transformations#

See the API reference.
A lot of the time, you'll want to use

Each method returns the selection again, so you can chain them together:

d3.selectAll('circle')
    .data(myData)
    .attr('r', function(d, i) {
        return d;
    })
    .classed('high', function(d, i) {
        return d >= 40;
    })
    .attr('cx', function(d, i) {
     return i * 120;
    });

Examples#

Simple bar chart#

From D3 in depth,

const cities = [
  { name: 'London', population: 8674000},
  { name: 'New York', population: 8406000},
  { name: 'Sydney', population: 4293000},
  { name: 'Paris', population: 2244000},
  { name: 'Beijing', population: 11510000}
];

// Join cities to rect elements and modify height, width and position
d3.selectAll('rect')
    .data(cities)
    .attr('height', 19)
    .attr('width', function(d) {
        const scaleFactor = 0.00004;
        return d.population * scaleFactor;
    })
    .attr('y', function(d, i) {
        return i * 20;
    });

// Join cities to text elements and modify content and position
d3.selectAll('text')
    .data(cities)
    .attr('y', function(d, i) {
        return i * 20 + 13;
    })
    .attr('x', -4)
    .text(function(d) {
    return d.name;
    });

Useful functions#

Scaling inputs#

Making shapes#

Layouts#

Interactions#


Resources
D3 in depth
Interactive data visualisation for the web

top