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.
height: 1160 |
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 | |
}, | |
*/ |