Interactive Small Multiples Jim Vallandingham

Wee Little Things Lena Groeger

Basics

Small multiples use the same basic graphic or chart to display difference slices of a data set

A chorus of little stories to help tell a bigger one.

Uses

Origins

Tufte?

Interaction

Linked

Updateable

Sortable

Highlightable

Scrubbable

Brushable

Implementation

MetaFilter

Data

"year" "category" "n" "2004" "clothing, beauty, & fashion" 141 "2004" "computers & internet" 2489 "2004" "education" 151 "2005" "clothing, beauty, & fashion" 203 "2005" "computers & internet" 2200 "2005" "education" 201 "2005" "food & drink" 324 "2005" "grab bag" 248 "2005" "health & fitness" 590

"year" "category" "n" "2004" "clothing, beauty, & fashion" 141 "2004" "computers & internet" 2489 "2004" "education" 151 "2005" "clothing, beauty, & fashion" 203 "2005" "computers & internet" 2200 "2005" "education" 201 "2005" "food & drink" 324 "2005" "grab bag" 248 "2005" "health & fitness" 590

[ {"key":"clothing, beauty, & fashion", "values": [{"year":"2004", "n":141, "date":"2004-01-01"}, {"year":"2005", "n":203, "date":"2005-01-01"},...] }, {"key":"computers & internet", "values": [{"year":"2004", "n":2489, "date":"2004-01-01"}, {"year":"2005", "n":2200, "date":"2005-01-01"},...] }, ... ]

data = d3.nest() .key(function(d) { return d.category;}) .sortValues(function(a, b) { return d3.ascending(a.date, b.date); }) .entries(rawData);

[ {"key":"clothing, beauty, & fashion", "values": [{"year":"2004", "n":141, "date":"2004-01-01"}, {"year":"2005", "n":203, "date":"2005-01-01"},...] }, {"key":"computers & internet", "values": [{"year":"2004", "n":2489, "date":"2004-01-01"}, {"year":"2005", "n":2200, "date":"2005-01-01"},...] }, ... ]

var div = d3.select("#vis") .selectAll(".chart") .data(data); div.enter() .append("div") .attr("class", "chart") .append("svg").append("g");

var div = d3.select("#vis") .selectAll(".chart") .data(data); div.enter() .append("div") .attr("class", "chart") .append("svg").append("g");

var svg = div.select("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom); var g = svg .select("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

g.append("rect") .attr("class", "background") .style("pointer-events", "all") .attr("width", width + margin.right) .attr("height", height);

.chart { float: left; padding-right: 5px; padding-bottom: 5px; padding-top: 0; padding-left: 0; } .background { fill: black; }

var xScale = d3.time.scale().range([0, width]); var yScale = d3.scale.linear().range([height, 0]);

var line = d3.svg.line() .x(function(d) { return xScale(d.date); }) .y(function(d) { return yScale(d.n); }); var area = d3.svg.area() .x(function(d) { return xScale(d.date); }) .y0(height).y1(function(d) { return yScale(d.n); });

lines = g.append("g"); lines.append("path") .attr("class", "area") .attr("d", function(c) { return area(c.values); }); lines.append("path") .attr("class", "line") .attr("d", function(c) { return line(c.values); });

.area { fill: #cec6b9; } .line { fill: none; stroke: #74736c; stroke-width: 1.4px; }

Interactive

Scrubbable

g.append("rect") .attr("class", "background") .style("pointer-events", "all") .attr("width", width + margin.right) .attr("height", height) .on("mouseover", mouseover) .on("mousemove", mousemove) .on("mouseout", mouseout);

// hide for now circle = lines.append("circle") .attr("r", 2.2) .attr("opacity", 0)

var mouseover = function() { circle.attr("opacity", 1.0); return mousemove.call(this); };

var mouseout = function() { circle.attr("opacity", 0); return true; };

var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var date = xScale.invert(xpos); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = findIndex(c.values, date); return yScale(c.values[index].n); }); //... }

var format = d3.time.format("%Y"); var xScale = d3.time.scale() .domain([format.parse("2013"), format.parse("2014")]); .range([0, width])

var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var date = xScale.invert(xpos); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = findIndex(c.values, date); return yScale(c.values[index].n); }); //... }

var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var date = xScale.invert(xpos); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = findIndex(c.values, date); return yScale(c.values[index].n); }); //... }

// find location in 'array' with // date nearest to 'date' var findIndex = function(array, date) { var found = false; var index = 1; while (!found && index < array.length ) { if(array[index].date > date) { found = true; } else { index++; } } return index - 1; };

var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var date = xScale.invert(xpos); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = findIndex(c.values, date); return yScale(c.values[index].n); }); //... }

//create bisector // (binary search) var bisect = d3.bisector(function(d) { return d.date; }) .left;

//with bisector var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var date = xScale.invert(xpos); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = bisect(c.values, date, 0, c.values.length - 1); return yScale(c.values[index].n); }); //... }

[ {"key":"clothing, beauty, & fashion", "values": [{"year":"2004", "n":141, "date":"2004-01-01"}, {"year":"2005", "n":203, "date":"2005-01-01"},...] }, {"key":"computers & internet", "values": [{"year":"2004", "n":2489, "date":"2004-01-01"}, {"year":"2005", "n":2200, "date":"2005-01-01"},...] }, ... ]

var format = d3.time.format("%Y"); var mousemove = function() { // 'this' is current DOM element var xpos = d3.mouse(this)[0]; var year = xScale.invert(xpos).getFullYear(); var date = format.parse('' + year); var index = 0; circle .attr("cx", xScale(date)) .attr("cy", function(c) { index = bisect(c.values, date, 0, c.values.length - 1); return yScale(c.values[index].n); }); //... }

Sortable

var setupIsoytpe = function() { $("#vis").isotope({ itemSelector: '.chart', layoutMode: 'fitRows', getSortData: { } }); };

var setupIsoytpe = function() { $("#vis").isotope({ itemSelector: '.chart', layoutMode: 'fitRows', getSortData: { count: function(e) { var d = d3.select(e).datum(); var sum = d3.sum(d.values, function(d) { return d.n; }); return sum * -1; } } }); };

var setupIsoytpe = function() { $("#vis").isotope({ itemSelector: '.chart', layoutMode: 'fitRows', getSortData: { count: function(e) { var d = d3.select(e).datum(); var sum = d3.sum(d.values, function(d) { return d.n; }); return sum * -1; }, name: function(e) { var d = d3.select(e).datum(); return d.key; } } }); };

$("#vis").isotope({sortBy: 'count'});

d3.select("#button-wrap") .selectAll("div") .on("click", function() { var id = d3.select(this).attr("id"); $("#vis").isotope({sortBy: id}); // ... }); });