D3 Workshop

Mike Bostock
@mbostock

Outline

Preface

Visualizing Data with Web Standards

D3 provides transformation; no new representation.

DataElements

Visualization requires visual encoding: mapping data to elements.

Data-Driven Documents

The name “D3” refers to the W3C Document Object Model.

Web Standards

There are myriad free resources for learning standards.

HTML Resources

HTML5 Spec, HTML5 for Developers, MDN, Dive Into HTML5

<!DOCTYPE html>
<meta charset="utf-8">
<body>
Hello, world!

hello-world.html

SVG Resources

SVG Spec, MDN, D3 API Reference

<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500">
  <text y="12">
    Hello, world!
  </text>
</svg>

hello-svg.html

CSS Resources

CSS Spec, Selectors Spec

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body { background: steelblue; }
</style>
<body>
Hello, world!

hello-css.html

JavaScript Resources

MDN, Douglas Crockford’s JavaScript: The Good Parts

<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script>
console.log("Hello, world!");
</script>

hello-javascript.html

Start small!

You don’t need to master everything at once.

Getting Started

http://d3js.org/

<!DOCTYPE html>
<meta charset="utf-8">
<style>/* CSS */</style>
<body>
<script src="d3.v2.js"></script>
<script>/* JavaScript */</script>

hello-d3.html

> python -m SimpleHTTPServer 8888 &

A web server is required when loading external data (e.g., d3.csv).

Developer Tools
⌥⌘I

JavaScript Console
⌥⌘J

D3 Resources

D3 API Reference, D3 Wiki, D3 Group, D3 Stack Overflow

Selections

Selectors

CSS provides a handy way to refer to specific elements.

pre, code {
  font-family: "Menlo", monospace;
  font-size: 48px;
}

CSS assigns style rules via selectors.

#foo        // <any id="foo">
foo         // <foo>
.foo        // <any class="foo">
[foo=bar]   // <any foo="bar">
foo bar     // <foo><bar></foo>

Simple selectors identify elements by one facet.

foo.bar     // <foo class="bar">
foo#bar     // <foo id="bar">

Compound selectors identify elements by two or more facets.

document.querySelectorAll("pre, code")

The W3C standardized selector support for JavaScript.

d3.selectAll("pre, code")

D3 provides shorthand for selecting and manipulating.

Selections are Arrays

Use developer tools to select, inspect, modify and experiment.

// select all <circle> elements
var circle = d3.selectAll("circle");

// set some attributes and styles
circle.attr("cx", 20);
circle.attr("cy", 12);
circle.attr("r", 24);
circle.style("fill", "red");

The attr and style methods set attributes and styles, respectively.

// select all <circle> elements
// and set some attributes and styles
d3.selectAll("circle")
    .attr("cx", 20)
    .attr("cy", 12)
    .attr("r", 24)
    .style("fill", "red");

Method chaining allows shorter (and more readable) code.

selection.append

Create new elements, append, and select.

// select the <body> element
var body = d3.select("body");

// add an <h1> element
var h1 = body.append("h1");
h1.text("Hello!");

With one element selected, adds one element.

// select all <section> elements
var section = d3.selectAll("section");

// add an <h1> element to each
var h1 = section.append("h1");
h1.text("Hello!");

With many elements selected, adds one element to each.

var h1 = d3.selectAll("section")
    .style("background", "steelblue")
  .append("h1")
    .text("Hello!");

Use caution with method chaining: append returns a new selection!

Add many elements?

No single operation; this brings us to our next topic…

Data

Data are Arrays

You can represent data however you like (in JavaScript).

// A bar chart, perhaps?
var data = [1, 1, 2, 3, 5, 8];

Data can be numbers.

bar-chart.html

// A scatterplot, perhaps?
var data = [
  {x: 10.0, y: 9.14},
  {x:  8.0, y: 8.14},
  {x: 13.0, y: 8.74},
  {x:  9.0, y: 8.77},
  {x: 11.0, y: 9.26}
];

Data can be objects.

dot-chart.html

DataElements

Use data to create multiple elements.

svg.selectAll("circle")
    .data(data)
  .enter().append("circle")
    .attr("cx", x)
    .attr("cy", y)
    .attr("r", 2.5);

We want the selection “circle” to correspond to data.

var circle = svg.selectAll("circle")
    .data(data);

The data method computes the join, defining enter and exit.

var circle = svg.selectAll("circle")
    .data(data);

circle.enter().append("circle");

Appending to the enter selection creates the missing elements.

var circle = svg.selectAll("circle")
    .data(data);

circle.enter().append("circle")
    .attr("cx", x)
    .attr("cy", y)
    .attr("r", 2.5);

The new elements are bound to data, so we can compute attributes.

Enter, Update & Exit

Thinking with Joins

Enter

New data, for which there were no existing elements.

var circle = svg.selectAll("circle")
    .data(data)
  .enter().append("circle")
    .attr("cx", x)
    .attr("cy", y)
    .attr("r", 2.5);

When initializing, you might ignore update and exit.

Update

New data that was joined successfully to an existing element.

var circle = svg.selectAll("circle")
    .data(data)
    .attr("cx", x)
    .attr("cy", y)
    .attr("r", 2.5);

When updating, you might ignore enter and exit.

Exit

Existing elements, for which there were no new data.

Enter +
Update

Entering nodes are added to update on append.

Key Function

You can control the join; by default, the join is by index.

// A scatterplot, perhaps?
var data = [
  {name: "Alice", x: 10.0, y: 9.14},
  {name:   "Bob", x:  8.0, y: 8.14},
  {name: "Carol", x: 13.0, y: 8.74},
  {name:  "Dave", x:  9.0, y: 8.77},
  {name: "Edith", x: 11.0, y: 9.26}
];

If needed, data should have a unique key for joining.

function key(d) { return d.name; }

var circle = svg.selectAll("circle")
    .data(data, key)
    .attr("cx", x)
    .attr("cy", y)
    .attr("r", 2.5);

The key function returns a unique string for each datum.

Loading Data

D3 provides several convenience routines using XMLHttpRequest.

CSV

Comma-Separated Values: d3.csv

symbol,date,price
S&P 500,Jan 2000,1394.46
S&P 500,Feb 2000,1366.42
S&P 500,Mar 2000,1498.58
S&P 500,Apr 2000,1452.43
S&P 500,May 2000,1420.6
S&P 500,Jun 2000,1454.6
S&P 500,Jul 2000,1430.83

stocks.csv

var format = d3.time.format("%b %Y");

d3.csv("stocks.csv", function(stocks) {
  stocks.forEach(function(d) {
    d.price = +d.price;
    d.date = format.parse(d.date);
  });
});

CSV is untyped, so coercion from strings is required.

area-chart.html

JSON

JavaScript Object Notation: d3.json

[{"symbol": "S&P 500", "date": "Jan 2000", "price": 1394.46},
 {"symbol": "S&P 500", "date": "Feb 2000", "price": 1366.42},
 {"symbol": "S&P 500", "date": "Mar 2000", "price": 1498.58},
 {"symbol": "S&P 500", "date": "Apr 2000", "price": 1452.43},
 {"symbol": "S&P 500", "date": "May 2000", "price": 1420.6},
 {"symbol": "S&P 500", "date": "Jun 2000", "price": 1454.6},
 {"symbol": "S&P 500", "date": "Jul 2000", "price": 1430.83},
 {"symbol": "S&P 500", "date": "Aug 2000", "price": 1517.68},
 {"symbol": "S&P 500", "date": "Sep 2000", "price": 1436.51},
 {"symbol": "S&P 500", "date": "Oct 2000", "price": 1429.4},
 {"symbol": "S&P 500", "date": "Nov 2000", "price": 1314.95},
 {"symbol": "S&P 500", "date": "Dec 2000", "price": 1320.28},
 {"symbol": "S&P 500", "date": "Jan 2001", "price": 1366.01}…

stocks.json

var format = d3.time.format("%b %Y");

d3.json("stocks.json", function(stocks) {
  stocks.forEach(function(d) {
    d.date = format.parse(d.date);
  });
});

JSON is typed, but you must still parse dates.

Data is Asynchronous

Code that depends on data must be invoked via callback.

Data is Messy

Data is rarely in the exact format needed for visualization.

array.
{filter,map,sort,…}

JavaScript has a number of useful built-in array methods.

d3.
{nest,keys,values,…}

D3 also has a variety of data-transform methods; explore the API.

Scales & Axes

Scales

DataAttributes

Attributes (and styles) control position and appearance.

DomainRange

Scales are functions that map from data-space to visual-space.

function x(d) {
  return d * 42 + "px";
}

Scales are convenient but optional; you can roll your own.

Quantitative Scales

Map a continuous (numeric) domain to a continuous range.

var x = d3.scale.linear()
    .domain([12, 24])
    .range([0, 720]);

x(16); // 240

A linear scale simply translates and scales.

var x = d3.scale.sqrt()
    .domain([12, 24])
    .range([0, 720]);

x(16); // 268.9056992603583

A sqrt (or pow) scale applies an exponential transform.

var x = d3.scale.log()
    .domain([12, 24])
    .range([0, 720]);

x(16); // 298.82699948076737

A log scale applies a logarithmic transform.

Domains & Ranges

Typically, domains are derived from data while ranges are constant.

var x = d3.scale.linear()
    .domain([0, d3.max(numbers)])
    .range([0, 720]);

Use d3.min and d3.max to compute the domain.

var x = d3.scale.log()
    .domain(d3.extent(numbers))
    .range([0, 720]);

Use d3.extent to compute the min and max simultaneously.

function value(d) { return d.value; }

var x = d3.scale.log()
    .domain(d3.extent(objects, value))
    .range([0, 720]);

Use an accessor function to derive a numeric value for objects.

Interpolators

Quantitative scales support multiple interpolators.

var x = d3.scale.linear()
    .domain([12, 24])
    .range(["steelblue", "brown"]);

x(16); // #666586

Colors are detected automatically for RGB interpolation.

var x = d3.scale.linear()
    .domain([12, 24])
    .range(["0px", "720px"]);

x(16); // 240px

String interpolation matches embedded numbers; quite flexible.

var x = d3.scale.linear()
    .domain([12, 24])
    .range(["steelblue", "brown"])
    .interpolate(d3.interpolateHsl);

x(16); // #3cb05f

Interpolators can be set explicitly, if desired.

You can even interpolate objects!

Diverging Scales

Sometimes, you want a compound (“polylinear”) scale.

var x = d3.scale.linear()
    .domain([-10, 0, 100])
    .range(["red", "white", "green"]);

x(-5); // #ff8080
x(50); // #80c080

The domain and range can have more than two values!

Ordinal Scales

Map a discrete domain to a discrete range.

var x = d3.scale.ordinal()
    .domain(["A", "B", "C", "D"])
    .range([0, 10, 20, 30]);

x("B"); // 10

An ordinal scale is essentially an explicit mapping.

var x = d3.scale.category20()
    .domain(["A", "B", "C", "D"]);

x("B"); // #aec7e8

Ordinal scales are often used to assign categorical colors.

var x = d3.scale.category20()
    .domain(["A", "B", "C", "D"]);

x("E"); // #2ca02c
x("E"); // #2ca02c
x.domain(); // A, B, C, D, E

Unknown values are implicitly added to the domain.

A handful of color scales are built-in; see also ColorBrewer.

var x = d3.scale.ordinal()
    .domain(["A", "B", "C", "D"])
    .rangePoints([0, 720]);

x("B"); // 240

Ordinal ranges can be derived from continuous ranges.

var x = d3.scale.ordinal()
    .domain(["A", "B", "C", "D"])
    .rangeRoundBands([0, 720], .2);

x("B"); // 206, bar position
x.rangeBand(); // 137, bar width

Ordinal ranges are particularly useful for bar charts.

Axes

D3 provides convenient labeling for scales.

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

Create an axis for a given scale, and configure as desired.

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);

Render the axis by calling a <g> selection.

.axis path, .axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

Customize axis appearance via CSS.

Ticks

Quantitative scales can be queried for “human-readable” values.

var x = d3.scale.linear()
    .domain([12, 24])
    .range([0, 720]);

x.ticks(5); // [12, 14, 16, 18, 20, 22, 24]

The requested count is only a hint (for better or worse).

Tick Formats

See d3.format and d3.time.format.

Marks

SVG Coordinates

Absolute positioning; the origin ⟨0,0⟩ is the top-left corner!

Use transforms to define a new origin.

var svg = d3.select("body").append("svg")
    .attr("width", outerWidth)
    .attr("height", outerHeight);

var g = svg.append("g")
    .attr("transform", "translate("
      + marginLeft + ","
      + marginTop + ")");

Use margins for decorative elements, such as axes.

SVG Basic Shapes

SVG Spec

<rect
    x="0"
    y="0"
    width="0"
    height="0"
    rx="0"
    ry="0">

Rect

<circle
    cx="0"
    cy="0"
    r="0">

Circle

<line
    x1="0"
    y1="0"
    x2="0"
    y2="0">

Line

<text
    x="0"
    y="0"
    dx="0"
    dy="0"
    text-anchor="start">

Text

<text dy="0">bottom</text>
<text dy=".35em">middle</text>
<text dy=".71em">top</text>

Vertical alignment of text is slightly awkward.

SVG Paths

SVG Spec

<path d="M152.64962091501462,320.5600780855698L133.88913955606318,325.4363177123538L134.96890954443046,330.37917634921996L131.19348249532786,331.158393614812L98.56681109628815,335.53933807857004L91.14450799488135,333.79662025279L72.1880101321918,333.74733970068166L69.51723455785742,332.8569681440152L62.37313911354066,333.2100666843387L62.248334309137434,335.3677272708405L58.843440998888326,335.0574959605036L53.97667317214221,331.36075125633175L56.30952738118711,325.9417994311851L63.80207296237137,326.0219658098969L68.37010032001055,321.68160223702955L68.82177412097933,318.08112591435287L73.34…">

Paths require another mini-language!

Path Generators

Configurable functions for generating paths from data.

d3.svg.line

Define a path in terms of x and y.

var x = d3.scale.linear(),
    y = d3.scale.linear();

var line = d3.svg.line()
    .x(function(d) { return x(d.x); })
    .y(function(d) { return y(d.y); });

Compose scales with data accessors to define position.

svg.append("path")
    .datum(objects)
    .attr("class", "line")
    .attr("d", line);

Pass data to the line generator directly, or via selection.attr.

d3.svg.area

Define a path in terms of x, y0 and y1.

var x = d3.scale.linear(),
    y = d3.scale.linear();

var area = d3.svg.area()
    .x(function(d) { return x(d.x); })
    .y0(height)
    .y1(function(d) { return y(d.y); });

For non-stacked area charts, y0 is constant.

For streamgraphs, use d3.layout.stack to compute the baseline.

Line Interpolators

Line and area generators support multiple interpolation modes.

Linear Interpolation

Step Interpolation

Basis Interpolation

Radial Areas & Lines

Similar to d3.svg.{area,line}, except in polar coordinates.

d3.svg.arc

A path generator for pie and donut charts, among other uses.

var myArc = {
  "innerRadius": 0,
  "outerRadius": 360,
  "startAngle": 0, // 12 o'clock
  "endAngle": 1.2 // radians
};

By default, takes as input an object with arc-related properties.

var arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(360);

Override the accessors to set constant properties.

// construct a default pie layout
var pie = d3.layout.pie();

// derive data to feed to d3.svg.arc
var myArcs = pie(numbers);

Use d3.layout.pie to compute start and end angles from data.

Layouts

Layouts are Data

Layouts are reusable algorithms that generate data, not display.

Layouts are Varied

Each layout is different. Most are stateless, but not all.

Hierarchical Layouts

There are lots of ways to visualize hierarchical data!

d3.layout.treemap

d3.layout.tree

d3.layout.pack

d3.layout.partition

var parent = {"children": […]},
     child = {"value": …};

The hierarchical layouts use a shared representation of data.

var treemap = d3.layout.treemap()
    .padding(4)
    .size([width, height]);

Layouts are configurable functions.

function x(d) { return d.x; }
function y(d) { return d.y; }
function dx(d) { return d.dx; }
function dy(d) { return d.dy; }

The layout populates position properties on the nodes.

svg.selectAll(".cell")
    .data(treemap.nodes(root))
  .enter().append("rect")
    .attr("class", "cell")
    .attr("x", x)
    .attr("y", y)
    .attr("width", dx)
    .attr("height", dy);

By accessing those properties, you can visualize the layout.

d3.layout.force (talk)

d3.layout.bundle

d3.geom.voronoi

d3.layout.chord

Chart Components

Reuse code through configurable functions.

Mike Bostock
@mbostock