Developing dank datavis with d3.js – But first, let's discuss – principles



Developing dank datavis with d3.js – But first, let's discuss – principles

0 0


d3-talk

Slides for my talk on d3.js at UnCodebar

On Github richardwestenra / d3-talk

Developing dank datavis

with
d3.js

Richard Westenra

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.

But first, let's discuss

principles

What to do and not to do when visualizing data

Published in 1983, by Tufte. He is considered the godfather of datavis.

1. Just show the data

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.

2. Be honest

No! Axes and scales have meanings!

This is basically just pretending to be a chart.

The Lie factor

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.

3. Don't make me think

Compare:

Rugby Six Nations Head to Head

vs

An iPod visualised as Vinyl

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.

Working with data

One of the great things about d3 is the way it handles data.

Importing 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.

Format data before use

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.

Selections

d3.select('ul').selectAll("li")
        

Works a bit like jQuery: CSS selectors, chaining, etc.

Select vs SelectAll

Binding data

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>
        

Exit, Enter, update

Exit, Enter, update

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; });
        

Transitions

Transitions

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);});
        

Scales

are functions that map from an input domain to an output range

Input: [20,60] 40
Output: [0,400] 200

The biggest problem in datavis is converting data units into pixel units. Scale functions take care of this for you.

Input: Domain!

Output: Range!

Quantitative

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.

Ordinal

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.

Maps!

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.

GeoJSON & TopoJSON

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

Layouts!

Use the force, Luke!

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.

Collision detection

Gravity: 0.05
Charge: 0
Friction: 0.9

See bl.ocks.org/mbostock/3231298

Use friction, charge, gravity etc to adjust node behaviour

Making it responsive

Detect resize events with event listeners and then update with width/height values and use the update the values.

Thanks!

Slides and code:

richardwestenra.com/d3-talk or github.com/richardwestenra/d3-talk

Resources

Developing dank datavis with d3.js Richard Westenra 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.