Responsive charts

Use percentage values to make our bar chart responsive and fill its container element.

Transcript

In the last video, we used D3's scales to make our bar chart fill up the container SVG element. Now, if we look at our SVG element, we see that it's 300 pixels wide by 150 pixels tall - and we didn't set this ourselves, this is just the default value for new SVG elements.

If we look at our final mockup, we see that we'll eventually have three charts above our table, and that they each take up a third of the available space. So, we need to figure out how to size our charts in a way that will let us build this out.

If we come back to our app, and look at our SVG's parent, we can see that this div right here stretches out to fill up the full screen width; and it's currently taking on a height of 150, because its child - the SVG element - has a height of 150 pixels.

What we want to do is make our SVG container stretch to fill up its parent div element, and that way we can just use plain HTML and CSS to help us with our layouts.

So, instead of just having the SVG take on its default values, let's come to the code, open up our bar chart's template, and we'll make this root svg node stretch by setting its width to 100% and its height to 100%:

<svg width='100%' height='100%'></svg>

And now if we look at the svg element, we can see that it's actually stretching to fill up the parent.

But the bars themselves are not resizing. Let's come to our blog post template, and pass some classes into our bar chart here, so that this parent div has an explicit height and width. We're using Tachyons, which is an atomic CSS library. So we can just add the w4 and h4 classes, which will give us a width of 128 pixels and a height of 128 pixels to this component.

{{bar-chart class='w4 h4'}}

If you've never heard of Tachyons before, I definitely recommend checking it out, and you can also expect ot see a series on it from us soon.

Now if we save this and come back to our app, the SVG is now 128 pixels by 128 pixels, but the bars are all cut off.

This is because our bars' scales are still based on the old values of 300 pixels and 150 pixels. What we want to do is change them to use percentages, just like we did for our root SVG element.

So let's come back to our code, open up the component, and we'll start with the xScale. Right now the range is 0 to 300 - we're going to change this to 100

.range([ 0, 100 ])

And everywhere we use this scale, we're going to write the values as a percentage. So first we'll come down to its width, we'll use a template literal to wrap this bandwitdth method, and then we'll add a percent sign to the end:

.attr('width', `${xScale.bandwidth()}%`)

And we'll do the same thing for the x attribute:

.attr('x', author => `${xScale(author.name)}%`)

Let's save this. And now our bars widths' are correct!

We can see that they're still too tall, so let's come back and fix the yScale.

We'll do the same thing, we'll come here and update its range to be 0 to 100. And then we'll come down to the height, and wrap the calculation in a template string with a percent sign:

.attr('height', author => yScale(author.count))

And finally we want to fix the y offset. Now it used to be 150 pixels tall (the total height of the chart) minus the scaled bar height, but we want to change this all to percentage calculations. So we can change this to 100 percent minus the scaled bar height (which is also a percent), and then add the percentage sign:

.attr('y', author => `${100 - yScale(author.count)}%`);

Let's save this, come back to our app, and now our bars fill up the SVG element perfectly! And we can actually test this out, we can click on the parent div and change its width and height, and as we do, the bars are scaling correctly. Awesome!

Ok, so now we can come back to our blog post template and get our basic layout working. Let's remove these classes from our bar chart, and use Flexbox to get our three chart containers that will each take up a third of the width.

We'll use a wrapper flex element and then use a flex-auto child, and we'll move our bar chart inside:

<div class='flex'>
  <div class='flex-auto'>
    {{bar-chart}}
  </div>
</div>

If we save this, we can see our chart fills up the whole width, and it even resizes if we change the width of our app!

Let's add two more charts. Alright, now we've got three charts here, but they could use some breathing room. Tachyons has a nice class to add some horizontal padding, so we'll come here and add ph4 (which stands for padding horizontal, and the 4th value of the scale):

<div class='flex'>
  <div class='flex-auto ph4'>
    {{bar-chart}}
  </div>
  <div class='flex-auto ph4'>
    {{bar-chart}}
  </div>
  <div class='flex-auto ph4'>
    {{bar-chart}}
  </div>
</div>

Let's save this - and now our charts are looking much better.


I really like this pattern that we just went over for doing layouts with D3. If you make your visualizations stretch out to fill their parent, you then get to use standard HTML and CSS to actually write your layouts – and most people are familiar with how to do this.

Now, there are times where your visualizations will need to do a bit more work to be responsive, but for us changing to percentage values was enough to get the job done.

In the next video, let's get our charts rendering some real data from our application.

Questions?

Send us a tweet:

Or ask us in Inside EmberMap, our private Slack workspace for subscribers.