Skip to content

Instantly share code, notes, and snippets.

@tlfrd
Last active August 15, 2017 11:40
Show Gist options
  • Save tlfrd/cddf303325e859aa399429ffe8ee21cd to your computer and use it in GitHub Desktop.
Save tlfrd/cddf303325e859aa399429ffe8ee21cd to your computer and use it in GitHub Desktop.
London Grid Rect2Geo
license: mit
height: 500

An interactive grid map of London. Hovering over each constituency rectangle transforms the geometry into the constituency area (using Flubber for smooth animation) which have been translated to their position in the grid map.

forked from tlfrd's blocks: London Constituency Grid Layout, London Grid with Geo Shapes


TODO:

  • Fix Flubber issue where constituencies with multiple areas do not animate correctly
<!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>
var londonGrid = [
{
"ons_code": "E14000692",
"name": "Enfield Southgate",
"position": {
"x": 5,
"y": 0
}
},
{
"ons_code": "E14000691",
"name": "Enfield North",
"position": {
"x": 6,
"y": 0
}
},
{
"ons_code": "E14000731",
"name": "Harrow East",
"position": {
"x": 2,
"y": 1
}
},
{
"ons_code": "E14000741",
"name": "Hendon",
"position": {
"x": 3,
"y": 1
}
},
{
"ons_code": "E14000636",
"name": "Chipping Barnet",
"position": {
"x": 4,
"y": 1
}
},
{
"ons_code": "E14000752",
"name": "Hornsey & Wood Green",
"position": {
"x": 5,
"y": 1
}
},
{
"ons_code": "E14000687",
"name": "Edmonton",
"position": {
"x": 6,
"y": 1
}
},
{
"ons_code": "E14000634",
"name": "Chingford & Woodford Green",
"position": {
"x": 7,
"y": 1
}
},
{
"ons_code": "E14000906",
"name": "Ruislip, Northwood & Pinner",
"position": {
"x": 0,
"y": 2
}
},
{
"ons_code": "E14000732",
"name": "Harrow West",
"position": {
"x": 1,
"y": 2
}
},
{
"ons_code": "E14000592",
"name": "Brent North",
"position": {
"x": 2,
"y": 2
}
},
{
"ons_code": "E14000727",
"name": "Hampstead & Kilburn",
"position": {
"x": 3,
"y": 2
}
},
{
"ons_code": "E14000703",
"name": "Finchley & Golders Green",
"position": {
"x": 4,
"y": 2
}
},
{
"ons_code": "E14000763",
"name": "Islington North",
"position": {
"x": 5,
"y": 2
}
},
{
"ons_code": "E14001002",
"name": "Tottenham",
"position": {
"x": 6,
"y": 2
}
},
{
"ons_code": "E14001013",
"name": "Walthamstow",
"position": {
"x": 7,
"y": 2
}
},
{
"ons_code": "E14000759",
"name": "Ilford North",
"position": {
"x": 8,
"y": 2
}
},
{
"ons_code": "E14000900",
"name": "Romford",
"position": {
"x": 9,
"y": 2
}
},
{
"ons_code": "E14000751",
"name": "Hornchurch & Upminster",
"position": {
"x": 10,
"y": 2
}
},
{
"ons_code": "E14001007",
"name": "Uxbridge & Ruislip South",
"position": {
"x": 0,
"y": 3
}
},
{
"ons_code": "E14000675",
"name": "Ealing North",
"position": {
"x": 1,
"y": 3
}
},
{
"ons_code": "E14000591",
"name": "Brent Central",
"position": {
"x": 2,
"y": 3
}
},
{
"ons_code": "E14000768",
"name": "Kensington",
"position": {
"x": 3,
"y": 3
}
},
{
"ons_code": "E14000750",
"name": "Holborn & St Pancras",
"position": {
"x": 4,
"y": 3
}
},
{
"ons_code": "E14000764",
"name": "Islington South & Finsbury",
"position": {
"x": 5,
"y": 3
}
},
{
"ons_code": "E14000720",
"name": "Hackney North & Stoke Newington",
"position": {
"x": 6,
"y": 3
}
},
{
"ons_code": "E14001032",
"name": "West Ham",
"position": {
"x": 7,
"y": 3
}
},
{
"ons_code": "E14000790",
"name": "Leyton & Wanstead",
"position": {
"x": 8,
"y": 3
}
},
{
"ons_code": "E14000760",
"name": "Ilford South",
"position": {
"x": 9,
"y": 3
}
},
{
"ons_code": "E14000657",
"name": "Dagenham & Rainham",
"position": {
"x": 10,
"y": 3
}
},
{
"ons_code": "E14000737",
"name": "Hayes & Harlington",
"position": {
"x": 0,
"y": 4
}
},
{
"ons_code": "E14000676",
"name": "Ealing Southall",
"position": {
"x": 1,
"y": 4
}
},
{
"ons_code": "E14000674",
"name": "Ealing Central & Acton",
"position": {
"x": 2,
"y": 4
}
},
{
"ons_code": "E14000629",
"name": "Chelsea & Fulham",
"position": {
"x": 3,
"y": 4
}
},
{
"ons_code": "E14001036",
"name": "Westminster North",
"position": {
"x": 4,
"y": 4
}
},
{
"ons_code": "E14000553",
"name": "Bermondsey & Old Southwark",
"position": {
"x": 5,
"y": 4
}
},
{
"ons_code": "E14000721",
"name": "Hackney South & Shoreditch",
"position": {
"x": 6,
"y": 4
}
},
{
"ons_code": "E14000882",
"name": "Poplar & Limehouse",
"position": {
"x": 7,
"y": 4
}
},
{
"ons_code": "E14000679",
"name": "East Ham",
"position": {
"x": 8,
"y": 4
}
},
{
"ons_code": "E14000540",
"name": "Barking",
"position": {
"x": 9,
"y": 4
}
},
{
"ons_code": "E14000701",
"name": "Feltham & Heston",
"position": {
"x": 0,
"y": 5
}
},
{
"ons_code": "E14000593",
"name": "Brentford & Isleworth",
"position": {
"x": 1,
"y": 5
}
},
{
"ons_code": "E14000726",
"name": "Hammersmith",
"position": {
"x": 2,
"y": 5
}
},
{
"ons_code": "E14000887",
"name": "Putney",
"position": {
"x": 3,
"y": 5
}
},
{
"ons_code": "E14000639",
"name": "Cities of London & Westminster",
"position": {
"x": 4,
"y": 5
}
},
{
"ons_code": "E14001008",
"name": "Vauxhall",
"position": {
"x": 5,
"y": 5
}
},
{
"ons_code": "E14000555",
"name": "Bethnal Green & Bow",
"position": {
"x": 6,
"y": 5
}
},
{
"ons_code": "E14000718",
"name": "Greenwich & Woolwich",
"position": {
"x": 7,
"y": 5
}
},
{
"ons_code": "E14000696",
"name": "Erith & Thamesmead",
"position": {
"x": 8,
"y": 5
}
},
{
"ons_code": "E14000558",
"name": "Bexleyheath & Crayford",
"position": {
"x": 9,
"y": 5
}
},
{
"ons_code": "E14001005",
"name": "Twickenham",
"position": {
"x": 1,
"y": 6
}
},
{
"ons_code": "E14000896",
"name": "Richmond Park",
"position": {
"x": 2,
"y": 6
}
},
{
"ons_code": "E14000998",
"name": "Tooting",
"position": {
"x": 3,
"y": 6
}
},
{
"ons_code": "E14000549",
"name": "Battersea",
"position": {
"x": 4,
"y": 6
}
},
{
"ons_code": "E14000673",
"name": "Dulwich & West Norwood",
"position": {
"x": 5,
"y": 6
}
},
{
"ons_code": "E14000615",
"name": "Camberwell & Peckham",
"position": {
"x": 6,
"y": 6
}
},
{
"ons_code": "E14000789",
"name": "Lewisham Deptford",
"position": {
"x": 7,
"y": 6
}
},
{
"ons_code": "E14000690",
"name": "Eltham",
"position": {
"x": 8,
"y": 6
}
},
{
"ons_code": "E14000869",
"name": "Old Bexley & Sidcup",
"position": {
"x": 9,
"y": 6
}
},
{
"ons_code": "E14000770",
"name": "Kingston & Surbiton",
"position": {
"x": 1,
"y": 7
}
},
{
"ons_code": "E14001040",
"name": "Wimbledon",
"position": {
"x": 2,
"y": 7
}
},
{
"ons_code": "E14000823",
"name": "Mitcham & Morden",
"position": {
"x": 3,
"y": 7
}
},
{
"ons_code": "E14000978",
"name": "Streatham",
"position": {
"x": 4,
"y": 7
}
},
{
"ons_code": "E14000788",
"name": "Lewisham West & Penge",
"position": {
"x": 5,
"y": 7
}
},
{
"ons_code": "E14000787",
"name": "Lewisham East",
"position": {
"x": 6,
"y": 7
}
},
{
"ons_code": "E14000604",
"name": "Bromley & Chislehurst",
"position": {
"x": 7,
"y": 7
}
},
{
"ons_code": "E14000984",
"name": "Sutton & Cheam",
"position": {
"x": 2,
"y": 8
}
},
{
"ons_code": "E14000621",
"name": "Carshalton & Wallington",
"position": {
"x": 3,
"y": 8
}
},
{
"ons_code": "E14000655",
"name": "Croydon North",
"position": {
"x": 4,
"y": 8
}
},
{
"ons_code": "E14000654",
"name": "Croydon Central",
"position": {
"x": 5,
"y": 8
}
},
{
"ons_code": "E14000551",
"name": "Beckenham",
"position": {
"x": 6,
"y": 8
}
},
{
"ons_code": "E14000872",
"name": "Orpington",
"position": {
"x": 7,
"y": 8
}
},
{
"ons_code": "E14000656",
"name": "Croydon South",
"position": {
"x": 4,
"y": 9
}
}
]
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment