block by sxywu 2a7a264ba0ea1f6aef75a8db5c02443a

Metis class 11: linked viz, #2

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

forked from sxywu‘s block: Metis class 11: linked viz, #1

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 dispatch = d3.dispatch('hover');
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
  var title = states.append('text')
  	.attr('x', 15)
  	.attr('y', 5)
  	.attr('dy', '1em')
  	.attr('font-size', 14);
  
  // 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');
  hover.append('circle')
  	.attr('fill', red)
  	.attr('r', 3);
  hover.append('text')
    .attr('text-anchor', 'middle')
  	.attr('y', -10)
  	.style('font-size', 12);
  
  // append the rect for hover
  states.append('rect')
  	.attr('width', width)
  	.attr('height', height)
  	.attr('opacity', 0)
  	.on('mousemove', mouseover);
  
  function mouseover(d) {
    var [x, y] = d3.mouse(this);
    var year = Math.round(xScale.invert(x));
    
    dispatch.call('hover', this, year);
  }
 
 	dispatch.on('hover.dot', function(year) {
    hover.datum(d => {
      var y = d.values.find(d => d.year === year);
      return {
        last: y,
        values: d.values,
      }
    }).attr('transform', d =>
            'translate(' + [xScale(d.last.year), yScale(d.last.value)] + ')');
    
    hover.select('text')
      .text(d => "'" + new String(d.last.year).slice(2) +
          ' (' + d3.format('.1f')(d.last.value) + '%)');
  });
  
  dispatch.on('hover.title', function(year) {
    title.text(d => {
      var y = d.values.find(d => d.year === year);
    	return d.key.toUpperCase() +
        ' (' + d3.format('.1f')(y.value) + '%)'
  	});
  });
  
  // finally set the hover for the last value
  dispatch.call('hover', this, 2016);
  
});
  </script>
</body>