How to create an interactive vote swing viewer in D3 (Part 1)

Written by puntofisso | Published 2017/05/19
Tech Story Tags: d3 | javascript | interactive-journalism | ddj | data-journalism

TLDRvia the TL;DR App

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.

Warning: you will see some very dirty coding. I created this tool in a couple of hours after work. With a bit more time and care, I’m sure you could do better.

Here we go.

First of all, get the data and make sure it’s reliable

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.

Clean the data and remove what you don’t need

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.

The general idea

What I’m going to do is relatively simple:

  1. I will read the list of Labour/Conservative constituencies with their majority
  2. a rectangle per constituency will be drawn in a SVG canvas
  3. the rectangle will be to the right if currently held by a Conservative MP, and coloured light blue; red if currently Labour, and coloured light red
  4. the rectangle will be stacked in a “pots” representing its majority, so we can count gains; each pot will represent a couple of percent points of swing — for example, every seat with a majority between 29% and 31% will be in the pot “30”
  5. a slider will let you interact with the swing; seats that swing will assume a more intense blue or red colour, according to whose gain it is.

Set up your html page

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.

Let’s add the constituency data

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 ...];

Chart setup: a bit of scaffolding

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);

We’re ready to go: the drawing function

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:

  • light blue or light red represent the current holder of the constituency; this will appear when no swing is being applied
  • if we’re applying a swing, and the swing is greater than the current majority for the constituency represented by that rectangle, the colour will become intense blue or red according to who wins the seat
  • by the way we have designed our slider, we’ll need to consider a negative swing if the direction is left, i.e. Labour, and positive if right, i.e. Conservative.

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);

Labels are optional but will make the chart easier to read

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.

The slider listener

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!

Remember to configure your CSS

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

Well done for getting here!

There’s not much else for me to say, other than the usual:

  1. if you have any feedback, please let me know — this is work in progress
  2. follow me on twitter at http://twitter.com/puntofisso
  3. subscribe to my newsletter about all geeky curiosities, data visualization, open data, and data journalism at http://puntofisso.net/newsletter
  4. please recommend and share this Medium post :-)

Part 2 of this tutorial is now available. For more like this, please follow me on twitter and subscribe to my newsletter.


Published by HackerNoon on 2017/05/19