I created an animated chart using D3.js. Last time I built something, I wrote about it, and people seemed to like it. So, now I'm writing about building my chart. It looks simple on the surface, but under the hood, it's...still pretty simple.
The finished chart is here on Codepen.
Topics
- Background
- SVG
- D3.js
- Animation with GSAP
Background
I created this chart to complete the Visualize Data with a Bar Chart assignment from Free Code Camp. The criteria were straightforward:
- You must use D3.js to build this project
- I can see US Gross Domestic Product by quarter, over time
- I can mouse over a bar and see a tooltip with GDP amount and exact year and month that bar represents
Easy, except for the fact that I'd never used D3.js.
SVG
D3.js makes heavy use of SVG, so I took some time to familiarize myself with SVG syntax before jumping in. I was already aquainted with SVG, but I'd never created an SVG graphic from scratch. To me, SVG was just a great file format to use for saving logos and other simple images. Turns out, it's useful for more than that.
Since I was already planning on using Greensock's GSAP to animate the chart, I ended up getting pretty much everything I needed to know from GreenSock's svg animation tutorial. After 30 minutes of messing around in Codepen, I had the basics: x-position, y-position, width, height, fill. Everything else was pretty easy to pick up along the way.
Here's pretty much all the SVG I needed to know before starting this assignment:
<svg version="1.1" width="260" height="200" xmlns="w3.org/2000/svg" xmlns:xlink="w3.org/1999/xlink">
<rect x="[X-COORDINATE]" y="[Y-COORDINATE]" width="[WIDTH]" height="[HEIGHT]" fill="[COLOR]" />
</svg>
While I was working, I also picked up on the concept of SVG groups (<g>) which are used to contain multiple SVG elements (like groups in Photoshop) and SVG text (<text>) which renders text. Also, it took me a while to figure out that only groups can be used to contain other elements. For example, if I wanted to place some text in a rectangle:
<!-- WON'T WORK -->
<rect x="100" y="100" width="100" height="100">
<text>Hello World!</text>
</rect>
<!-- WILL WORK -->
<g>
<rect x="100" y="100" width="100" height="100">
<text x="150" y="150">Hello World!</text>
</g>
Obviously, SVG gets a lot more complicated than that, but that just about covers what I needed to know for this project. Overall, SVG is a lot more approachable than I initially thought. I've been wanting to try SVG animation for over a year, but have always been intimidated by the complexity of more advanced animations and never bothered to start learning. If you want to learn, just go for it.
SVG Resources I Used
D3.js
I first heard about D3.js over a year ago. I didn't figure out what it actually was until I did this project. For a year, all I knew was that D3 is used to visualize data; I didn't understand how. Why not just use HTML and CSS? Why not just write out the SVG? Hah! How naive I was. I'm still not in love with D3 (yet), but I definitely appreciate what it's able to do.
The basic idea of D3.js: Turn data into DOM elements. If you have an array of names, for example, you can use D3 to select/create a bunch of <p> elements and insert your data into them. This would look something like:
const list = d3.select('#list');
const data = ['Mike', 'Jill', 'Jack'];
// Loops through all names and returns <p>NAME</p> for each name
list.append('p')
.data(data)
.text(name => name);
Which will result in this HTML:
<div id="list">
<p>Mike</p>
<p>Jill</p>
<p>Jack</p>
</div>
Of course, you wouldn't need D3.js to do something as simple as list some data. So, we introduce some more complex data and some SVGs so we can really visualize our data. Let's say we want to chart comic book prices over time. Here is a simplified version of what the JS would look like:
const chart = d3.select('#chart');
const data = [
{date: '1/2/1993', price: 0.89},
{date: '2/3/2004', price: 1.06},
{date: '3/4/2015', price: 1.09}
]
const x = // Code to set the range and domain of x-coordinates...
const y = // Code to set the range and domain of y-coordinates...
const group = chart.selectAll('g')
.data(data)
.enter().append('g')
group.append('circle')
.attr('cx', d => x(d.date)) // Set x-position based on domain, range, and date
.attr('cy', d => y(d.price)) // Set y-position based on domain, range, and price
.attr('r', 5) // Set radius of circle
This will give you a fairly simple chart of your data. It may seem a little confusing if you're looking at D3.js code for the first time. That's okay, I just want to give you an idea of the basic structure. Once you take a look at a tutorial or some docs, it will become pretty clear. I used this tutorial, but it's for version 3 of D3.js. I was using version 4 (the current version), so things got a little confusing when I encountered deprecated code.
If you work with a lot of data or think you might like to, I definitely recommend looking into D3.js. Right now, I'm using Marvel's API to chart some comic book data. Fun!
Edit: I finished that Marvel chart and it's available here.
D3.js Resources I Used
Animation with GSAP
I've been in love with UI animation for a few years now. I started out with horrible jQuery animations that slaughtered performance. From there, I moved to CSS3 transitions and dabbled in keyframes. I got pretty comfortable there, but complex animations with CSS are a serious pain. Then, I saw this talk by animation expert Sarah Drasner and I knew I had to give GSAP a shot.
GSAP is a collection of libraries that make web animation magically simple. I heard about it a year or so ago and ignored it because I was still climbing the CSS animation mountain. Turns out, it's pretty good.
How good is it? The animation on my chart required only 3 lines of code:
const rects = $('.chart__bar'); // Select my data bars
const t = new TimelineMax(); // Create a timeline with GSAP TimelineMax
t.staggerFrom(rects, .5, {attr: {height: 0}}, .005}; // Animate each bar's height over .5 seconds, staggered by .005 seconds
After a bit of thought, I could probably do this with CSS animations. It wouldn't be nearly as clean and simple, though. Since GSAP is fairly performant, there's really no reason to go through the struggle of CSS animations unless the animation is a simple UI change.
I'm not fully hopping on the GSAP train just yet, but it's definitely gotten my interest. I'll keep experimenting with it and report back.
Animation Resources I Used
That's all for now. By the time I published this story, I also finished this scatterplot chart. Right now I'm working with Express microservices. I'll likely write a little about that soon.