Animated storytelling using the Javascript D3 library

On the day I publish this article, the UK goes to the polls in its first December general election since 1923. Representative government is very interesting, and the data on votes cast can often tell a different story from what eventuates in terms of seats in the House of Commons and which party — or coalition of parties — forms a government.

Inspired by Nathan Yau’s original viz and by a fantastic project by these guys — for which I liberally stole and adjusted code — I set about creating a story of all UK general elections since 1918 (which is considered the first modern era election). I had in mind a fairly simple layout where I would have a force directed bubble chart of 1000 eligible voters of the left, where voters would ‘move around’ from election to election, showing how the popular vote distributed. On the right, I would have a simple narrative with a photo of the elected Prime Minister and a short narrative about what happened. Here’s my initial sketch of what I wanted to do.

If you can’t be bothered reading the ‘how’, my full code is here and the visualization is hosted here.

My initial sketch

What do I need?

For this project I’m going to need:

  1. Some data — we’ll need to do some manipulation and put that in a data folder.
  2. Some images — I pulled the photos of all the Prime Ministers from Wikpedia and put them in a folder called img. Each file was named by the election date for convenience.
  3. Some Javascript, which will go in a js folder.
  4. Some css for styling, which will go in a css folder.

Prepping the data

From the UK Parliament library, I was able to get the data on the percent of the vote which went to the various political parties and I was able to combine this with turnout statistics so that I could include those who did not vote in my chart. So my starting csv file is here.

I needed to reimagine how this would look if it were 1000 voters, each represented by a small bubble on a bubble chart. So what I needed to do was to convert these statistics in a dataframe with 1000 rows and 27 columns, with each row being a voter and each column being an election date. Then I coded each vote 0, 1, 2, 3, 4, 5 according to the five party groups and the ‘did not vote’ group. In each cell of my dataframe is the code of the party that person voted for in that election.

Then I needed to add some time series to the data, as it will be used to illustrate people’s movements through time. Making use of the approach used by the work that inspired this, I decided to imagine the 27 elections as a single day in minutes, and to split the time equally over 1440 minutes, so that the end visualization would display each election result for the same amount of time before moving onto the next one.

Finally, I needed to output each person’s data as a comma-separated string where the odd elements represented a political party code and the even elements represented time, as this will be the format that will feed into the web code.

I did this transformation in R and wrote the output as a tsv file which I will later feed into my web code. The R code I used is here and the final tsv output is here.

Setting up the viz in D3

We need to have D3 v3.5.5 available in our js folder. I took the Javascript code used in the American Time Use viz and adjusted it for my purposes here, making a file called election_bubbles.js. First I set a starting speed and chart dimensions, created a list to use later and set the clock at zero:

var USER_SPEED = "fast"; 
var width = 750,    
height = 750,
padding = 1,
maxRadius = 3,
sched_objs = [],
curr_minute = 0;

Next, I defined each activity code as being a political party:

var act_codes = [ 
{"index": "0", "short": "Conservative", "desc": "Conservative"},
{"index": "1", "short": "Labour", "desc": "Labour"},
{"index": "2", "short": "Liberal/Liberal Democrats", "desc": "Liberal Democrats"},
{"index": "3", "short": "Other", "desc": "Other"},
{"index": "4", "short": "Plaid Cymru/SNP", "desc": "Plaid Cymru/Scottish National Party"},
{"index": "5", "short": "Did not vote", "desc": "Did not vote"},
];

I also set my speed options in milliseconds for use later:

var speeds = { "slow": 200, "medium": 125, "fast": 75 };

Now I used a list calledtime_notes to hold all the display information I need for the visualization. For each election I created key-value pairs as follows:

  • start_minute: The time on our ‘virtual clock’ to start each election display — multiples of 53 plus 1. (53 being a rounded version of 1440/27).
  • stop_minute: The time to stop each election display — 50 virtual minutes after start_minute.
  • year: The date of the election to be displayed at the top of the viz.
  • img: The path to the photo of the elected Prime Minister.
  • color: The border to surround each img— corresponding to the color of the political party the PM belonged to.
  • note: The text narrative to be displayed.

This is a fairly long list so I won’t display it here — you can see it in the linked source code. Also, I will set a variable notes_index = 0 to indicate in later code that I want to start at the first element of the list — the 1918 election.

Next, I want to set the position for “Did not vote” at the center of the viz, and then equally space the five political party groupings in a circle around the center. I want the first party on the list to take the top-right position in the circle and will use my indices to ensure that:

var center_act = "Did not vote", 
center_pt = { "x": 380, "y": 365 };
var foci = {};
act_codes.forEach(function(code, i) {
if (code.desc == center_act) {
foci[code.index] = center_pt;
} else {
var theta = 2 * Math.PI / (act_codes.length-1);
foci[code.index] = {x: 250 * Math.cos((i - 1) * theta)+380, y: 250 * Math.sin((i - 1) * theta)+365 };
}
});

Now I am going to start the initial svg — I plan to position it absolutely within its parent when I finally put it in HTML:

var svg = d3.select("#chart").append("svg")    
.attr("width", width)
.attr("height", height)
.attr('position', 'absolute')
.attr('left', '200px')
.attr('top', '200px');

Running the viz

Now I am going to edit the main viz function to move through the force directed networks for each election. All of the code in this section will be wrapped in a function which will run on our tsv file of 1000 voters, like this:

d3.tsv("data/elec_results.tsv", function(error, data) {}

First, we pull from the tsv to create a list of key-value pairs of voting activity and duration and these will populate out sched_objs list:

data.forEach(function(d) {  
var day_array = d.day.split(",");

var activities = [];
for (var i=0; i < day_array.length; i++) {
if (i % 2 == 1) {
activities.push({'act': day_array[i-1], 'duration': +day_array[i]});
}
}
sched_objs.push(activities);
});

Next, we just steal the code to populate the 1000 nodes of each election and write them onto our initial svg along with the labels. I won’t copy it all here but you can find it at lines 99–175 here.

Now we use the timer() function to run the switches in data as our virtual clock ticks in the background. You’ll find the full timer() function in lines 179–294 here, but within this function, I want to highlight a few things unique to this visualization.

First, we scroll through the dates in the title of the chart by using D3’s transition() function to make text appear rapidly by fading it in from the background color (this is the first time that you’ll see certain style elements appearing like background color and font):

  if (true_minute == time_notes[notes_index].start_minute) {     
d3.select("#year")
.style("color", "#fffced")
.style("text-align", "left")
.style("font-size", "300%")
.style("font-family", "adobe-caslon-pro")
.text(time_notes[notes_index].year)
.transition()
.duration(500)
.style("text-align", "center")
.style("color", "#000000");
}

Similarly, we can fade images in and out by transitioning their opacity:

if (true_minute == time_notes[notes_index].start_minute + 10) {
d3.select("#image").append('img')
.attr('src', time_notes[notes_index].img)
.attr('width', 200)
.attr('height', 250)
.style('position', 'absolute')
.style('top', '100px')
.style('left', '150px')
.style('opacity', 0)
.style("display", "block")
.style("background", time_notes[notes_index].color)
.style("padding", "8px")
.style("border", "1px solid #ccc")
.style("box-shadow", "5px 5px 5px #999")
.transition()
.duration(1000)
.style('opacity', 1);
}

And we can also scroll the notes up and down:

if (true_minute == time_notes[notes_index].start_minute + 10) {
d3.select("#note")
.style("top", "500px")
.style("color", "#fffced")
.style("font-size", "150%")
.style("font-style", "italic")
.transition()
.duration(500)
.style("top", "370px")
.style("color", "#000000")
.text(time_notes[notes_index].note);
}

You’ll see a number of other code segments like this that handle the appearance and disappearance of text and images according to the timing of the movements in the force-directed bubble chart. Also, there’s a function to set the colors of the nodes according to the official colors of the political parties in the UK.

Building the viz

Our Javascript now gives us several animated objects to slot into our webpage.

  • An election date (#year), which I want to have at the top, centered, underneath some other title text
  • A force-directed bubble chart (#chart)which I want to have on the left of the page.
  • An image (#image) which I want on the right, centered above some narrative text
  • Some narrative text (#note)which I want on the right, centered, below the image.

Some simple html alongside some css styling to be found in the css folder will achieve this — here is the general setup which worked for me:

<div class="container" style="width:1600px">
<div class="row">
<img src="img/commons_logo.png" width="400px" class="center">
</div>
<div class="row">
<div style="text-align:center;font-family:adobe-caslon-pro;font-size:200%;font-weight:bold">A Century of UK General Elections</div>
</div>
<div class="row">
<div id="year"></div>
</div>
<div class="row">
<div id="chart" class="col-lg-2" style="position:relative;width:1000px"></div>
<div class="col-lg-2" style="position:relative;width:500px">
<div id="image"></div>
<div id="note" style="text-align:center"></div>
</div>
</div>
<div class="row">
<footer style="font-size:25%;text-align:right">Created by @dr_keithmcnulty. Github: <a href="https://www.github.com/keithmcnulty/uk_election_bubble">keithmcnulty/uk_election_bubble</a>. Source: UK Parliament, Statistica, Wikipedia</footer>
</div>
<div class="row" style="text-align:center">
<div id="speed">
<div class="togglebutton slow" data-val="slow">Slow</div>
<div class="togglebutton medium" data-val="medium">Med</div>
<div class="togglebutton fast current" data-val="fast">Fast</div>
</div>
</div>
</div>
</div>

I inserted this in electionvis.html, which also calls in some bootstrap styling and my JS scripts. This was then inserted as an iframe into the main index.html to allow for controlling of the overall height and width of the viz if I needed.

The finished product

Here is what the finished product looks like:

See the full thing here

Working in Javascript takes some learning, but D3 is an incredibly flexible tool for building data-driven storytelling, and totally worth the effort in my opinion. Plus there is a lot of pre-built code out there ready for you to repurpose for your own aims, just like I did here.

Leave a Reply

%d bloggers like this: