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.
- Download or
git clone
this repository. - In terminal, run the command
python -m SimpleHTTPServer 8000
- In a browser, open the url http://localhost:8000/
- In a text editor (e.g. Sublime Text), open the repository folder.
- Start with 1-dom-manipulation/index.html
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 TODO
s). If you get stuck, you can always peek at the next step to see how I wrote the code.
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
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
.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 dict
s 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.
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.
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.
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
.
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.
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
Our chart is meaningless without labels. Let's label the axes and (some of) the points using SVG <text>
elements.
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.
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.
docs: .data() key function, Bostock's explanation
Demo: add transition to circles for position and radius
Task: add a transition to labels
.
Extra credit: add transitions to .exit() calls.
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.
Extra credit docs: 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.
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 README.md using readme.R
. I've also included intermediate-d3-nicar-2018.Rproj
which can be opened in RStudio.