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
<!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>