Driving deaths and the rate at which an alcohol-impaired driver is involved in Wisconsin counties.
Uses the d3.forceChart() plugin to create the cartogram layout. Cartogram style inspired by this and this block. Data from County Health Rankings & Roadmaps.
xxxxxxxxxx
<html>
<head>
<style>
body {
font: 14px sans-serif;
}
.cartogram text {
pointer-events: none;
text-shadow:
-1px -1px 0.5px #fff,
1px -1px 0.5px #fff,
-1px 1px 0.5px #fff,
1px 1px 0.5px #fff;
}
</style>
</head>
<body>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="force-chart.js"></script>
<script>
var width = 960,
height = 500;
var area = function(d) { return d.driving_deaths; },
color = function(d) { return d.pct_alcohol_driving_deaths; };
var areaScale = d3.scale.linear().range([300, 3500]),
colorScale = d3.scale.threshold()
.domain([30, 40, 50, 60])
.range(["#F8C8AB","#E08D74","#B75947","#852D23","#4C0A08"]);
var rValue = function(d) { return Math.sqrt(areaScale(area(d))/ Math.PI); },
colorValue = function(d) { return colorScale(color(d)); };
var cartogram = d3.forceChart()
.size([width, height])
.padding(4)
.shape("square")
.draggable(false)
.x(function(d) { return d.centroid[0]; })
.y(function(d) { return d.centroid[1]; })
.r(rValue) // In order to make for a nicer layout...
.rStart(4) // start squares out small so there are no collisions
.rGravity(10); // and have radius grow quickly
var projection = d3.geo.conicConformal()
.rotate([90, 0])
.center([1.5, 44.4])
.parallels([29.5, 45.5])
.scale(6000)
.translate([width/2, height/2])
.precision(0.1);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
queue()
.defer(d3.json, "wi.json")
.defer(d3.json, "wi-alcohol.json")
.await(ready);
function ready(error, wi, alcohol) {
if (error) throw error;
// Merge county geography with alcohol-related data
var countyData = topojson.feature(wi, wi.objects.wi).features
.map(function(feature) {
// Get the alcohol-related data for the county
var fips = +feature.properties.GEOID;
var d = alcohol
.filter(function(d) {
return d.fips === fips;
})[0];
// Get the centroid for the county
d.centroid = path.centroid(feature);
return d;
});
areaScale.domain([0, d3.max(countyData, area)]);
// Draw legends
svg.append("g").call(legend)
.attr("class", "legend");
// Draw cartogram
svg.append("g").call(cartogram, countyData)
.attr("class", "cartogram")
.selectAll(".node").append("rect")
.attr("x", function(d) { return -d.r0; })
.attr("y", function(d) { return -d.r0; })
.attr("width", function(d) { return 2*d.r0; })
.attr("height", function(d) { return 2*d.r0; })
.attr("fill", colorValue)
.on("mouseenter", drawTooltip)
.on("mouseleave", removeTooltip);
}
function drawTooltip(d) {
var map = d3.select(".cartogram");
map.append("text")
.attr("x", d.x)
.attr("y", d.y)
.attr("dy", -d.r - 3)
.style("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "bold")
.text(d.county)
map.append("text")
.attr("x", d.x)
.attr("y", d.y)
.attr("dy", 3)
.style("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text(d.pct_alcohol_driving_deaths + "%");
}
function removeTooltip() {
d3.selectAll(".cartogram text").remove();
}
function legend(selection) {
var legendData = {
size: [
{ driving_deaths: 100, text: "100 driving deaths", dy: 0 },
{ driving_deaths: 50, text: "50 deaths", dy: 45 },
{ driving_deaths: 10, text: "10 deaths", dy: 80 }
],
color: [
{ pct_alcohol_driving_deaths: 60, text: "60% alcohol-impaired", dy: 0 },
{ pct_alcohol_driving_deaths: 50, text: "50%", dy: 20 },
{ pct_alcohol_driving_deaths: 40, text: "40%", dy: 40 },
{ pct_alcohol_driving_deaths: 30, text: "30%", dy: 60 },
{ pct_alcohol_driving_deaths: 20, text: "", dy: 80 }
]
};
// Size legend
selection.append("g")
.attr("class", "legend size")
.attr("transform", "translate(" + 3*width/4 + "," + height/4 + ")")
.selectAll(".item").data(legendData.size)
.enter().append("g")
.attr("class", "item")
.attr("transform", function(d) { return "translate(0," + d.dy + ")"; })
.each(function(d) {
var r = rValue(d);
d3.select(this).append("rect")
.attr("x", -r)
.attr("y", -r)
.attr("width", 2*r)
.attr("height", 2*r)
.style("fill", "none")
.style("stroke", "slategrey")
.style("stroke-width", 1.5);
d3.select(this).append("text")
.attr("dx", 30)
.attr("dy", 5)
.text(d.text);
});
// Color legend
selection.append("g")
.attr("class", "legend color")
.attr("transform", "translate(" + 3*width/4 + "," + height/2 + ")")
.selectAll(".item").data(legendData.color)
.enter().append("g")
.attr("class", "item")
.attr("transform", function(d) { return "translate(0," + d.dy + ")"; })
.each(function(d) {
d3.select(this).append("rect")
.attr("x", -10)
.attr("y", -10)
.attr("width", 20)
.attr("height", 20)
.style("fill", colorValue);
d3.select(this).append("text")
.attr("dx", 18)
.attr("dy", 15)
.text(d.text);
d3.select(this).append("line")
.attr("x1", 10)
.attr("x2", 14)
.attr("y1", 10)
.attr("y2", 10)
.style("stroke", "slategrey")
.style("stroke-width", d.text == "" ? 0 : 1.5);
});
}
</script>
</body>
</html>
https://d3js.org/d3.v3.min.js
https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.20/topojson.min.js
https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js