On Github richardwestenra / d3-talk
twitter.com/richardwestenra • github.com/richardwestenra
Hey, I'm Richard. I'm a front-end developer and interactive designer on the creative team at Distilled.
Today I'm going to take you through the basics of working with d3.
We make JavaScript data-visualisations, and interactive gubbins.
We're basically Buzzfeed for hire.
What to do and not to do when visualizing data
Published in 1983, by Tufte. He is considered the godfather of datavis.
Data-ink ratio = Data-ink / Total ink used to print the graphic
The share of ink on a graphic presenting data-information should be as large as possible.
No! Axes and scales have meanings!
This is basically just pretending to be a chart.
To talk about graphical integrity, Tufte introduced the concept of the lie-factor.
The lie-factor is equal to the size of the effect shown in the graphic divided by the size of effect in data. So in the image above, the decision to use perspective corrupts the display and results in a lie-factor of 14.8.
Rugby Six Nations Head to Head
vs
d3 is very powerful and flexible, but has a steep learning curve and requires bit more setup compared to libraries like Highcharts.
It enforces separation of concerns between data and presentation.
It can be a little counterintuitive at first, but it's a really powerful way of programming once you get your head around it.
Loads of examples available online. Read them to get used to the style of coding.
One of the great things about d3 is the way it handles data.
var data; // a global d3.csv('path/to/file.csv', function(error, json) { if (error) { return console.warn(error); } data = json; visualizeit(); });
Works for JSON, CSV, TSV, XML, HTML, text, etc.
See github.com/mbostock/d3/wiki/Requests
Use CSV/TSV or JSON depending on data size
Make your data as compact as possible then format it in JS to make it prettier. e.g. Use 0/1 instead of true/false or 'happy','sad', the use a script to replace the value on load.
CSV is untyped, so coercion from strings is required.
Asynchronous. Code that depends on data must be invoked via callback.
var rawData = [ {n: 'bunnies', c: 1}, {n: 'kittens', c: 2}, {n: 'puppies', c: 2}, {n: 'cows', c: 0} ]; var formattedData = data.map(function (d){ return { name: d.n.toUpperCase() + '!!!', cuteness: ['Not very cute', 'Cute', 'Very cute!!!'][d.c] }; });
Make your data as compact as possible then format it in JS to make it prettier. e.g. Use 0/1 instead of true/false or 'happy','sad', the use a script to replace the value on load.
Clean up the data before attaching it to objects, not the other way around.
Avoid DOM operations
JavaScript has a number of useful built-in array methods: Array.map, Array.filter, Array.sort, Array.reduce, etc. If you need more, D3 also has a variety of data-transform methods. ALso try Underscore or Lo-dash.
Bind as much data to an element as possible in order to make it more flexible.
d3.select('ul').selectAll("li")
Works a bit like jQuery: CSS selectors, chaining, etc.
Select vs SelectAll
d3.select('ul').selectAll("li").data(['foo','bar','baz'])
See bost.ocks.org/mike/join and bost.ocks.org/mike/circles
Selections are arrays
d3.select('ul') .selectAll('li') .data(['foo','bar','baz']) .enter() .append('li') .text(function (d) { return d; });
<ul> <li>foo</li> <li>bar</li> <li>baz</li> </ul>
var rect = svg.selectAll('rect') .data(data); rect.exit() .remove(); rect.enter() .append('rect') .attr('y', function(d) { return d.y; }); rect.attr('width', function(d) { return d.width; });
d3.selectAll('rect') .data(randomData) .enter() .append('rect') .attr('width',0) .transition() .duration(600) .delay(function (d,i){ return i * 200;}) .attr('width', function (d){ return x(d);});
The biggest problem in datavis is converting data units into pixel units. Scale functions take care of this for you.
var x = d3.scale.linear() .domain([12, 24]) .range([0, 720]); x(16); // 240
Quantitative scales map a continuous (numeric) domain to a continuous range (e.g. linear, exponential, logarithmic, etc.
var x = d3.scale.ordinal() .domain(['A', 'B', 'C', 'D']) .range([0, 10, 20, 30]); x('B'); // 10
Ordinal scales map a discrete domain to a discrete range.
Placing points according to lat/long
D3 includes different projections and you can add more
The first task for any map is finding geometry. Try government census data, Ordnance Survey, GeoCommons, etc.
Hands-down, the most convenient source of free geographic data is Natural Earth. An apparent labor of love by cartographer Nathaniel Vaughn Kelso (and others), Natural Earth provides a variety of cultural, physical and raster datasets. Shapefiles are beautifully simplified by hand for different resolutions, letting you choose the resolution appropriate to your application.
See Mike Bostock's Let's Make a Map
If you care about file size or topology, then use TopoJSON. If you don't care about either, then use GeoJSON for simplicity's sake.
You'll need to install command line tools
mercatorequirectangularazimuthalEqualAreaazimuthalEquidistantconicEqualAreaconicConformalconicEquidistantorthographicstereographictransverseMercator
See mbostock.github.io/d3/talk/20110921
Use friction, charge, gravity etc to adjust node behaviour
Force charts include a tick function the handles simulation's animation. On each tick you can have it do something, until it stops ticking.
Internally, the layout uses a cooling parameter alpha which controls the layout temperature: as the physical simulation converges on a stable layout, the temperature drops, causing nodes to move more slowly. Eventually, alpha drops below a threshold and the simulation stops completely, freeing the CPU and avoiding battery drain.
Detect resize events with event listeners and then update with width/height values and use the update the values.
Slides and code:
richardwestenra.com/d3-talk or github.com/richardwestenra/d3-talk