block by sxywu cbaeb50daa75aec136db89059b5a116a

Metis class 11: linked viz, #1

Full Screen

Data and inspiration from Matt Stiles‘s Four Decades of State Unemployment Rates, in Small Multiples published on The Daily Viz.

Built with blockbuilder.org

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <style>
    .axis path, .axis line {
      stroke: #ccc;
    }
    .axis line {
      stroke-dasharray: 2;
    }
    .axis text {
      fill: #ccc;
    }
    
    .state text {
      font-weight: 600;
    }
  </style>
</head>

<body>
  <script>
var width = 250;
var height = 200;
var margin = {top: 20, right: 30, bottom: 20, left: 30};
var xScale = d3.scaleLinear().range([0, width]);
var yScale = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom()
	.scale(xScale)
	.ticks(5)
	.tickFormat(d => "'" + new String(d).slice(2))
	.tickSizeInner(-height)
	.tickSizeOuter(0)
	.tickPadding(6);
var yAxis = d3.axisLeft()
	.scale(yScale)
	.ticks(5)
	.tickFormat(d => d + '%')
	.tickSizeInner(-width)
	.tickSizeOuter(0)
	.tickPadding(6);
var line = d3.line()
	.x(d => xScale(d.year))
	.y(d => yScale(d.value));
var red = '#d8472b';
    
d3.json('data.json', function(err, data) {
  // get the data ready, currently grouped by
  // year, so create a flat array with attributes
  // state, value, year
  var flatData = [];
	data.forEach(function(obj) {
    var year = +obj.date;
    // cannot use forEach on an obj, can only for-in
    // (good reason to use utility libraries like lodash)
    for (var state in obj) {
      if (state === 'date' || state === 'avg') continue;
      flatData.push({
        state: state,
        value: +obj[state],
        year: year,
      });
    }
  });
  
  // now that all the data is flat, create scales
  var xDomain = d3.extent(flatData, d => d.year);
  var yDomain = d3.extent(flatData, d => d.value);
  xScale.domain(xDomain);
  yScale.domain(yDomain).nice();
  
  // and now group the data by state
  var dataByState = d3.nest()
  	.key(function(d) {return d.state})
  	.entries(flatData);
  
  var states = d3.select('body').selectAll('.state')
  	.data(dataByState, d => d.key).enter().append('svg')
  	.classed('state', true)
  	.attr('width', width + margin.left + margin.right)
    .attr('height', height + margin.top + margin.bottom)
  	.append('g')
      .attr('transform', 'translate(' + [margin.left, margin.top] + ')');
  
  // axis
  states.append('g')
  	.classed('x axis', true)
  	.attr('transform', 'translate(' + [0, height] + ')')
  	.call(xAxis);
  states.append('g')
  	.classed('y axis', true)
  	.call(yAxis);
  
  // state title
  states.append('text')
  	.attr('x', 15)
  	.attr('y', 5)
  	.attr('dy', '1em')
  	.attr('font-size', 14)
  	.text(d => d.key.toUpperCase());
  
  // path
  states.append('path')
  	.datum(d => d.values)
  	.attr('d', line)
  	.attr('fill', 'none')
  	.attr('stroke', red)
  	.attr('stroke-width', 2);
  
  // circle & text for hover
  var hover = states.append('g')
  	.datum(d => {
      var last = d.values.slice(-1)[0];
      return {
        values: d.values,
        last: last,
      }
    }).attr('transform', d =>
            'translate(' + [xScale(d.last.year), yScale(d.last.value)] + ')');
  var circle = hover.append('circle')
  	.attr('fill', red)
  	.attr('r', 3);
  var text = hover.append('text')
  	.attr('text-anchor', 'middle')
  	.attr('y', -10)
  	.style('font-size', 12)
  	.text(d => "'" + new String(d.last.year).slice(2) +
          ': ' + d3.format('.1f')(d.last.value) + '%')
  	
});
  </script>
</body>