A couple of days ago this article by Chris Cook, BBC Newsnight’s policy editor, appeared on the BBC website. The article discussed how May and Corbyn are fighting very different campaigns and it used the following chart to support its analysis:
Image © BBC: http://www.bbc.co.uk/news/uk-politics-39927866
I thought: wow — that’s a very good visualization that could be used easily to represent any 2-party swing. It could do with a bit more interactivity, too. So I set out to replicate it and I’m going to show you how. The final result is here.
Here we go.
I say this because I first tried to use some data I had scraped from Wikipedia previously. I pushed the “publish” button without checking. Bad mistake. Luckily, Twitter is always helpful
The best source for this data is the British Election Study, as suggested by Chris Hanretty (whom you should follow if you are interested in political data analysis). The data can be downloaded here under “BES Constituency Results with Census and Candidate Data”. Using a recognised dataset like BES will also make your analysis and tools comparable with others.
BES comes with a lot of useful variables, but for a simple 2-party swing tool all I needed was a triple containing: constituency name, winning party, and majority (percent). Also, I only needed the constituencies with Labour and Conservative in the first two positions (in any order). Luckily, BES is well structured so I simply filtered it using Excel filters and a bit of scripting, obtaining a CSV that looks like this:
The resulting spreadsheet
Now, I could have used this CSV file as it is, but I kinda prefer to use JSON as it works very well with D3. As it’s more structured, it would also allow me to extend the data to power different future analyses. I used a quick Bash script to do this:
while read linedoconst=`echo $line | awk -F"," {'print $1'}`maj=`echo $line | awk -F"," {'print $3'}`winner=`echo $line | awk -F"," {'print $2'}`
echo "{"
echo " \\"name\\": \\"$const\\","
echo " \\"majority\\": \\"$maj\\","
echo " \\"winner\\": \\"$winner\\""
echo "},"
done < bes.csv
Make sure the resulting JSON is correct, remove the final comma, and you’re ready to go.
What I’m going to do is relatively simple:
To bring it all together, I’ve used a bootstrap template, but you can use whatever you feel comfortable with. All it needs to work is a container for the chart, a slider, and some containers for the text that shows the swing (being lazy, I’ve also used a hidden span to contain a number representing the current slider value). Remember to add the latest jquery library and D3 (my code uses D3 version 4):
<div id="graph"></div><input type="range" id="slider" min=0 max=100 /></input><span id="slidertext">No swing</span><span id="slidervalue">0</span><span id="seats">0</span>
Note that I set the slider to operate on a scale 0 to 100. You can make it behave it differently, but I preferred to do it this way as it’s handy if I want to switch to use percentages.
Using the script above, you can either generate a JSON file that gets read by D3, or embed it as a variable in your html file. I opted for the latter:
var constituencies = [{"name": "Gower","majority": "0.06","winner": "Conservative"},{"name": "Derby North","majority": "0.09","winner": "Conservative"},// and so on ...];
The chart setup is relatively easy. First of all we select the div we have created above by using its id (#graph). We add an svg element to it, and configure its main properties — width, height, etc:
var svgContainer = d3.select("#graph").append("svg").attr("width", 600).attr("height", 350).style("border", "0px solid black");
We also want a tooltip to show on each constituency. This will just be a div appearing somewhere:
var div = d3.select("body").append("div").attr("class", "tooltip").style("opacity", 0);
The hard work on the page is made by a function. paint_constituencies() is called every time the page is loaded and every time the slider is moved (which I will show you in a bit). You could probably do it more efficiently in a way that doesn’t require a redraw, but this is simple enough that performance won’t be an issue.
First of all, let’s clear the canvas from its contents, and check if there is a swing on the slider (so that we can use this value later);
svgContainer.selectAll("*").remove();var swing = d3.select('#slidervalue').node().innerHTML;var seats_gained = 0;
Creating one rectangle per constituency is easily done by binding our array to a factory method that creates the rectangles. We create the rectangles and append them to the svg container that we have just emptied. If you see the html elements in your developer tools, you will realise that these are just <svg/> tags being added to the main <svg/> tag. We’ll worry later about the styling.
var rectangles = svgContainer.selectAll(“rect”).data(constituencies).enter().append(“rect”);
We’ve reached the heart of this app. All we need to do now is apply the right styling to the rectangles we’ve just created.
Before we do this, you will remember that we said we want to stack the rectangles based on their majority. I simply use counters, one per “pot”: this way I know that if I want to add a rectangle on top of a stack, I can see at what level that stack is. For example, let’s say I want to add a constituency with a 30% majority; all I need to do is to check the 29–31 pot. There will be pots to cover all possible majorities (East Ham is quite the outlier with a 65.50% majority!) and I’ve opted to have them split by 2%. To initialise the pots, I do the following:
var stackLab = {};var stackCon = {};for (i = 0; i<67; i=i+2) {stackLab[i] = 0;stackCon[i] = 0;}
All we need to do now is to give the rectangles the right attributes: their x and y positions, size, fill, etc. Before we get there, I suggest you familiarise with the way an SVG canvas works — it’s not exactly a cartesian plane. Its axis system always start with (x=0,y=0) in the top/left corner; hence, the axis are always positive and you might need to translate your coordinates from cartesian by adjusting the sign and shifting the value. Which is what we’ll be doing in the next few lines of code.
The way D3 allows you to set up the attributes is very simple. The general form this assumes is the following:
rectangles.attr(<attribute name>, <value>);
The most important aspect is that <value> can be a function, with a parameter indicating which specific rectangle we’re working on. For example, if we wanted to place the rectangle on the horizontal axis starting from 0 to exactly where their majority lies, we would do:
rectangles.attr("x", function(d) {return d['majority'];});
Take some time to appreciate these few lines. We’re going to use them extensively.
The first step is to give our rectangles a size. This is relatively simple, and you can play with the values until you’re satisfied:
rectangles.attr("width", 6).attr("height", 14);
Yes, you concatenate multiple settings like that — D3 is object oriented so each function always returns the original object, as amended by whatever the function did. This is very handy.
Let’s put the rectangles in the right place.
For the x axis, the line of code above won’t work as it is because we are splitting our area in two regions, the Labour-held region and the Conservative-held region. In practice, we need to translate everything by half of the total width, which we defined as 600 when we initialised the chart.
Imagine the x axis on a cartesian plane. Conservative will be on the right, where x>0; Labour will be on the left, where x<0. On our svg chart, our 0 is half its width: 300. So Conservative rectangles will be roughly in the region [301, 302, … 600], while Labour rectangles will be in the region [0, 1, 2 … 300].
We also want to consider the size of the rectangles and the spacing between them, and make sure we remember not to use the actual majority, but the pot to which that majority belongs. This amounts to the following lines:
rectangles.attr("x", function (d) {var pot = Math.round(d['majority'] / 2)*2;if (d['winner'] === "Conservative")val = 300 + (pot+2)*4;elseval = 300 — (pot+2)*4;return val;});
The code for the y position is very similar, except instead of the mere pot value we’re going to use the stacking level reached for that pot. In other words, we want to place the rectangle on top of any rectangle already in that pot, and increment the stacking level for that pot:
rectangles.attr("y", function (d) {var pot = Math.round(d['majority'] / 2)*2;if (d['winner'] === "Labour")val = 310 — (stackLab[pot]++)*15;elseval = 310 — (stackCon[pot]++)*15;return val;});
We are now ready to add the colours, by using the swing variable that we set at the beginning:
To achieve all of this, we use the “fill” property, and a swing formula — in short, remember that to win a seat with a 10% majority you need a 5% swing. As we’re at it, we can also set our text indicator of gains directly from d3. Hence:
rectangles.style("fill", function(d) {var returnColor;
if (d['winner'] === "Labour"){if (swing*2 > +d['majority']) {seats_gained++;returnColor = "#0000ff"; //red} else {returnColor = "#f08080"; //light red}} else if (d['winner'] === "Conservative"){if (-swing*2 > +d[majority']) {seats_gained++;returnColor = "#ff0000"; //blue} else {returnColor = "#87cefa";//light blue}
d3.select('#seats').node().innerHTML = seats_gained + " gains";return returnColor;}
Finally, we want the rectangles to show some data about the constituency. We can do this showing the tooltip we have defined earlier, and we’ll do so using listeners. Listeners are helper functions that detect specific events on the page, and allow you to define reactions to those events. In our case, we want the tooltip to become visible when the mouse pointer hits a rectangle, and disappear when it’s out of focus:
rectangles.on("mouseover", function(d) {div.transition().duration(200).style("opacity", .9);div.html(d['name'] + "<br/>" + d['majority'] + "%").style("left", (d3.event.pageX) + "px").style("top", (d3.event.pageY — 28) + "px");}).on("mouseout", function(d) {div.transition().duration(500).style("opacity", 0);});
The code above may appear confusing, but if you read it line by line it’s actually pretty easy. What it does is to use a transition, in this case a delay, to display the tooltip, which makes it more pleasant to the user. The rest is just matter of styling (adding opacity = appearing, removing opacity = disappearing), and adding content, which in this case is just the name of the constituency and the current majority.
To make things clearer, I’ve added a vertical bar in the middle. This is actually very simple, as it just uses two coordinates, (x1,y1) for the bottom end, (x2,y2) for the top end:
svgContainer.append(“line”).style(“stroke”, “black”).attr(“x1”, 304).attr(“y1”, 350).attr(“x2”, 304).attr(“y2”, 50);
If you see my original swing tool, there are labels on the x axis. To do this, I’ve created a function add_labels() that I call from paint_constituencies(). This allows me to decide where and what to place, but there are much better and integrated ways, for example using ranges and domains. In this case, as I have pots and might not want to display all labels, I opted for a very manual approach.
What my function does is append text to the svg container. For example:
svgContainer.append("text") .attr("class", "x label") .attr("text-anchor", "end") .attr("x", 300 + (1+2) * 4) .attr("y", 340 ) .text("0");
I leave the rest for exercise. The general idea is that you find the perfect spot on the y-axis, then decide how to space them.
To bring it all together, we need to react when the swing slider moves. Remember that we’ve set it to have a range between 0 and 100, while in fact this might represent a swing of 0%–100% (well, in theory…), which will be positive if towards the Conservatives, and negative if towards Labour. In practice, I use a 0–100 slider to represent a -100–100 range. So I need to translate the value, and when we’re done with the calculations we can set the text and repaint the rectangles:
$('#slider').change(function() {swing_pc = $(this).val()-50;swing=200*swing_pc/100;
if (swing>0){text = swing+"% swing to Conservative";} else {text = -swing+"% swing to Labour";}$('#slidervalue').html(swing); $('#slidertext').html(text);
paint_constituencies();});
And that’s pretty much it!
To make it all display nicely, you will set some styling. You might want the slider to be of a given size, and the tooltip to appear somewhere specific. There are several ways of doing this, but I’ll paste my CSS for completeness:
div.tooltip {position: absolute;text-align: center;width: 120px;height: 28px;padding: 2px;font: 12px sans-serif;background: white;border: 0px;border-radius: 8px;pointer-events: none;}
#slider {width: 600px !important;}
span#slidervalue {visibility: hidden !important;}
There’s not much else for me to say, other than the usual:
Part 2 of this tutorial is now available. For more like this, please follow me on twitter and subscribe to my newsletter.