This is a combination of choropleth mapping and gaussian blurs on a threshold scale to visualize US county unemployment rates.
A lot of heavy borrowing for the choropleth code, dataset, and zoom code came from these:
Choropleth (Mike Bostock's Block 4060606)
Zoom to Bounding Box (Mike Bostock's Block 4699541)
Data sources: Bureau of Labor Statistics, Census Bureau
xxxxxxxxxx
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=yes" />
</head>
<body>
<svg width="960" height="600" stroke-linejoin="round" stroke-linecap="round">
<defs></defs>
<rect></rect>
<g class="counties"></g>
<g class="states"></g>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://unpkg.com/topojson-client@3.0.0"></script>
<script>
var svg = d3.select('svg');
// background invisible rect to help with resetting transformations
d3.select('rect')
.attr('width', svg.attr('width'))
.attr('height', svg.attr('height'))
.style('fill', 'none')
.style('pointer-events', 'all')
.on('click', reset);
var gCounties = d3.select('g.counties'),
gStates = d3.select('g.states'),
defs = svg.select('defs'),
activeState = d3.select(null),
originalStrokeWidth = 0.5,
transitionDuration = 750,
unemployment = d3.map(),
path = d3.geoPath();
var colorScale = d3.scaleThreshold()
.domain(d3.range(2, 10)) // input bounds
.range(d3.schemeRdPu[9]); // output bounds
var blurScale = d3.scaleThreshold()
.domain(d3.range(1, 10)) // input bounds
.range(d3.range(0, 4.5, 0.5).reverse()); // output bounds
d3.queue()
.defer(d3.json, 'https://unpkg.com/us-atlas@1/us/10m.json')
.defer(d3.tsv, 'unemployment.tsv', function(d) {
unemployment.set(d.id, +d.rate);
})
.await(ready);
function ready(error, us) {
if (error) throw error;
// generate filter defs for every unique range value in the blurScale
defs.selectAll('filter')
.data(blurScale.range())
.enter().append('filter')
.attr('id', function(blurRangeValue) {
return 'blur_' + blurRangeValue;
})
.append('feGaussianBlur')
.attr('stdDeviation', function(blurRangeValue) {
return blurRangeValue;
});
gCounties.selectAll('path')
.data(topojson.feature(us, us.objects.counties).features)
.enter().append('path')
.style('stroke', 'black')
.style('stroke-width', originalStrokeWidth)
.style('fill', function(countyGeoJson) {
countyGeoJson.rate = unemployment.get(countyGeoJson.id);
return colorScale(countyGeoJson.rate);
})
.style('filter', function(d) {
return 'url(#blur_' + blurScale(d.rate) + ')';
})
.style('cursor', 'pointer')
.attr('d', path)
.on('click', reset)
.append('title')
.text(function(countyGeoJson) {
return countyGeoJson.rate + '% unemployment';
});
gStates.selectAll('path')
.data(topojson.feature(us, us.objects.states).features)
.enter().append('path')
.style('stroke', 'transparent')
.style('fill', 'transparent')
.style('cursor', 'pointer')
.attr('d', path)
.on('click', clicked);
}
function clicked(stateGeoJson) {
if (activeState.node() === this) {
return reset();
}
activeState.classed('active', false);
activeState = d3.select(this).classed('active', true);
var width = svg.attr('width'),
height = svg.attr('height'),
bounds = path.bounds(stateGeoJson),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = .9 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
gCounties
.transition()
.duration(transitionDuration)
.attr('transform', 'translate(' + translate + ')scale(' + scale + ')')
.selectAll('path')
.style('stroke-width', originalStrokeWidth / scale + 'px')
gStates
.attr('transform', 'translate(' + translate + ')scale(' + scale + ')')
.selectAll('path')
// set to none to allow for county-level tooltips
.style('stroke', 'none')
.style('fill', 'none');
}
function reset() {
activeState.classed('active', false);
activeState = d3.select(null);
gCounties
.transition()
.duration(transitionDuration)
.attr('transform', '')
.selectAll('path')
.style('stroke-width', originalStrokeWidth + 'px');
gStates
.attr('transform', '')
.selectAll('path')
// set to transparent to allow for clicks at the continental view
.style('stroke', 'transparent')
.style('fill', 'transparent');
}
</script>
</body>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js
https://unpkg.com/topojson-client@3.0.0