block by tophtucker d161169c785ac1eeec01c92f89bd0ecd

The Brexit Game

Full Screen

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.

index.html

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

results.js

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

*/