index.html
<!DOCTYPE html>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 12px;
}
path.county {
fill: #bdbdbd;
}
path.link {
fill: none;
}
path.link:hover {
stroke-opacity: 1;
}
.county-borders {
fill: none;
stroke: #fff;
stroke-width: 0.5px;
stroke-linejoin: round;
stroke-linecap: round;
pointer-events: none;
}
</style>
<svg width="960" height="600"></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://d3js.org/topojson.v2.min.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script>
var svg = d3.select("svg");
var path = d3.geoPath();
function ready(error, us, migration) {
if (error) throw error;
migration.forEach(function(d) { d.movers = +d.movers; });
var color = d3.scaleQuantize()
.domain([0, d3.max(migration, function(d) { return d.movers; })])
.range(d3.schemeBuPu[6]);
us.objects.counties.geometries.forEach(function(d) {
var match = migration.filter(function(m) { return m.FIPS === d.id; });
d.properties = {movers: 0};
if (match.length) d.properties = match[0];
});
var cookCentroid = path.centroid(
topojson.feature(us, us.objects.counties.geometries.filter(
function(d) { return d.id === "17031"; }
)[0])
);
function link(d) {
c = path.centroid(d);
return "M" + c[0] + "," + c[1]
+ "C" + (c[0] + cookCentroid[0]) / 2 + "," + c[1]
+ " " + (c[0] + cookCentroid[0]) / 2 + "," + cookCentroid[1]
+ " " + cookCentroid[0] + "," + cookCentroid[1];
}
var line = d3.line()
.curve(d3.curveBasis)
.x(function(d) { return d[0]; })
.y(function(d) { return d[1]; });
var features = topojson.feature(us, us.objects.counties).features;
var g = svg.append("g").attr("class", "counties");
var strokeScale = d3.scaleLinear()
.range([0, 50])
.domain(d3.extent(features, function(d) { return d.properties.movers; }));
g.selectAll("path.county")
.data(features).enter()
.append("path")
.attr("d", path)
.attr("class", "county");
g.append("path")
.attr("class", "county-borders")
.attr("d", path(topojson.mesh(us, us.objects.counties, function(a, b) { return a !== b; })));
var legend = g.append("g")
.attr("class", "legend")
.attr("transform", "translate(700,500)");
legend.append("rect")
.attr("width", 225)
.attr("height", 100)
.attr("fill", "#9ecae1")
.style("opacity", 0.5);
legend.append("text")
.attr("x", 10)
.attr("y", 20)
.attr("font-weight", "bold")
.text("Cook County Migration");
legend.append("text")
.attr("x", 10)
.attr("y", 50)
.attr("id", "legendCounty")
.text("Hover over a link");
legend.append("text")
.attr("x", 10)
.attr("y", 70)
.attr("id", "legendCount")
.text("");
legend.append("text")
.attr("x", 10)
.attr("y", 85)
.attr("id", "cook")
.text("");
var linkFeatures = features.filter(function(d) { return d.properties.movers > 1000; });
g.selectAll("path.link")
.data(linkFeatures)
.enter().append("path")
.attr("d", link)
.attr("class", "link")
.attr("stroke", function(d) { return color(d.properties.movers); })
.attr("stroke-width", function(d) { return strokeScale(d.properties.movers); })
.attr("stroke-opacity", 0.6)
.on("mouseover", function(d) {
d3.select("#legendCounty").text(d.properties.county + ", " + d.properties.state);
d3.select("#legendCount").text(d3.format(",")(d.properties.movers) + " moved from");
d3.select("#cook").text("Cook County");
})
.on("mouseout", function(d) {
d3.select("#legendCounty").text("Hover over a link");
d3.select("#legendCount").text("");
d3.select("#cook").text("");
});
}
(function() {
d3.queue()
.defer(d3.json, "https://d3js.org/us-10m.v1.json")
.defer(d3.csv, "cook_county_out_migration.csv")
.await(ready)
})()
</script>