News

Build animated bubble visualisations with D3.js

The developers at LRXD reveal the technologies, methods and code behind crafting the animated timeline elements found on cadenceandcause.com

BubbleAnimation

The developers at LRXD reveal the technologies, methods and code behind crafting the animated timeline elements found on cadenceandcause.com

BubbleAnimation

GET THE TUTORIAL CODE

The animated timeline

Cadence & Cause features animated timelines that use D3 (d3js.org) to animate campaign data over time. To get started with visualising you don’t have to write a huge data collection or aggregation code. Here we’ll use a public dataset to demonstrate how these timeline elements were created.

Set up the HTML

We’ll be loading CSV data with AJAX, so make sure you have a local web server running. Let’s use Bootstrap (getbootstrap.com) to get a CSS layout going, before dropping in some containers for the graph and the labels. Download the Significant Volcanic Eruptions dataset from Tableau Public (tinyurl.com/pdzrnoc) and save the XLSX data in CSV format.

Load the data

In our JavaScript begin by declaring some variables. The d3.scale.category20() function gives us some colours to work with. D3 has a CSV loader and parser, so we can use it to easily load our data. We’ll also pass in a callback to run our visualizeData() function once the parsed data is available to us:

001 $(function () {             
002 var data;
003 var nodes = [];
004 var throttledNodes = [];
005 var graph;     
006 var width = 800;      
007 var height = 600;        
008 var throttleIndex = 0;     
009 var throttleNum = 10;    
010 var timer;    
011 var labels = [];
012 var color = d3.scale.category20();
013 d3.csv('/csv/significantvolcanoeruptions.csv',    
014 function (csvData) {    
015 data = csvData;    
016 visualizeData();    
017 }        
018 );    

Prepare components

Now we need to ‘massage’ our data into a useable format. Iterate over each row of CSV data and stash what we need in the nodes array. Use D3 to add an SVG element that will work as our canvas. We’ll use D3’s layout to create some rules. The throttledNodes array is empty, but will populate with data points later:

001 var visualizeData = function () {    
002 data.forEach(function (val, index,     array) {    
003 var node = {};
004 node.date = new Date(val['Year'],     1, 1, 0, 0, 0, 0);    
005 node.type = val['Type'];        
006 if ('' == node.type) node.type =     'Type N/A';    
007 node.amount = val['Volcano         Explosivity Index (VEI)'];    
008 node.radius = node.amount * 2;
009 nodes.push(node);        
010 if (labels.indexOf(node.type) ==     -1) labels.push(node.type);    
011 });    
012 nodes.sort(function(a, b) {return     a.date – b.date;}); 
013 var svg = d3.select("#volcano-    graph").append("svg")
014 .attr('width', width)    
015 .attr('height', height);

Set up the animation rules

The D3 Force Layout function calculates where each bubble needs to go, triggering a tick event for rendering each animation frame. We then set up a listener to apply the calculated values to the page elements. We’re using the d3.geom.quadtree tool to subdivide the graph into sections. The visit function will go through each section and run a collision handler (from bl.ocks.org/mbostock/3231298).

001 var force = d3.layout.force()
002 .gravity(0.04)            
003 .charge(0)     
004 .nodes(throttledNodes)        
005 .size([width, height]);         
006 force.on("tick", function(e) {
007 var q = d3.geom.quadtree(throttledNodes),        
008 i = 0,    
009 n = throttledNodes.length;    
010 while (i < n) {     
011 q.visit(collide(throttledNodes[i]))    ;         
012 i++;     
013 }
014 svg.selectAll(".data-circle")
015 .attr("cx", function(d) { return     d.x; })        
016 .attr("cy", function(d) { return     d.y; });     
017 });        
018 var collide = function (node) {     
019 //handle collisions here
020 };

Show the data

The addCirces function copies nodes from the nodes array into the throttledNodes array in chunks. This lets us trickle in the circles. We also need to let the Force Layout object know to apply the rules to the new nodes. Finally, use a timer for adding circles over time:

001 var addCircles = function(){     
002 var adds = nodes.slice(throttleIndex, throttleIndex +=     throttleNum)                 
003 throttledNodes = throttledNodes.    concat(adds);
004 svg.selectAll('.data-circle')     
005 .data(throttledNodes)
006 .enter()
007 .append("circle")
008 .attr("r", function(d) { return     d.radius; })                
009 .attr('class',function(d){ return     'data-circle'; })    
010 .style("fill", function (d, i) {     return color(labels.indexOf
011 (d.type));});    
012 if (throttledNodes.length >= nodes. length) {    
013 clearInterval(timer);
014 }    
015 force.nodes(throttledNodes);    
016 force.start();    
017 }
018 timer = setInterval(addCircles,     100);

Label the graph

Now what does each colour represent? We’ll fix any confusion by adding a key to our graph.

001 labels.forEach(function (val) {     
002 $('#labels').append(
003 '<p style="border-left: 20px solid  ' + color(labels.indexOf(val))
004 + ';padding-left: 10px;">'    
005 + val + '</p>');    
006 });    
007 }});
×