|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/topojson.v2.min.js"></script> |
|
<script src="https://unpkg.com/[email protected]"></script> |
|
<script src="london-grid.js"></script> |
|
<style> |
|
body { |
|
margin: 0; |
|
} |
|
|
|
.constituency-label { |
|
font-family: monospace; |
|
pointer-events: none; |
|
} |
|
|
|
.constituency { |
|
fill: white; |
|
fill-opacity: 0; |
|
stroke: black; |
|
} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<script> |
|
|
|
var cfg = { |
|
gridLength: 11, |
|
gridHeight: 10, |
|
padding: 10 |
|
} |
|
|
|
var margin = {top: 50, right: 200, bottom: 50, left: 200}; |
|
|
|
var width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
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 + ")"); |
|
|
|
var rectWidth = (width / cfg.gridLength) - cfg.padding, |
|
rectHeight = (height / cfg.gridHeight) - cfg.padding; |
|
|
|
var url = "topo_wpc_london.json"; |
|
|
|
function calculateCoords(d) { |
|
var x = d.x * (rectWidth + cfg.padding); |
|
var y = d.y * (rectHeight + cfg.padding); |
|
return [x, y]; |
|
} |
|
|
|
var londonGridLookup = {}; |
|
londonGrid.forEach(function(d) { |
|
londonGridLookup[d.ons_code] = d; |
|
}); |
|
|
|
var constituencies = svg.append("g").attr("class", "constituencies"); |
|
|
|
var geoLondonExtent; |
|
|
|
d3.json(url, function(topoLondon) { |
|
|
|
var geoLondon = topojson.feature(topoLondon, topoLondon.objects.wpc).features; |
|
|
|
geoLondonExtent = topojson.feature(topoLondon, topoLondon.objects.wpc); |
|
|
|
var rectangles = constituencies.selectAll("path") |
|
.data(geoLondon) |
|
.enter().append("path") |
|
.attr("class", "constituency"); |
|
|
|
rectangles |
|
.attr("d", d => d.rectangle = rectPath(londonGridLookup[d.id].position)) |
|
.on("mouseover", function(d) { |
|
d.geoArea = localProjection(d, londonGridLookup[d.id].position); |
|
|
|
d3.select(this) |
|
.raise() |
|
.transition() |
|
.attrTween("d", () => flubber.interpolate(d.rectangle, d.geoArea)) |
|
}) |
|
.on("mouseout", function(d) { |
|
d3.select(this) |
|
.transition() |
|
.attrTween("d", () => flubber.interpolate(d.geoArea, d.rectangle)); |
|
}) |
|
|
|
var text = constituencies.selectAll("text") |
|
.data(geoLondon) |
|
.enter().append("text") |
|
.attr("class", "constituency-label") |
|
.attr("x", d => calculateCoords(londonGridLookup[d.id].position)[0] + rectWidth / 2) |
|
.attr("y", d => calculateCoords(londonGridLookup[d.id].position)[1] + rectHeight / 2) |
|
.attr("dy", rectHeight / 8) |
|
.attr("text-anchor", "middle") |
|
.text(d => d.properties.PCON13NM.slice(0, 2)); |
|
|
|
}); |
|
|
|
function localProjection(geometry, position) { |
|
var centroid = d3.geoPath().centroid(geometry); |
|
|
|
var projection = d3.geoAlbers() |
|
.center(centroid) |
|
.rotate([0, 0]) |
|
.fitExtent([[0, 0], [width, height]], geoLondonExtent); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
var projectedCentroid = path.centroid(geometry); |
|
|
|
var x = calculateCoords(position)[0] + rectWidth / 2; |
|
var y = calculateCoords(position)[1] + rectHeight / 2; |
|
|
|
projection.translate([x, y]) |
|
|
|
return path(geometry); |
|
} |
|
|
|
function rectPath(position) { |
|
var x1 = calculateCoords(position)[0], |
|
y1 = calculateCoords(position)[1], |
|
x2 = calculateCoords(position)[0] + rectWidth, |
|
y2 = calculateCoords(position)[1] + rectHeight; |
|
|
|
var coord1 = [x1, y1], |
|
coord2 = [x2, y1], |
|
coord3 = [x2, y2], |
|
coord4 = [x1, y2], |
|
coord5 = [x1, y1]; |
|
|
|
var coordinates = [coord1, coord2, coord3, coord4, coord5]; |
|
|
|
var pathGen = d3.geoPath().projection(null); |
|
var rectangle = pathGen({ |
|
type:"Polygon", |
|
coordinates: [coordinates] |
|
}); |
|
|
|
return rectangle; |
|
} |
|
|
|
</script> |
|
</body> |