I the last few months I’ve become very interested in data visualizations and I spent a lot of time working with d3.js. D3 is an amazing javascript library used to manipulate data and has tools to create almost every kind of chart or visualization imaginable. The ease of adding interactivity make it more powerful.
I went through all of the examples in the book, Interactive Data Visualization for the Web, written by Scott Murphy. The book goes over a variety of bar and pie charts as well as different approaches for adding and manipulating your data. Check out this post I did that sums up the examples and some of the updates I had to make for d3.js version 4.
Learn By Doing
Getting excited for new datasets available goes hand and hand when learning all about data visualization, in my case anyways. I wanted to expand on some of the examples and tutorials I went through and find my own datasets to explore.
Living in California means I’ve dealt with Earthquakes for much of my life, even though we can go years between actually feeling them. I knew the USGS site had a lot of information about Earthquakes as they happen, especially the Did You Feel It site. When checking out their site I found that they had some great API’s available to work with their datasets. They make them available in GeoJSON which gives you the coordinates so I could map them out on a map.
One of the examples that I worked on in the book used GeoJSON to create a map of a State, Country, or the entire Globe depending on the dataset you use. D3 then draws out a path element for the borders. There are varying levels of detailed files that you can get and either draw out each country individually or as one object. For this project I opted to do the entire globe as one object since it would draw faster and I was more interested in the Earthquakes themselves.
g.insert('path', '.graticule')
.datum(topojson.feature(world, world.objects.land))
.attr('class', 'land')
.attr('d', path)
.style('fill', 'steelblue')
Earthquakes v1.0
Before I could get to the earthquake data I needed a way to represent them on the map. In this example I don’t really need each individual country drawn out so I use .datum(topojson.feature(world, world.objects.land)). This way the land is drawn out as one object and speeds up that part of the process. Next I needed to plot of the earthquakes from the USGS API.
Fortunetly this part is easy. Within the callback of the d3.json for the world map I used another d3.json call to the USGS API for all of the earthquakes in the last 24 hours. They have several variations including
- Past Hour
- Past Day
- Past 7 Days
- Past 30 Days
And each of these feeds have magnitude options
- Significant Earthquakes
- M 4.5+ Earthquakes
- M 2.5+ Earthquakes
- M 1.0+ Earthquakes
- All Earthquakes
I had already decided on the all earthquakes over the last day feed. Their feed is a standard GeoJSON and you can see the details here.
Once you have the data you just need to append circles using the geometry data.
.attr('cx', function(d) {
return projection([d.geometry.coordinates[0], d.geometry.coordinates[1]])[0];
})
.attr('cy', function(d) {
return projection([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1];
})
I’ve skipped some of the basic things but you can see my full code in my pen at the bottom.
See the Pen Earthquakes in the last day by Anthony Skelton (@ajskelton) on CodePen.
Adding Animation
I had all of the quakes displaying, but I wanted some way to have the circles show up in the order that they happened. If there were twenty quakes but one an hour in an area, that would be very different all twenty in just one hour.
Now I needed to first set the radius of all of the quakes to zero and have them expand to the correct size over time. But the problem was I needed to find a way figure out how long to set between each quakes reveal. Each quake has the a time object in their properties. I decided to start with a new date object that was 24 hours ago.
// Setup 24 hours ago object
var dateObj = new Date();
dateObj.setDate(dateObj.getDate() - 1);
Next, I loop through my array of quakes and find the difference between the time object of the current quake and my starting point of 24 hours ago.
// Function that calculates the difference between the earthquake and 24 hours ago and
// creates a delay property.
var setQuakeDelay = function() {
for(var i = 0, max = quake.length; i < max; i++){
var timeDiff = quake[i].properties.time - dateObj;
var timeDiffObj = new Date(timeDiff);
quake[i].delay = Date.parse(timeDiffObj) / 5000; // Speed up the animation, otherwise it would take 24 hours ><
}
}
setQuakeDelay();
Now each quake has a new property of delay that is a number of seconds from the starting point that the earthquake happened. I divided the number by 5000 to speed up the process.
The next step is animating all of the quake circles with their new delay.
// Changes the radius of the earthquakes to their magnitue using a transition
// and the delay created from the setQuakeDelay function
var quakeCircles = svg.selectAll('.quake-circle')
.data(quake)
.transition()
.delay(function(d) {
return d.delay;
})
.duration(1000)
.attr('r', function(d) {
if(d.properties.mag < 0) {
return 0.1;
} else {
return d.properties.mag
}
});
Perfect! Now the circles are all animating from 0 to their magnitude. I wanted to draw the viewers attention to each quake as it appears but some of the circles are very small. I decided to add another circle that pulses out and dissolves. I append these similar to the original circles. I later realized I needed the actual quakes to be added second so they are on top of the pulse. This matters for the text title tooltip that I add on to each quake.
I animate the pulse circles similarly to the quake circles, except that the size of the circle is multipled by a factor of 8. I also remove the pulse circle afterwards to clean up the DOM.
// Changes the radius of the pulse circle to eight times the magnitude
// and fades out as it expands over two seconds
var pulseCircles = svg.selectAll('.pulse-circle')
.data(quake)
.transition()
.delay(function(d) {
return d.delay;
})
.duration(2000)
.attr('r', function(d) {
if(d.properties.mag < 0) {
return 0.1 * 8;
} else {
return d.properties.mag * 8;
}
})
.style('opacity', 0)
.remove()
Adding an Timeline Axis
It’s great that the quakes are happening at roughly the right time increments over the last 24 hours, but I wanted to have a clock or something so you could see the pace of the quakes as they are happening.
After I append the world map I add an x-axis along the top of the map. First I set my variable x to d3.scaleTime() using the 24 hour ago object as the domain. This way my ticks represent the hours starting at the correct time.
// Create the x scale based on the domain of the 24 hour ago object and now
var x = d3.scaleTime()
.domain([dateObj, new Date()])
.range([0, width - margin.right - margin.left]);
// Append the xAxis on top
var xAxis = svg.append('g')
.attr('id', 'xAxis')
.attr('transform', 'translate(20, 20)')
.call(d3.axisTop(x));
I create a red dot similar to my earthquakes as a progress marker along the timeline, but I need to set the length of time it should take to traverse the axis. For this I grab the longest delay property of an earthquake by accessing the last quake in my array.
var longestDelay = quake[quake.length - 1].delay;
This is the longest delay an animation should take, so that’s a great place to start. I could have tried to make this more accurate but I opted to just add 1000 ms and wrapped up the project. I would like to next build in the option to repeat the animation, and also zoom in on certain areas, but for now I have version 2 complete.
Click Run Pen to watch all of the earthquakes in the last 24 hours. It’s not responsive, so watch on a larger screen for optimal results.
Earthquakes v2.0
See the Pen Earthquakes in the last 24 hours by Anthony Skelton (@ajskelton) on CodePen.
See the Pen Earthquakes in the last 24 hours by Anthony Skelton (@ajskelton) on CodePen.
I changed up the colors some from v1.0 as a suggestion from my wife. With the darker gray colors for the oceans and land mass it lets the red and white earthquake circles really pop.
Leave a Reply