block by armollica 6314f45890bcaaa45c808b5d2b0c602f

Small Multiple Choropleth Map

Full Screen

Small multiples choropleth map. Color shows the annual percent change in the number of manufacturing jobs. Red is a decline, blue is growth.

Three recessions are covered in this timespan: July 1990 to March 1991, March 2001 to November 2001, and December 2007 to June 2009 (see NBER’s business cycle dates).

In a real world situation you wouldn’t want to have the user’s browser do all the computation needed to draw these maps. The maps are rendered in <canvas> so it would be fairly straightforward to pre-render them as images and to serve those instead.

Source: Bureau of Labor Statistics’ QCEW program

index.html

<html>
<head>
<style>

html {
  font-family: monospace;
}

th, .row-header {
  font-size: 20px;
  font-weight: normal;
}

.row-header {
  padding: 20px;
}

</style>
</head>
<body>

<h4 class="loading-text">
  Loading... (This takes a long time. Has to render 300,000 tiny polygons. 
  This would be pre-rendered in a normal situation)
</h4>
  
<script src="//d3js.org/d3.v3.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="https://d3js.org/d3-queue.v2.min.js"></script>
<script>

var queue = d3_queue.queue();

var width = 210,
    height = 125;

var colorScale = d3.scale.threshold()
  .domain([-0.2, -0.1, -0.025, 0.025, 0.1, 0.2])
  .range(["#b2182b", "#ef8a62", "#fddbc7", "#f7f7f7", "#d1e5f0", "#67a9cf", "#2166ac"]);

var projection = d3.geo.albersUsa()
  .scale(250)
  .translate([width/2, height/2]);
  
var path = d3.geo.path()
  .projection(projection);

var table = d3.select("body").append("table");

queue
  .defer(d3.json, "us.json")
  .defer(d3.json, "manufacturing.json")
  .await(ready);

function ready(error, us, manufacturing) {
  if (error) throw error;
  
  var nested = d3.nest()
    .key(function(d) { return d.year; })
    .key(function(d) { return d.qtr; })
    .rollup(function(d) { return d3.map(d, function(d) { return d.fips; })})
    .entries(manufacturing);
 
  var counties = topojson.feature(us, us.objects.counties);
  
  counties.features
    .forEach(function(feature) {
      feature.properties.centroid = path.centroid(feature);
    });
  
  table.append("thead").selectAll("th")
      .data(["", "Q1", "Q2", "Q3", "Q4"])
    .enter().append("th")
      .text(function(d) { return d; });
    
  var tr = table.selectAll("tr")
      .data(nested, function(d) { return d.key; })
    .enter().append("tr");
    
  tr.append("td")
    .attr("class", "row-header")
    .text(function(d) { return d.key; });

  var td = tr.selectAll(".map").data(function(d) { return d.values; })
    .enter().append("td")
      .attr("class", "map");
  
  var canvases = td.append("canvas")
    .attr("width", width)
    .attr("height", height)
    .each(render);

  d3.select(".loading-text").remove();
  
  function render(d) {
    var data = d.values;
    
    var context = d3.select(this).node().getContext("2d");
    
    var color = function(d) {
      if (data.has(d.id)) {
        var value = data.get(d.id).pct_change_emp;
        return value ? colorScale(value) : "#fff";
      }
      return "#fff";
    };

    // Want the maps to render sequentially. Use setTimeout to give the
    // browser a break in between drawing each map.
    window.setTimeout(function() {      
      drawMap(context, color);
    }, 500);
  }

  function drawMap(context, color) {
    path.context(context);
    context.strokeStyle = "#fff";
    context.lineWidth = 0.1;
    counties.features.forEach(function(d) {
      context.beginPath()
      path(d);
      context.fillStyle = color(d);
      context.fill();
      context.stroke();
    });
  }
}

</script>
</body>
</html>