I walked up to Joe Weisenthal in a bar (The Churchill!) having a Brexit party and asked him to give me an assignment. Map based on Let's Make A Map, which was a good head start!
Results based on the excellent Guardian Brexit results page.
I walked up to Joe Weisenthal in a bar (The Churchill!) having a Brexit party and asked him to give me an assignment. Map based on Let's Make A Map, which was a good head start!
Results based on the excellent Guardian Brexit results page.
| <!DOCTYPE html> | |
| <meta charset="utf-8"> | |
| <!-- <meta name="viewport" content="width=device-width, initial-scale=1"> --> | |
| <meta name="viewport" content="width=960"> | |
| <style> | |
| html, body { | |
| margin: 0; | |
| padding: 0; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| .subunit.SCT { fill: rgba(255,0,0,.05); stroke: #aaa; } | |
| .subunit.WLS { fill: rgba(0,255,0,.05); stroke: #aaa; } | |
| .subunit.NIR { fill: rgba(255,0,255,.05); stroke: #aaa; } | |
| .subunit.ENG { fill: rgba(0,255,255,.05); stroke: #aaa; } | |
| .subunit.IRL, | |
| .subunit-label.IRL { | |
| display: none; | |
| } | |
| .subunit-boundary { | |
| fill: none; | |
| stroke: #777; | |
| stroke-dasharray: 2,2; | |
| stroke-linejoin: round; | |
| } | |
| .subunit-boundary.IRL { | |
| stroke: #aaa; | |
| } | |
| .subunit-label { | |
| fill: #777; | |
| fill-opacity: .5; | |
| font-size: 20px; | |
| font-weight: 300; | |
| text-anchor: middle; | |
| } | |
| .place, | |
| .place-label { | |
| display: none; | |
| fill: #444; | |
| } | |
| text { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| font-size: 10px; | |
| pointer-events: none; | |
| } | |
| h1 { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| text-align: center; | |
| position: fixed; | |
| width: 100%; | |
| padding: 0 .25em; | |
| font-size: 50px; | |
| pointer-events: none; | |
| } | |
| h1.prompt { | |
| top: .25em; | |
| } | |
| h1.results { | |
| bottom: .25em; | |
| } | |
| circle { | |
| stroke-width: 2; | |
| stroke: black; | |
| } | |
| circle.guess { | |
| fill: rgba(0,0,0,.6); | |
| } | |
| circle.answer { | |
| fill: rgba(0,255,0,.6); | |
| } | |
| button { | |
| font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
| position: fixed; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%,-50%) rotate(20deg); | |
| border-radius: 50%; | |
| padding: 1em; | |
| background: rgba(255,255,255,.5); | |
| border: 2px solid black; | |
| font-size: 70px; | |
| cursor: pointer; | |
| } | |
| div.finish { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(255,255,255,.95); | |
| } | |
| div.finish h1 { | |
| position: absolute; | |
| top: 50%; | |
| transform: translate(0,-50%); | |
| margin: 0; | |
| } | |
| </style> | |
| <body> | |
| <h1 class="prompt"> | |
| <span class="name"></span> voted to | |
| <span class="result"></span>, | |
| <span class="percentage"></span>%. Where do you think it is? | |
| </h1> | |
| <h1 class="results"></h1> | |
| <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script></script> | |
| <script src="//d3js.org/topojson.v1.min.js"></script> | |
| <script src="results.js"></script> | |
| <script> | |
| // COORDINATES are LONG, LAT | |
| var guessDistances = []; | |
| var width = 960, | |
| height = 1160; | |
| var projection = d3.geo.albers() | |
| .center([0, 55.4]) | |
| .rotate([4.4, 0]) | |
| .parallels([50, 60]) | |
| .scale(1200 * 5) | |
| .translate([width / 2, height / 2]); | |
| var path = d3.geo.path() | |
| .projection(projection) | |
| .pointRadius(2); | |
| var svg = d3.select("body").append("svg") | |
| .attr("width", width) | |
| .attr("height", height); | |
| d3.json("uk.json", function(error, uk) { | |
| var subunits = topojson.feature(uk, uk.objects.subunits), | |
| places = topojson.feature(uk, uk.objects.places); | |
| svg.selectAll(".subunit") | |
| .data(subunits.features) | |
| .enter().append("path") | |
| .attr("class", function(d) { return "subunit " + d.id; }) | |
| .attr("d", path); | |
| svg.append("path") | |
| .datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a !== b && a.id !== "IRL"; })) | |
| .attr("d", path) | |
| .attr("class", "subunit-boundary"); | |
| svg.append("path") | |
| .datum(topojson.mesh(uk, uk.objects.subunits, function(a, b) { return a === b && a.id === "IRL"; })) | |
| .attr("d", path) | |
| .attr("class", "subunit-boundary IRL"); | |
| svg.selectAll(".subunit-label") | |
| .data(subunits.features) | |
| .enter().append("text") | |
| .attr("class", function(d) { return "subunit-label " + d.id; }) | |
| .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; }) | |
| .attr("dy", ".35em") | |
| .text(function(d) { return d.properties.name; }); | |
| svg.append("path") | |
| .datum(places) | |
| .attr("d", path) | |
| .attr("class", "place"); | |
| svg.selectAll(".place-label") | |
| .data(places.features) | |
| .enter().append("text") | |
| .attr("class", "place-label") | |
| .attr("transform", function(d) { return "translate(" + projection(d.geometry.coordinates) + ")"; }) | |
| .attr("x", function(d) { return d.geometry.coordinates[0] > -1 ? 6 : -6; }) | |
| .attr("dy", ".35em") | |
| .style("text-anchor", function(d) { return d.geometry.coordinates[0] > -1 ? "start" : "end"; }) | |
| .text(function(d) { return d.properties.name; }); | |
| promptPlace(results.pop()); | |
| function promptPlace(place) { | |
| if(place===undefined) { | |
| finish(); | |
| return; | |
| } | |
| // reset | |
| d3.selectAll('circle').remove(); | |
| d3.select('button').remove(); | |
| d3.select('.results').text(''); | |
| // fill in prompt | |
| d3.select('.name').text(place.name); | |
| d3.select('.result').text(place.vote > 50 ? 'remain' : 'leave'); | |
| d3.select('.percentage').text(place.vote > 50 ? place.vote : 100-place.vote); | |
| d3.select('svg').on('click', function() { | |
| var performance = d3.scale.threshold() | |
| .domain([20,80,150,300]) | |
| .range(['Amazing!!!', 'Very good!', 'OK!', 'Mediocre.', 'You have no clue.']) | |
| var guess = projection.invert(d3.mouse(this)); | |
| var dist = distance(place.coordinates, guess); | |
| guessDistances.push(dist); | |
| d3.select('.results').text('You were ' + Math.round(dist) + ' “kilometres” away. ' + performance(dist)); | |
| svg.append('circle') | |
| .classed('guess', true) | |
| .attr('cx', d3.mouse(this)[0]) | |
| .attr('cy', d3.mouse(this)[1]) | |
| .attr('r', 1e-6) | |
| .transition() | |
| .delay(0) | |
| .duration(250) | |
| .attr('r', 30); | |
| svg.append('circle') | |
| .classed('answer', true) | |
| .attr('cx', projection(place.coordinates)[0]) | |
| .attr('cy', projection(place.coordinates)[1]) | |
| .attr('r', 1e-6) | |
| .transition() | |
| .delay(250) | |
| .duration(250) | |
| .attr('r', 30); | |
| // next | |
| d3.select('svg').on('click', null); | |
| setTimeout(function() { | |
| d3.select('body').append('button').text('Next!') | |
| .on('click', function() { | |
| promptPlace(results.pop()); | |
| }) | |
| }, 1000); | |
| }) | |
| } | |
| function finish() { | |
| var performance = d3.scale.threshold() | |
| .domain([20,80,150,300]) | |
| .range([ | |
| 'You are practically Executive Director Dr. John Ludden of the British Geological Survey!', | |
| 'You must be British!', | |
| 'You are a decent human being!', | |
| 'You are not good at this but we support your existence anyway!', | |
| 'You are very very bad at this unimportant game.' | |
| ]); | |
| d3.select('body').append('div') | |
| .classed('finish', true) | |
| .append('h1') | |
| .text('THE END! On average you were off by ' + Math.round(d3.mean(guessDistances)) + ' km, with a st.dev. of ' + Math.round(d3.deviation(guessDistances)) + ' km. ' + performance(d3.mean(guessDistances))); | |
| } | |
| }); | |
| // from https://www.geodatasource.com/developers/javascript | |
| function distance(from, to, unit) { | |
| if(unit===undefined) unit = 'K'; | |
| var lat1 = from[1]; | |
| var lon1 = from[0]; | |
| var lat2 = to[1]; | |
| var lon2 = to[0]; | |
| var radlat1 = Math.PI * lat1/180 | |
| var radlat2 = Math.PI * lat2/180 | |
| var theta = lon1-lon2 | |
| var radtheta = Math.PI * theta/180 | |
| var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta); | |
| dist = Math.acos(dist) | |
| dist = dist * 180/Math.PI | |
| dist = dist * 60 * 1.1515 | |
| if (unit=="K") { dist = dist * 1.609344 } | |
| if (unit=="N") { dist = dist * 0.8684 } | |
| return dist | |
| } | |
| </script> |
| var results = [ | |
| { | |
| 'name': 'Clackmannanshire', | |
| 'coordinates': [-3.75, 56.166667], | |
| 'vote': 57.78 | |
| }, | |
| { | |
| 'name': 'Sunderland', | |
| 'coordinates': [-1.385, 54.91], | |
| 'vote': 38.66 | |
| }, | |
| { | |
| 'name': 'Isles of Scilly', | |
| 'coordinates': [-6.322778, 49.936111], | |
| 'vote': 56.39 | |
| }, | |
| { | |
| 'name': 'Broxbourne', | |
| 'coordinates': [-0.0216, 51.7495], | |
| 'vote': 33.74 | |
| }, | |
| { | |
| 'name': 'Swindon', | |
| 'coordinates': [-1.78, 51.56], | |
| 'vote': 45.34 | |
| }, | |
| { | |
| 'name': 'Kettering', | |
| 'coordinates': [-0.72292, 52.39312], | |
| 'vote': 39.01 | |
| }, | |
| { | |
| 'name': 'Shetland Islands', | |
| 'coordinates': [-1.216667, 60.35], | |
| 'vote': 56.51 | |
| }, | |
| { | |
| 'name': 'South Tyneside', | |
| 'coordinates': [-1.438, 54.959], | |
| 'vote': 37.95 | |
| }, | |
| { | |
| 'name': 'West Dunbartonshire', | |
| 'coordinates': [-4.515, 55.99], | |
| 'vote': 61.99 | |
| }, | |
| { | |
| 'name': 'Dundee', | |
| 'coordinates': [-2.97, 56.464], | |
| 'vote': 59.78 | |
| }, | |
| { | |
| 'name': 'Cannock Chase', | |
| 'coordinates': [-2.001, 52.746], | |
| 'vote': 31.14 | |
| }, | |
| { | |
| 'name': 'Coventry', | |
| 'coordinates': [-1.510556, 52.408056], | |
| 'vote': 44.4 | |
| }, | |
| { | |
| 'name': 'Rochdale', | |
| 'coordinates': [-2.161, 53.6136], | |
| 'vote': 39.93 | |
| }, | |
| { | |
| 'name': 'Erewash', | |
| 'coordinates': [-1.316667, 52.916667], | |
| 'vote': 38.77 | |
| }, | |
| { | |
| 'name': 'South Ribble', | |
| 'coordinates': [-2.69, 53.697], | |
| 'vote': 41.44 | |
| }, | |
| { | |
| 'name': 'South Somerset', | |
| 'coordinates': [-2.9893344, 50.9844058], | |
| 'vote': 42.75 | |
| }, | |
| { | |
| 'name': 'Haringey', | |
| 'coordinates': [-0.112915, 51.601632], | |
| 'vote': 75.57 | |
| }, | |
| { | |
| 'name': 'Oadby & Wigston', | |
| 'coordinates': [-1.095, 52.592], | |
| 'vote': 45.42 | |
| }, | |
| { | |
| 'name': 'Carlisle', | |
| 'coordinates': [-2.937, 54.879], | |
| 'vote': 39.86 | |
| }, | |
| { | |
| 'name': 'Peterborough', | |
| 'coordinates': [-0.25, 52.583333], | |
| 'vote': 39.11 | |
| }, | |
| { | |
| 'name': 'South Lakeland', | |
| 'coordinates': [-2.88, 54.312], | |
| 'vote': 52.86 | |
| }, | |
| { | |
| 'name': 'Reigate & Banstead', | |
| 'coordinates': [-0.16, 51.249], | |
| 'vote': 49.51 | |
| }, | |
| { | |
| 'name': 'Hastings', | |
| 'coordinates': [0.572875, 50.856302], | |
| 'vote': 45.12 | |
| }, | |
| { | |
| 'name': 'Powys', | |
| 'coordinates': [-3.416667, 52.3], | |
| 'vote': 46.26 | |
| }, | |
| { | |
| 'name': 'Ipswich', | |
| 'coordinates': [1.155556, 52.059444], | |
| 'vote': 41.74 | |
| }, | |
| { | |
| 'name': 'Chelmsford', | |
| 'coordinates': [0.4798, 51.7361], | |
| 'vote': 47.17 | |
| }, | |
| { | |
| 'name': 'Doncaster', | |
| 'coordinates': [-1.133, 53.515], | |
| 'vote': 31.04 | |
| }, | |
| { | |
| 'name': 'Vale of White Horse', | |
| 'coordinates': [-1.5, 51.6], | |
| 'vote': 56.7 | |
| }, | |
| { | |
| 'name': 'Reading', | |
| 'coordinates': [-0.973056, 51.454167], | |
| 'vote': 58.03 | |
| }, | |
| { | |
| 'name': 'Ryedale', | |
| 'coordinates': [-0.79, 54.139], | |
| 'vote': 44.74 | |
| }, | |
| ]; | |
| d3.shuffle(results); | |
| results = results.slice(0,10); | |
| /* | |
| { | |
| 'name': '', | |
| 'coordinates': [0,0], | |
| 'vote': 50 //remain | |
| }, | |
| */ |