No description, website, or topics provided.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.

Intermediate D3 workshop at NICAR 2018

We will build on D3.js basics by exploring more complex chart forms, covering functions for fetching and manipulating data, and introducing transitions and interaction. We will write working code together and break down how some of our favorite examples of D3 charts work.

This is designed to build on the beginner workshop.

Steps to get started

  1. Download or git clone this repository.
  2. In terminal, run the command python -m SimpleHTTPServer 8000
  3. In a browser, open the url http://localhost:8000/
  4. In a text editor (e.g. Sublime Text), open the repository folder.
  5. Start with 1-dom-manipulation/index.html

Follow along

View the slides

Docs: everything D3 can do

We will make an animated chart together that shows the relationship between health spending and life expectancy using data from the OECD. This chart was inspired by Peter Aldhous (you can view his implementation in R here).

Each step builds upon the last one. We will discuss the new concepts introduced in the step, read some documentation and then write a few lines of code (look for commented TODOs). If you get stuck, you can always peek at the next step to see how I wrote the code.

1. Working with data

starting point

Before we start, a word about anonymous functions

Load the data: Docs: d3.csv.

d3.csv(‘oecd.csv’, function(data) { // do things with the data });

Take a look at your data using console.log(data) or console.table(data).

Now let’s play with the data, using built in Array functions:

TASK: filter out values where one or both are NA. Then create arrays of life expectancies and health expenditures using .map().

Finally, lets play with some helpful d3-array functions:

d3.max, d3.min, d3.mean, d3.median, d3.extent

what it should look like

2. Grouping data

starting point

Our goal is to structure the data in the way we want our SVG to be structured.

d3.nest() is like GROUP BY in SQL

Docs: d3.nest

nest diagram

.entries(data) takes your data as an argument and returns an array

.map() returns an object which is useful for creating lookup tables. There's also .object() which you can use if you are sure your data doesn't collide with js reserved words. JS objects are not equivalent to hash tables like python dicts but can be used in much the same way.

Try it out with Mr. Nester.

create an object called dataByYear and use it with your circle selection. Try different years

Now repeat the excercise using .entries. Note that the structure changes.

what it should look like

3. Scatterplot

starting point

setting up the chart

First we need to set up our chart. We will use the Mike Bostock's D3 margin convention. This convention is not technically necessary to set up a chart, but it is widely used in the D3 community.

The convention makes use of two new concepts: <g> and transform. <g> is an SVG element that groups other SVG elements. transform is an SVG attribute that can translate, rotate and scale an element.

Docs for <g>

Docs for transform

chart convention

defining scales

Docs: d3-scale

Next, choose what variable goes on each axis. We will be using health expenditures on x and life expectancy on y.

Since the minimum value for health expenditures is relatively near zero on our scale, it is good practice to include zero in the domain.

var xScale = d3.scaleLinear()
    .range([0, width]);
    .domain([0, d3.max(healthExpenditures)])

Task: write the y scale, using the extent of the life expectancy for the domain. Note that zero in SVG is at the top, so you’ll want to flip your range.

drawing circles

Task: use the .data().enter().append() pattern to draw circles based on our oecd data. When you get that working, try using just a single year from dataByYear.

Docs: SVG circle

Extra credit: see what happens if you change the domain of your y scale to include zero. When MUST a chart have a zero baseline? (more on this topic) Try plotting the year on the x-axis rather than health expenditures.

what it should look like

4. Axes

starting point

Docs: D3 axis

Demo: create an x axis

Task: create a y axis

The backbone of the axis is a <path> while the ticks are <line>s so you can easily style them separately with CSS.

We can make a dashed line with stroke-dasharray: TKpx TKpx;.

Task: reduce the number of ticks and make the y axis ticks extend across the chart

Use a transform to move your axis around.

Play with the following:

.tickSize to specify length of ticks. Try making your axes as wide and tall as your chart.

.ticks to specify number of ticks

.tickFormat format the tick

Docs: axis ticks

what it should look like

5. Annotations

starting point

Our chart is meaningless without labels. Let's label the axes and (some of) the points using SVG <text> elements.

Docs: <text>

Docs: text-anchor

what it should look like

6. Connected scatterplot

starting point

Lets make a connected scatterplot by drawing a path for each country. Connected scatterplots are cool because they give us an alternate way to present comparative time series data. The effectiveness of this chart type is highly dependent on trends in your data. If your dataset has heavily cyclical trends, for instance, a connected scatterplot may end up looking like an incomprehensible scribble. But with the right data they can really shine. Learn more.

We can use the data we grouped by country in step 2 and the scales we already set up in step 3.

Docs: d3.line

what it should look like

7. Advanced update pattern

starting point

In order to support animation and interaction, we need to be able to dynamically update the data in our selections. In this step we will write a function that we can call to update our data.

We are familiar with .enter() which gets called for new data points. But now we need to define what happens when data points update (.merge()) or are removed (.exit()).

We also need to tell .data() how to index our dataset (linking data points between updates) by writing a key function.

update pattern

docs: .data() key function, Bostock's explanation

what it should look like

8. Transitions

starting point

Docs: d3 transition

Demo: add transition to circles for position and radius

Task: add a transition to labels.

Extra credit: add transitions to .exit() calls.

what it should look like

9. Interaction

starting point

When entering or adding elements chain .on(EVENTNAME, callback). Similar to jQuery, this calls a function when an event happens.

D3 will call your callback function with the datum like you get in other accessor functions. Use to select the element that was triggered.

Some events:

  • mouseenter
  • mouseleave
  • mouseover
  • click

Demo: create a mouseover function for the circles

Task: make a replay “button” that triggers when you ’click’ it.

Extra credit docs: Voronoi

what it should look like

what's in the advanced class

  • Layouts (force, hierarchy)
  • Geo tools
  • Modules
  • Behaviors (drag and zoom)
  • Canvas

libraries and tools you may find useful

crowbar to download your chart as an SVG. You can then edit it using vector graphics software such as Adobe Illustrator.

d3-jetpack for convenience functions that will save you a lot of repetitive typing.

d3-legend to make convenient legends based on your scales.

Textures.js to use patterns in your visualizations.

d3-annotation for interactive annotations.

flubber for better transitions between shapes. Example.

Observable Mike Bostock's shiny new notebook coding environment. Example.


The data (data/oecd.csv) is created by an R script oecd.R which is included in this repository. You can run it like this:

Rscript oecd.R

index.html is generated from using readme.R. I've also included intermediate-d3-nicar-2018.Rproj which can be opened in RStudio.