Small Multiples with d3

I love small multiples more than anything else.

What is a small multiple?

Instead of one big complicated chart, you just make a lot of small ones. Here’s one from the New York Times about droughts.

small multiples

Here’s another example from Wikipedia.

small multiples

Small multiples make information that would usually take animation or many overlapping lines instead become understandable at a glance!

Method 1: Multiple svg elements

Let’s say you can make a simple line chart using US climate data.

Open example in new window

<svg></svg>
<style>.line { fill: none; stroke-width: 1.5px; stroke: #000000;}</style>
<script>
var height = 300, width = 500;

// average per-month highs in Alaska
var title = "Alaska";
// jan, feb, mar, apr, may, etc
var datapoints = [57, 62, 70, 77, 84, 90, 92, 92, 87, 78, 69, 60];

// y_scale: highs between 20 and 110 give us y values between 0 and 300 (height)
// except you do it backwards because it's *distance from the top*
var y_scale = d3.scale.linear().domain([20,110]).range([height, 0])

// x_scale: use the index of the measurement, so 57 is 0, 62 is 1, etc
// this is because it's per-month data, jan=0, dec=11
var x_scale = d3.scale.linear().domain([0,11]).range([0, width])

var line = d3.svg.line()
    .x(function(d, i) { return x_scale(i); })
    .y(function(d) { return y_scale(d); });

var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height)

svg.append("path")
      .datum(datapoints)
      .attr("class", "line")
      .attr("d", line);
</script>

Open example in new window

But let’s say we now have like nine different states and we want to display them all at once.

What we usually do when we have multiple data points is take that svg, do a selectAll, bind our data and add a ton of circle elements or rect elements or whatever we’re looking to do. But we want multiple svg elements, not multiple circles!

But really, you just do the same thing.

  1. Select the body tag, or the div that you’re going to put your svg elements into
  2. selectAll the svgs inside of it
  3. Bind your data
  4. .enter and .append('svg'), giving you a separate svg for each data point.
  5. Treat those svgs just like you would a single svg, except that the data is already bound!

You’ll want an example, so here you go.

Open example in new window

<style>.label { font-size: 10px; } .line { fill: none; stroke-width: 1.5px; stroke: #000000;} svg { border: solid 1px #333; margin: 5px;}</style>
<script>
var height = 50, width = 70;

var datapoints = [
  { 
    'state': 'Alabama',
    'measurements': [57, 62, 70, 77, 84, 90, 92, 92, 87, 78, 69, 60]
  },
  { 
    'state': 'Alaska',
    'measurements': [23, 27, 34, 44, 56, 63, 65, 64, 55, 40, 28, 25]
  },
  {
    'state': 'Arizona',
    'measurements': [67, 71, 77, 85, 95, 104, 106, 104, 100, 89, 76, 66]
  },
  {
    'state': 'Arkansas',
    'measurements': [51, 55, 64, 73, 81, 89, 92, 93, 86, 75, 63, 52]
  },
  {
    'state': 'California',
    'measurements': [54, 60, 65, 71, 80, 87, 92, 91, 87, 78, 64, 54]
  },
  {
    'state': 'Colorado',
    'measurements': [45, 46, 54, 61, 72, 82, 90, 88, 79, 66, 52, 45]
  },
  {
    'state': 'Connecticut',
    'measurements': [37, 40, 47, 58, 68, 77, 82, 81, 74, 63, 53, 42]
  },
  {
    'state': 'Delaware',
    'measurements': [43, 47, 55, 66, 75, 83, 87, 85, 79, 69, 58, 47]
  },
  {
    'state': 'Florida',
    'measurements': [64, 67, 74, 80, 87, 91, 92, 92, 88, 81, 73, 65]
  }
]

// y_scale: highs between 20 and 110 give us y values between 0 and 300 (height)
// except you do it backwards because it's *distance from the top*
var y_scale = d3.scale.linear().domain([20,110]).range([height, 0]);

// x_scale: use the index of the measurement, so 57 is 0, 62 is 1, etc
// this is because it's per-month data, jan=0, dec=11
var x_scale = d3.scale.linear().domain([0,11]).range([width, 0]);

var line = d3.svg.line()
    .x(function(d, i) { return x_scale(i); })
    .y(function(d) { return y_scale(d); });

// Treat an svg just like we would a circle - add one for every single data point
var svgs = d3.select("body")
              .selectAll("svg")
              .data(datapoints)
              .enter()
              .append('svg')
              .attr("width", width)
              .attr("height", height);

// Inside of each svg, draw your line
svgs.append("path")
      .attr("class", "line")
      .attr("d", function(d) {
        return line(d['measurements']);
      });

svgs.append("text")
      .attr('class','label')
      .attr('x', width / 2)
      .attr('y', height - 5)
      .text( function(d) {
        return d['state'];
      })
      .attr('text-anchor', 'middle')
</script>

Open example in new window

Yeah, it’s horrifying looking, but you get my point. If you treat the svg same as you’d treat a circle or anything else, you can add tons of stuff inside.

Want to hear when I release new things?
My infrequent and sporadic newsletter can help with that.