Workshop given at AAAS data storytelling conference by John Muyskens on Friday July 14, 2017.
- goal is for you to get a feel for library and get comfortable reading docs
- also learn some conventions - not the only way to do things!
- d3 is a toolkit, not a 3D printer
- what to use it for + what not to use it for
d3 is a powerful, low level toolkit for creating data-driven documents. It is overkill for basic charts, but if you need custom chart types, interaction, or you want to build your own charting library d3 is good to know. It can produce HTML and SVG documents or draw on a Javascript canvas.
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 involves reading some documentation, then writing a few lines of code. If you get stuck, there is a link at the end of each step to a working implementation.
We will be using Blockbuilder to write code. Blockbuilder is a code editor that runs in your browser and shows you the results which refresh as you write. If you have a GitHub account, you can log in and save your code as Gists. Here's a link to the starting point for our workshop.
Scalable Vector Graphics is a vector image format. It can be viewed with modern browsers and edited with programs like Adobe Illustrator or Inkscape (open source).
Demo: create an SVG
Task: create an SVG with a rect, circle, text and path.
EXTRA CREDIT: draw a snowman
TASK: draw the same shape with d3
Task: group some elements together and transform them.
Extra credit: rotate your group.
Demo: setting up a basic chart, my first steps with any D3 work
First, lets load our 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)
if you are fancy.
Now let’s play with the data, using built in Array functions:
TASK: filter out values where one or both are NA. Then create an Array of life expectancies.
Finally, lets play with some helpful d3-array functions:
d3.max, d3.min, d3.mean, d3.median, d3.extent
http://blockbuilder.org/jmuyskens/4fec60116f5eb5f81edd4380786f8fca
First, choose what variable goes on each axis. We will be doing spending on x and life expectancy on y
var xScale = d3.scaleLinear()
.domain([0, d3.max(healthExpenditures)])
.range([0, width]);
Task: write the y scale, using the extent of the life expectancy for the domain. Note that zero in SVG is at the top of the image, so you’ll want to flip your range.
Live coding: add circles
Task: add some text following the same enter pattern. Note: Use a class in your selection (like ‘text.label’) to differentiate your labels from the text in the axes. Remember to add the class to the text. I would suggest using transform
for placement so you can use dx
and dy
for tweaking placement.
There are a ton of labels! Filter out some so we can annotate just a few countries of interest.
http://blockbuilder.org/jmuyskens/9831944f2ccc77f86e627df0c28c9815
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, 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
http://blockbuilder.org/jmuyskens/4d7467f86e731a58848947118923e6f8
Goal is to structure your data the way you want your DOM to look.
Nest
is like "groupBy" in other functional programming languages
.entries
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 not equal 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.
http://blockbuilder.org/jmuyskens/4a0e5f282699b88082bf426b10ee1d21
docs: enter pattern example
Demo: create an update function
timer function:
var i = 0;
d3.interval(function() {
if (i < dataByYear.length) update(dataByYear[i++].values);
}, 500);
Task extend your update pattern to the labels labels so they also update
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 d3.select(this)
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.
Task: nest your data by country, then create a d3.line generator
Extra credit: animate the line into view
Live coding: add transition to circles for position and radius
Task: add transition to text. Try out different easing patterns.
- Voronoi
- Layouts (force, hierarchy)
- Geo tools
- Modules
- Behaviors (drag and zoom)
- Canvas
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.
Swoopy drag for interactive annotations.