Our first chart
Install D3.js and write our first simple chart in an Ember app.
Transcript
In this video we're going to dive into an existing application, add D3, and write our first chart. Let's take a look at the app we'll be working with.
This is a CMS backend for managing data that powers a media website. We'll be focusing on the Blog posts route here, which lists out all of our site's blog posts in this table. The headers of the table are also sortable, so that our users can find what they're looking for.
Now, some coworkers of ours who use this app came to us and asked if they could see some charts above the table, and they showed us this sketch.
They want to see some bar charts that show the authors, the categories, and the comment counts of all the blog posts. So this is what we're going to be building.
Ok, to start let's add D3 to our project. We'll come over to our terminal, stop our server, and we'll install the ember-d3
addon
ember install ember-d3
This addon adds D3's packages to our Ember app so that we can import them into our application code.
Next, looking at this sketch, we know that we're going to be building these three bar charts and that they'll go above the table - so let's go ahead and come to our terminal and we'll generate a bar-chart component that we'll be able to work with:
ember g component bar-chart
Now we can start the server again and come to our application code.
Here we're looking at the posts
route which is where our Blog Posts table is being rendered. Let's go ahead and render a bar-chart
component here above the table, so that we can see it while we actually build it out.
Now let's split our screens so we can see both the code and the app at the same time.
Ok, so to start using D3 to write our chart, we need to give our component a root SVG element. So let's open our component's template and we'll just drop an svg
element right here.
If we come to our app we can't see anything, but if we open the inspector we can actually see that there is the component instance being rendered right here with this root svg
element. So, our component is right here, and we'll be able to see its progress while we build it out.
Now let's open the component file and start writing some D3 code. We want to start by getting something simple on the screen.
First, we'll import the select
method from the d3-selection
module:
import { select } from 'd3-selection';
Next, we'll use the didInsertElement
hook to render our bar chart. We want to create a D3 selection of our root SVG element, so we'll use the select
method that we imported and we'll pass in this.$('svg')
. Now, this jQuery selector returns an array of elements but we just want that single root SVG, so we'll just pass in the first element. And now we have a selection.
Let's store this selection in a variable called svg
, and now we can start using the other D3 selection methods.
Let's draw a single bar - we can call .append
to add a rectangle, and then .attr
to give it a width of 20 and a height of 100. And there we see the rectangle in our app.
We can do this again, and this time we'll offset the second one by 25 pixels, by setting the x
attribute. And we can change the height to 50. And this is basically how we're going to be building out our chart.
didInsertElement() {
let svg = select(this.$('svg')[0]);
svg.append('rect')
.attr('width', 20)
.attr('height', 100);
svg.append('rect')
.attr('width', 20)
.attr('height', 50)
.attr('x', 30);
}
Now, instead of hard-coding these values, we actually want to work with some data. Ao, let's paste in an array of authors here:
authors: [
{ name: 'Mark Twain', count: 15 },
{ name: 'Virginia Woolf', count: 42 },
{ name: 'John Steinbeck', count: 23 },
{ name: 'Ralph Ellison', count: 27 }
]
Each author has a name and a count, and this is what we'll use to dynamically render our bar chart.
Now to create a bar for each author in this array, we need to create a data-bound D3 selection. Let me show you how to do that.
So first let's get rid of these two rectangles. And we'll start with our SVG selection; we'll call .selectAll('rect')
- and this gives us an array of rects that we can bind to. So then we'll call .data()
, and we'll just pass in our authors array.
And now we have a data-bound D3 selection which we can use to dynamically generate our bar chart.
Now, we're writing a lot of D3 methods here, and if you don't know them, don't worry about it, because we're going to cover them in detail later in the series.
Now, to work with our data's initial render, we'll call .enter()
on our data-bound selection. And then we can do exactly what we did before - we can append a rect
with a width of 20 and height of 100. But this time, we're rendering one rectangle for each author in our array.
Let's save and see what our app looks like. It looks like we only have one bar here, but if we open up the inspector, we actually see all 4 of them right here - they're just stacked on top of each other right now.
I find that when writing D3 code I use the inspector a lot, because seeing the actual output of the SVG that you're generating really helps you understand what your code is doing.
So, we do have four bars - but they have no offset from each other. What we want to do is render the first author's bar first, the second author's bar a little bit over, the third author's bar a little bit more over and so on.
So we can do this by setting the x attribute of each bar, but we want to set it using the index of the author that we're currently working with. Well, because we're working with a data-bound selection here, besides setting a static value like we're doing with the width and height, we can actually set a dynamic value using a function.
The function takes two parameters: the first is the author that's bound to this particular rectangle, and then the second is the index of the author in the original array. Then the return value of this function will get used for the x
attribute. So, we want each bar to be offset by some amount - say 25 pixels - multiplied by its index. This way the first bar will have a 0 offset, the second bar will have a 25 pixel offset, the third bar a 50 pixel offset and so on.
If we save this, we now see our four bars. Now the heights are currently 100 - but we also want to set them in a data-driven way, using the count
property from each one of our authors. So, let's update our height
attribute to be a function as well. We'll just pass in the first parameter, the author (since we don't need the index), and we'll return the author's count.
We'll save that - and now we have a bar chart that's being rendered from the local data in our component.
let svg = select(this.$('svg')[0]);
svg.selectAll('rect')
.data(this.get('data'))
.enter()
.append('rect')
.attr('width', 20)
.attr('height', d => d.count)
.attr('x', (d, i) => i * 25);
Alright - so we have something working! But our bar chart is looking a little small. In the next video we'll use D3's scales to fix this.