block by jeremycflin b20532210eb4ee263875e0021a7adff4

London Small Multiples

Full Screen

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

index.html

<!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>

london-grid.js

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
    }
  }
]