When I was learning D3, although there are a lot of great resources to learn from, I can rarely find detailed code explanations for me to fully understand how everything works, making it difficult to recreate a chart on my own without additional searches. So I want to fill this whitespace with a series of D3 (V6 starting this post) line-by-line code explanations for each chart I create, sharing with you what I’ve figured out.
In this post, I am going to show you how to build a (much-anticipated) basic flourish-type animated bar chart with D3.js, minus the interpolation which we will cover in our next post. It’s an eye-catching way to showcase changes over time that marketers and journalists love to use.
Viz goal: Show the top 20 most expensive US universities by in-state tuition & fees from 2009 to 2017.
Data: Tuition Tracker
Resources: Complete source code | Data prep Jupyter notebook + Raw Data
Visualization Strategy
Looping through each year:
- we first remove the existing elements that are not under the current selection from the DOM using
exit()
- then enter the elements under current selection and don’t exist on the DOM yet using
enter().
- finally, we use
merge()
to merge existing (but not removed) and new elements and transform them together.
Set up the Canvas
To set up the canvas for D3 graphs, in your HTML file, between <html></html>
tags:
- Line 3: Load D3 directly from d3js.org — so you don’t need to install locally and you can use this HTML code in your webpages.
- Line 4: Load the javascript document we will put D3 code in.
- Line 9–28: Style section to style different elements. Note code like
.yAxis
is to call and style the ‘yAxis’ class we will define later. - Line 32:
onload= “animatedBar()”
means we are telling the system to load theanimtedBar()
function immediately to show D3 graphs after the page has been loaded. - Line 33–35: Create a button to call the
animatedBar()
function on click so we can restart the animation. - Line 37–38: Create a
SVG
in the size of 1200px by 600px for us to put graphic elements in later.
The following code is put between <script></script>
tags after body in HTML or in a separate .js file.
Read Data and Set Up the Loop
Line 3–9: Read CSV file and call the createdBar() function
with d3.csv().then().catch()
. This is new in V6 vs. V4.
Line 11: Create createBar(data)
function to draw charts and create transitions.
Line 13: Assign d3.select(“svg”)
to svg
so we don’t need to keep retyping the same command later.
Line 16–18: Append “g” to group all axis elements (labels+ticks) so we can move them together .attr(“transform”,“translate(400,20)”)
— 400px along x-axis and 20 along y-axis (origin (0,0) of the SVG Canvas is at the top-left corner). We will select this class and call the yAxis function later within the loop, as yAxis elements (labels+ticks) will change.
Line 22–32: Set up the looping function. We use the setInterval()
function to call the draw(data)
function we will create next and filter the data with each year from 2009 to 2017 every 3000 ms. Stop the loop using clearInterval()
when the year hits 2017 so it doesn’t keep running. Use the button we created we can restart the loop as the outer animatedbar()
function gets called again.
Prep Data and Axis
We start drawing the chart within the draw(data)
function.
Line 4–5: For each year’s data passed through, we need to sort the data by Tuition column with data.sort()
in descending order (a,b) =>b.Tuition — a.Tuition
and get the top 20 .slice(0,20)
.
Line 8: Scale for the x-axis, which is the length of the bar since we don’t have an axis on this chart. Map [0, the max tuition] to a range that works for the canvas.
Line 11–12: Create a color scale to fill the bars based on Tuition $ amount. We don’t need to go from 0 in this case, just get the range using d3.extent()
since the shades only need to be relative.
Line 15: d3.scaleBrand()
is used for scales of ordinal/categorical variables. By default, the range is divided equally among the elements of the domain. I want the axis to be drawn from top-down from 20px to 500px, the school names will be positioned equally in between minus the paddings.
Line 18: Set up the yAxis
function we will call later. d3.axisLeft()
is a function that will create a vertical axis, ticks will be drawn from the axis towards the left, labels will be on the left side of the axis as well. tickSizeOuter(0)
makes the size of ticks at the ends of the axis 0.
Draw Bar Chart + Create Transitions
Line 2–5: Every time a year’s data is passed through, we select the existing y-axis class and call yAxis to draw the new yAxis. .duration(2000)
for a gradual transition effect.
Line 8–9: To draw bards, we first select .bar
class, and pass the sortedData
through with Name
column as key, one bar for one key.
Line 11–15: We then remove any bars (schools) not under the current selection but already exist on the DOM using .exit()
by gradually .duration(1000)
turning the width to 0 before completely remove them .remove()
Line 17–23:
- Append any bars (school) under the current selection but don’t exist on the DOM. Set the x position to be 1px right of the y-axis
.attr(“x”,401)
Determine y position using yScale of each school and make the bar middle of the tick by adding up thebandwidth()
of yScale. Bandwidth is determined by equally distribute the y axis range minus the paddings in between to bars. This way, new bars will emerge from the right starting point. - Initially, the width of new bars (length) is set to 0. And then after merging the new bars using
.merge()
with the existing bars that didn’t get removed because they still exist under the current selection, we set them all to the right y-position, width, and color. - For new bars, the y-position is already set right. They will emerge from the y-axis towards the right as the bar grows. (e.g. Colgate University when it’s first created). For existing bars, they will move to a new y-position depends on their rank (e.g. Reed College) while setting to the right width.
We use the same logic to create and position labels and titles. It should be pretty self-explanatory from here. Let me know if you have any questions.
One thing to call out is, for the title we use slice(0,1)
to only get the first element of the array — the year.