|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<title>Demers Cartogram</title> |
|
<style> |
|
body { |
|
font-family: "Avenir", sans-serif; |
|
} |
|
|
|
.states { |
|
fill: #b8b8b8; |
|
stroke: #fff; |
|
} |
|
|
|
.states.hidden { |
|
fill: none; |
|
stroke: none; |
|
} |
|
|
|
rect { |
|
/*fill: steelblue;*/ |
|
fill-opacity: .8; |
|
stroke: #000; |
|
stroke-width: 0px; |
|
} |
|
|
|
text.maptitle { |
|
text-anchor: middle; |
|
alignment-baseline: central; |
|
font-size: 36px; |
|
} |
|
|
|
text.legendtitle { |
|
font-weight: bold; |
|
} |
|
|
|
.node text { |
|
text-anchor: middle; |
|
alignment-baseline: central; |
|
fill: #fff; |
|
} |
|
|
|
form { |
|
position: absolute; |
|
right: 10px; |
|
top: 50px; |
|
font-family: sans-serif; |
|
} |
|
|
|
</style> |
|
<body> |
|
<form> |
|
<label><input type="radio" name="mode" value="mccann"> McCann</label> |
|
<label><input type="radio" name="mode" value="demers" checked> Demers</label> |
|
<label><input type="radio" name="mode" value="dorling"> Dorling</label> |
|
<!--label><input type="radio" name="election" value="2008" > 2008</label> |
|
<label><input type="radio" name="election" value="2012" checked> 2012</label--> |
|
<label><input type="checkbox" name="showmap" value="dorling" checked> Show Map</label> |
|
</form> |
|
<script src="//d3js.org/d3.v3.min.js"></script> |
|
<script src="//d3js.org/topojson.v1.min.js"></script> |
|
<script src="//d3js.org/queue.v1.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.10.0/d3-legend.min.js"></script> |
|
<script id="grid" type="text/plain"> |
|
AK ME |
|
VT NH |
|
WA ID MT ND MN IL WI MI NY RI MA |
|
OR NV WY SD IA IN OH PA NJ CT DE |
|
CA UT CO NE MO KY WV VA MD |
|
AZ NM KS AR TN NC SC DC |
|
OK LA MS AL GA |
|
HI TX FL PR |
|
</script> |
|
<script> |
|
//console.clear() |
|
var margin = {top: 50, right: 0, bottom: 20, left: 0}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom, |
|
padding = 3; |
|
|
|
var projection = d3.geo.albersUsa(); |
|
|
|
var radius = d3.scale.sqrt() |
|
.range([0, 50]); |
|
|
|
var force = d3.layout.force() |
|
.charge(0) |
|
.gravity(0) |
|
.size([width, height]); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")" + |
|
"scale(" + Math.min(width / 960, height / 500) + ")"); |
|
|
|
var path = d3.geo.path(); |
|
|
|
var grid = {}; |
|
d3.select("#grid").text().split("\n").forEach(function(line, i) { |
|
var re = /\w+/g, m; |
|
while (m = re.exec(line)) { |
|
grid[m[0]] = [m.index / 3, i] |
|
} |
|
}); |
|
|
|
var gridWidth = d3.max(d3.values(grid), function(d) { return d[0]; }) + 1, |
|
gridHeight = d3.max(d3.values(grid), function(d) { return d[1]; }) + 1, |
|
cellSize = 55, |
|
cellPadding = 5; |
|
|
|
var title = svg.append("text") |
|
.attr("class", "maptitle") |
|
.attr("x", width / 2) |
|
.attr("y", -(margin.top / 2)) |
|
.text("2012 Presidental Election Results"); |
|
|
|
var colorScale = d3.scale.linear() |
|
.domain([1, 0]) |
|
.range(["steelblue", "crimson"]); |
|
|
|
svg.append("g") |
|
.attr("class", "legendLinear") |
|
.attr("transform", "translate(" + (width - 150) + "," + (height - 100) + ")"); |
|
|
|
queue() |
|
.defer(d3.csv, "data.csv") |
|
.defer(d3.json, "us-state-centroids.json") |
|
.defer(d3.json, "us.json") |
|
.await(ready); |
|
|
|
function ready(error, data, centroids, us) { |
|
if (error) throw error; |
|
//console.table(data); |
|
|
|
var dataById = d3.map(data, function(d) { return +d.id; }); |
|
|
|
var totalD = d3.sum(data, function(d) { return +d["2012D"]; }), |
|
totalR = d3.sum(data, function(d) { return +d["2012R"]; }) |
|
|
|
var legendLinear = d3.legend.color() |
|
.shapeWidth(30) |
|
.cells(2) |
|
.title("Candidate (electoral votes)") |
|
.labels(["Obama (" + totalD + " votes)", |
|
"Romney (" + totalR + " votes)"]) |
|
.scale(colorScale); |
|
|
|
svg.select(".legendLinear") |
|
.call(legendLinear); |
|
|
|
svg.append("path") |
|
.attr("class", "states visible") |
|
.datum(topojson.feature(us, us.objects.states)) |
|
.attr("d", path); |
|
|
|
radius.domain([0, d3.max(data, function(d){ return +d["2012Total"];} )]); |
|
|
|
//console.log(dataById); |
|
var nodes = centroids.features |
|
.filter(function(d) { return dataById.has(+d.id); }) |
|
.map(function(d) { |
|
var stateData = dataById.get(+d.id), |
|
state = stateData.code, |
|
cell = grid[state], |
|
point = projection(d.geometry.coordinates), |
|
votes = stateData["2012Total"], |
|
marginD = +stateData["2012D"] / +stateData["2012Total"], |
|
color = colorScale(marginD); |
|
|
|
return { |
|
id: +d.id, |
|
x: point[0], y: point[1], |
|
x0: point[0], y0: point[1], |
|
xx: cell[0] * cellSize + 200, yy: cell[1] * cellSize - (cellSize / 2), |
|
r: radius(votes), |
|
r0: radius(votes), |
|
value: votes, |
|
state: state, |
|
color: color |
|
}; |
|
}); |
|
|
|
//console.log(nodes); |
|
|
|
force |
|
.nodes(nodes) |
|
.on("tick", tickDemers) |
|
.start(); |
|
|
|
var node = svg.selectAll("g.node") |
|
.data(nodes) |
|
|
|
var nodeEnter = node.enter().append("g") |
|
.attr("class", "node"); |
|
|
|
nodeEnter.append("rect") |
|
.attr("width", function(d) { return d.r * 2; }) |
|
.attr("height", function(d) { return d.r * 2; }) |
|
.style("fill", function(d) { return d.color; }) |
|
.attr("rx", function(d) { return 0; }) |
|
.attr("title", function(d) { return d.state + ": " + d.value + " electoral college votes"}) |
|
.on("click", function(d,i){ |
|
console.log("d[" + d.id + "]", d.state, d.value) |
|
}); |
|
|
|
nodeEnter.append("text") |
|
.attr("x", function (d) { return d.r; }) |
|
.attr("y", function (d) { return d.r; }) |
|
.style("font-size", function(d) { return (1.5 * d.r - 5); }) |
|
.text( function (d) { return d.state; }); |
|
|
|
function tickDemers(e) { |
|
node.each(gravity(e.alpha * .1)) |
|
.each(collideDemers(.5)) |
|
.attr("transform", function(d) { |
|
return "translate(" + (d.x - d.r) + "," + (d.y - d.r) + ")"; |
|
}); |
|
} |
|
|
|
function tickDorling(e) { |
|
node.each(gravity(e.alpha * .1)) |
|
.each(collideDorling(.5)) |
|
.attr("transform", function(d) { |
|
return "translate(" + (d.x - d.r) + "," + (d.y - d.r) + ")"; |
|
}); |
|
} |
|
|
|
function gravity(k) { |
|
return function(d) { |
|
d.x += (d.x0 - d.x) * k; |
|
d.y += (d.y0 - d.y) * k; |
|
}; |
|
} |
|
|
|
function collideDemers(k) { |
|
var q = d3.geom.quadtree(nodes); |
|
return function(node) { |
|
var nr = node.r + padding, |
|
nx1 = node.x - nr, |
|
nx2 = node.x + nr, |
|
ny1 = node.y - nr, |
|
ny2 = node.y + nr; |
|
q.visit(function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
lx = Math.abs(x), |
|
ly = Math.abs(y), |
|
r = nr + quad.point.r; |
|
if (lx < r && ly < r) { |
|
if (lx > ly) { |
|
lx = (lx - r) * (x < 0 ? -k : k); |
|
node.x -= lx; |
|
quad.point.x += lx; |
|
} else { |
|
ly = (ly - r) * (y < 0 ? -k : k); |
|
node.y -= ly; |
|
quad.point.y += ly; |
|
} |
|
} |
|
} |
|
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; |
|
}); |
|
}; |
|
} |
|
|
|
function collideDorling(k) { |
|
var q = d3.geom.quadtree(nodes); |
|
return function(node) { |
|
var nr = node.r + padding, |
|
nx1 = node.x - nr, |
|
nx2 = node.x + nr, |
|
ny1 = node.y - nr, |
|
ny2 = node.y + nr; |
|
q.visit(function(quad, x1, y1, x2, y2) { |
|
if (quad.point && (quad.point !== node)) { |
|
var x = node.x - quad.point.x, |
|
y = node.y - quad.point.y, |
|
l = x * x + y * y, |
|
r = nr + quad.point.r; |
|
if (l < r * r) { |
|
l = ((l = Math.sqrt(l)) - r) / l * k; |
|
node.x -= x *= l; |
|
node.y -= y *= l; |
|
quad.point.x += x; |
|
quad.point.y += y; |
|
} |
|
} |
|
return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1; |
|
}); |
|
}; |
|
} |
|
|
|
d3.selectAll("input[type='radio']") |
|
.on("change", function change() { |
|
if(this.value === "mccann"){ |
|
force.stop(); |
|
node.transition().duration(1000) |
|
.attr("transform", function(d) { |
|
return "translate(" + d.xx + "," + d.yy + ")"; |
|
}); |
|
|
|
node.selectAll("rect").transition().duration(1000) |
|
.attr("rx", function(d){ return 0; }) |
|
.attr("width", cellSize - cellPadding) |
|
.attr("height", cellSize - cellPadding); |
|
|
|
node.selectAll("text").transition().duration(1000) |
|
.attr("x", (cellSize - cellPadding) / 2) |
|
.attr("y", (cellSize - cellPadding) / 2) |
|
.style("font-size", 20); |
|
|
|
//force.on("tick", tickMcCann).resume() |
|
|
|
} else if(this.value === "demers"){ |
|
node.transition().duration(1000) |
|
.attr("transform", function(d) { |
|
return "translate(" + (d.x - d.r) + "," + (d.y - d.r) + ")"; |
|
}) |
|
.each("end", function(){ |
|
force.on("tick", tickDemers).resume(); |
|
}); |
|
|
|
node.selectAll("rect").transition().duration(1000) |
|
.attr("rx", function(d){ return 0; }) |
|
.attr("width", function(d){ return d.r * 2; }) |
|
.attr("height", function(d){ return d.r * 2; }) |
|
|
|
node.selectAll("text").transition().duration(1000) |
|
.attr("x", function (d) { return d.r; }) |
|
.attr("y", function (d) { return d.r; }) |
|
.style("font-size", function(d) { return (1.5 * d.r - 5); }); |
|
|
|
} else { |
|
node.transition().duration(1000) |
|
.attr("transform", function(d) { |
|
return "translate(" + (d.x - d.r) + "," + (d.y - d.r) + ")"; |
|
}) |
|
.each("end", function(){ |
|
force.on("tick", tickDorling).resume(); |
|
}); |
|
|
|
node.selectAll("rect").transition().duration(1000) |
|
.attr("rx", function(d){ return d.r; }) |
|
.attr("width", function(d){ return d.r * 2; }) |
|
.attr("height", function(d){ return d.r * 2; }) |
|
|
|
node.selectAll("text").transition().duration(1000) |
|
.attr("x", function (d) { return d.r; }) |
|
.attr("y", function (d) { return d.r; }) |
|
.style("font-size", function(d) { return (1.5 * d.r - 5); }); |
|
}; |
|
} |
|
); |
|
|
|
d3.selectAll("input[type='checkbox']") |
|
.on("change", function change() { |
|
if(d3.select(this).property("checked") === true){ |
|
console.log("Show Map") |
|
d3.selectAll(".states") |
|
.classed("hidden", false) |
|
} else { |
|
console.log("Hide Map") |
|
d3.selectAll(".states") |
|
.classed("hidden", true) |
|
}; |
|
} |
|
); |
|
}; |
|
|
|
</script> |