Small multiple bar charts of the 2017 General Election in a grid layout of London UK parliamentary constituencies. Layout my own. Hover over barcharts to reveal a tooltip.
forked from tlfrd‘s block: London Constituency Grid Layout
forked from tlfrd‘s block: London Small Multiples
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="london-grid.js"></script>
<style>
body {
margin: 0;
}
text {
font-family: monospace;
}
.label {
font-size: 10px;
}
.constituency .background {
fill: grey;
fill-opacity: 0.2;
}
div.tooltip {
position: absolute;
text-align: left;
padding: 5px;
font: 10px monospace;
background-color: rgba(255, 255, 255, .95);
border: 1px solid lightgray;
pointer-events: none;
width: 100px;
}
div.tooltip .result {
float: right;
}
div.tooltip .name {
font-weight: bold;
}
</style>
</head>
<body>
<script>
var cfg = {
gridLength: 11,
gridHeight: 10,
paddingX: 25,
paddingY: 15,
}
var margin = {top: 15, right: 100, bottom: 15, left: 100};
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 div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var rectWidth = (width / cfg.gridLength) - cfg.paddingX,
rectHeight = (height / cfg.gridHeight) - cfg.paddingY;
var dataUrl = "results.json";
var colours = {
"Con": "#0087dc",
"Green": "#008066",
"Lab": "#d50000",
"LabCoop": "#d50000",
"LibDem": "#FDBB30",
"UKIP": "#B3009D"
};
var constituencies = svg.append("g").attr("class", "constituencies");
function calculateCoords(d) {
var x = d.position.x * (rectWidth + cfg.paddingX);
var y = d.position.y * (rectHeight + cfg.paddingY);
return [x, y];
}
// Only include data for five main parties
var filteredGeData = {};
d3.json(dataUrl, function(geData) {
for (var c in geData) {
var candidates = [];
for (var i in geData[c].candidates) {
if (colours[geData[c].candidates[i].party]) {
candidates.push(geData[c].candidates[i]);
}
}
filteredGeData[c] = {
ons: c,
name: geData[c].name,
candidates: candidates
};
}
var constituency = constituencies.selectAll("g")
.data(londonGrid)
.enter().append("g")
.attr("class", "constituency")
.attr("transform", d => "translate(" + [calculateCoords(d)[0], calculateCoords(d)[1]] + ")")
.on("mouseover", showLabel)
.on("mouseout", hideLabel);
constituency.append("rect")
.attr("class", "background")
.attr("width", rectWidth)
.attr("height", rectHeight);
constituency.each(addBarchart);
});
function addBarchart(d) {
var barchart = d3.select(this),
results = filteredGeData[d.ons_code];
var x = d3.scaleBand()
.domain(results.candidates.map(d => d.party))
.range([0, rectWidth]);
var y = d3.scaleLinear()
.domain([0, 100])
.range([rectHeight, 0]);
var bars = barchart.append("g")
.selectAll("rect")
.data(results.candidates)
.enter().append("rect")
.attr("x", d => x(d.party))
.attr("y", d => y(d.percentageShare))
.attr("width", x.bandwidth())
.attr("height", d => rectHeight - y(d.percentageShare))
.style("fill", d => colours[d.party]);
var label = barchart.append("text")
.attr("class", "label")
.attr("x", rectWidth)
.attr("dx", -rectWidth / 3)
.attr("dy", rectHeight / 3)
.text(d => d.name.slice(0, 2));
}
function showLabel(d) {
var left = calculateCoords(d)[0],
right = calculateCoords(d)[1];
var candidates = filteredGeData[d.ons_code].candidates;
var html = "<span class='name'>" + filteredGeData[d.ons_code].name + "</span></br></br>";
candidates.forEach(function(c) {
html += c.party;
html += "<span class='result'>" + Math.round(c.percentageShare) + "%</span>";
html += "</br>";
});
div.transition()
.duration(200)
.style("opacity", 1)
div.html(html)
.style("left", (left + margin.left + rectWidth + 10) + "px")
.style("top", (right + margin.top) + "px")
}
function hideLabel(d) {
div.transition()
.duration(200)
.style("opacity", 0)
}
</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
}
}
]