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

Written by puntofisso | Published 2017/05/22
Tech Story Tags: d3 | ddj | data-journalism | elections | data

TLDRvia the TL;DR App

In my previous post I walked through how I used D3 to create a Labour/Conservative swing viewer for the coming General Election. It is a pretty simple, not particularly performant, but useful tool which can easily be readapted to any other elections with a meaningful 2-party swing.

My “open data bro” Jamie Whyte picked it up and created a very nice map visualization with it — moving the swing from a 2-axis stacked view to a geographical one. Take a look at Jamie’s interactive map by going to his website. I spent some time playing with Jamie’s map, as it’s really good. But with every geographical visualization, it comes with an issue: it overstates the impact of areas with low population.

There are many ways to overcome this issue, and luckily one has been provided by another stalwart of the UK open data community, the ODI node in Leeds. They have developed a simple standard to develop hexagon maps, a way to visualize grids in which nodes can be placed more freely than if they were squares or rectangles. Hex maps are a very powerful tool, that allows to have the simplicity of a cartesian grid while placing information in a way that resembles a geography.

Thanks to the d3-hexjson library developed by Oli Hawkins, hex maps are easy to use with D3. Here’s the final result and, how I got there:

650 constituencies in a hex map

How do hex maps work

Hex maps are basically a standard that puts data in specified way in a JSON file. You can specify nodes, give them a name, some properties, and place them on a grid. A full explanation can be found here. For this post, it suffices to say that I used a constituencies hex map made by ODI Leeds, that looks like this:

...

"E14000530":{"n":"Aldershot","q":-3,"r":-11,"a":"SE","u":"UKJ","p":72430},

"E14000531":{"n":"Aldridge-Brownhills","q":-3,"r":-1,"a":"WM","u":"UKG","p":60215},

"E14000532":{"n":"Altrincham and Sale West","q":-7,"r":3,"a":"NW","u":"UKD","p":71511},

"E14000533":{"n":"Amber Valley","q":0,"r":2,"a":"EM","u":"UKF","p":69510},

"E14000534":{"n":"Arundel and South Downs","q":0,"r":-15,"a":"SE","u":"UKJ","p":77242},...

Basically, each constituency is a JSON object identified by the constituency ID, with properties like its name in the object.

My data needed some review

If you look at my previous post, you will realise the JSON extract with the constituencies data looked like this:

,{"name": "Morley and Outwood","majority": "0.87","winner": "Conservative"},{"name": "Halifax","majority": "0.98","winner": "Labour"},

In data like this, there is a potential problem: if we want to link my data to the constituencies JSON above, we need to rely on the constituency names to be actually the same. But using names isn’t quite a smart way to do things, so I decide to make my extract from the BES spreadsheet again and include the constituency ID. The previous blog post explains you how to do this, with the final result looking this way:

...,{"name": "Morley and Outwood","id": "E14000826","majority": "0.87","winner": "Conservative"},{"name": "Halifax","id": "E14000723","majority": "0.98","winner": "Labour"},{"name": "Wirral West","id": "E14001044","majority": "1","winner": "Labour"},...

A simple walk-through the logic

Keeping the logic extremely naive wouldn’t work if we needed to draw thousands of nodes. Being lazy and having to deal with fewer than 650 nodes, I will keep things very easy. In short:

  1. At each calling of paint_constituencies(), I will store an object containing all the colours of the constituency nodes after applying the swing — so that Labour holds are light red, Conservative holds light blue, while gains are respectively intense red and intense blue
  2. I will pass this object to a function that draws the hex map, and will query this object by constituency ID, obtaining the status of the constituency, which determines the colour.

That’s it, simple, quick (and very inefficient — but we might discuss this in a later blog post).

Before we start, let’s adjust the html

Although styling is beyond the scope of this post, let me just add that I want the hex map to be just on the right of the swing viewer, so I put the two divs containing the two widgets in a row, and use Bootstrap’s classes to make sure they are more or less aligned:

<div class="row"><div class="col-md-8" id="graph"></div><div class="col-md-4" id="vis"></div></div>

The rest is very easy.

Creating the “winners” object

Do you remember we had a function called paint_constituencies()? This function assigns a colour to the nodes in the original swing viewer. The logic is pretty much the same, except we want to store the state of each constituency in an array. Instead of storing the colour, we will just store the outcome, as one of these values: LAB, CON, LABhold, CONhold.

Define an object at the beginning of the function

var winners_object = {};

Then, let’s amend the function setting the colour filling. You will remember this function basically checked the status of the swing, and assigned the node a colour after assessing the status as a hold or a gain. All we need to do is to add this reasoning into our object above:

rectangles.style(“fill”, function(d) {var returnColor;var cid = d['id'];if (d['winner'] === "Labour"){if (swing*2 > +d['majority']) {seats_gained++;returnColor = "#0000ff";winners_object[cid] = "CON";} else {returnColor = "#f08080";winners_object[cid] = "LABhold";}} else if (d['winner'] === "Conservative"){if (-swing*2 > +d['majority']) {seats_gained++;returnColor = "#ff0000";winners_object[cid] = "LAB";} else {returnColor = "#87cefa";winners_object[cid] = "CONhold";}}

I’ve highlighted the added line in bold. As you can see, the logic doesn’t change, and we use the constituency ID (cid) as an index.

As said, we pass this object to a function that draws the hex map. Let’s give it a name and in the next section we will see how it works. At the very end of paint_constituencies() add this line:

do_hex(winners_object);

Drawing the hex map

The actual hex map drawing is made very simple by Oli’s map. All we do is to load the constituencies JSON, and set each element. It’s very similar to what we did previously with setting the attributes for the rectangles: an in-line function allows you to directly deal with the hexagons in one go, as if you were enumerating them.

The basic shape of our do_hex() function is as follows:

d3.select("#vis").selectAll("*").remove();d3.json("maps/constituencies.hexjson", function(error, hexjson) {// do something}

As we did before, we call remove() because we are redrawing the hex map from scratch every time we move the slider. The content of the d3.json block are basically allowing us to manipulate the json as an object. The first thing we need to do is to actually instantiate the svg elements. To do so, we could also add some extra configuration attributes, for example adding margins to the svg map, but to keep things simple we do four things:

  1. add the svg block
  2. render the hexagons using Oli’s library
  3. bind our data to the hexagons
  4. style the hexagon.

Let’s see these steps one at a time. First of all, as we’ve seen in the previous post, adding the svg block is a simple operation:

var svg = d3.select("#vis").append("svg").append("g");

Here we also add a “g” element as we’ll be grouping the sub-elements. I suggest you look at the elements view in your developer tools after you call this function.

Secondly, the rendering is a one-liner:

var hexes = d3.renderHexJSON(hexjson, width, height);

which gives us the hexes in a variable that we can now pass to the binding function:

var hexmap = svg.selectAll("g").data(hexes).enter().append("g").attr("transform", function(hex) {return "translate(" + hex.x + "," + hex.y + ")";});

To be very tidy, you could remove the last call to .attr() — I often put all the styling together, all the structural stuff together, etc… Next, we need to style the hexagons, so we can use the hexmap variable and do a function-based styling as we did for our rectangles:

hexmap.append("polygon").attr("points", function(hex) {return hex.points;}).attr("stroke", "white").attr("stroke-width", "2").attr("fill", function(hex) {var party = winners[hex.key];var colour = "#000000";if (party === "LAB")colour = "#ff0000";else if (party === "CON")colour = "#0000ff";else if (party === "LABhold")colour = "#f08080"else if (party === "CONhold")colour = "#87cefa";return colour;});

The important bit here is to see how we identify an hexagon: we use hex.key, which is the key to the object we set above; in this case, the key will be the constituency ID. We use the ID to query the winners object and get the status of that constituency, which we use in turn to set the colour.

It’s done!

What next? Well, as I’ve said this isn’t particularly efficient. We actually don’t need to go through the two arrays, as we could merge them; also, we could find a way to avoid re-drawing the map and the swing viewer every time. Stay tuned ;-)


Published by HackerNoon on 2017/05/22