Scott Murray

Tutorials > D3 > Making a bar chart

Making a bar chart

Last updated 2015 September 15

Now we’ll integrate everything we’ve learned so far to generate a simple bar chart with D3.

We’ll start by reviewing the bar chart we made earlier using div elements. Then we’ll adapt that code to draw the bars with SVG instead, giving us more flexibility over the visual presentation. Finally, we’ll add labels, so we can see the data values clearly.

The Old Chart

Here’s what we had last time, with some new data.

var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
                11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];

d3.select("body").selectAll("div")
    .data(dataset)
    .enter()
    .append("div")
    .attr("class", "bar")
    .style("height", function(d) {
        var barHeight = d * 5;
        return barHeight + "px";
    });
1

It may be hard to imagine, but we can definitely improve on this simple bar chart made of divs.

The New Chart

First things first, we need to decide on the size of the new SVG:

//Width and height
var w = 500;
var h = 100;

(Of course, you could name w and h something else, like svgWidth and svgHeight. Use whatever is most clear to you. JavaScript has a cultural fixation on efficiency, so you’ll often see single-character variable names, code written with no spaces, and other hard-to-read, yet programmatically efficient, syntax.)

Then, we tell D3 to create an empty SVG element and add it to the DOM:

//Create SVG element
var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);

This inserts a new <svg> element just before the closing </body> tag, and assigns the SVG a width and height of 500 by 100 pixels. This statement also puts the result into our new variable called svg, so we can easily reference the new SVG without having to reselect it later using something like d3.select("svg").

Next, instead of creating divs, we generate rects and add them to svg.

svg.selectAll("rect")
   .data(dataset)
   .enter()
   .append("rect")
   .attr("x", 0)
   .attr("y", 0)
   .attr("width", 20)
   .attr("height", 100);

This code selects all rects inside of svg. Of course, there aren’t any yet, so an empty selection is returned. (Weird, yes, but stay with me. With D3, you always have to first select whatever it is you’re about to act on, even if that selection is momentarily empty.)

Then, data(dataset) sees that we have 20 values in the data set, so it calls enter() 20 times. enter(), in turn, returns a placeholder selection for each data point that does not yet have a corresponding rect — which is to say, all of them.

For each of the 20 placeholders, append("rect") inserts a rect into the DOM. As we learned in the SVG primer, every rect must have x, y, width, and height values. We use attr() to add those attributes onto each newly created rect.

Beautiful, no?

2

Okay, maybe not. All of the bars are there (check the DOM of the demo page with your web inspector), but they all share the same x, y, width, and height values, with the result that they all overlap. This isn’t a visualization of data yet.

Let’s fix the overlap issue first. Instead of an x of zero, we’ll assign a dynamic value that corresponds to i, or each value’s position in the data set. So the first bar will be at zero, but subsequent bars will be at 21, then 42, and so on.

.attr("x", function(d, i) {
    return i * 21;  //Bar width of 20 plus 1 for padding
})
3

Here’s that code in action.

That works, but it’s not particularly flexible. If our data set were longer, then the bars would just run off to the right, past the end of the SVG! Since each bar is 20 pixels wide, plus 1 pixel of padding, then a 500-pixel wide SVG can only accommodate 23 data points. Note how the 24th bar here gets clipped:

4

It’s good practice to use flexible, dynamic coordinates — heights, widths, x values, and y values — so your visualization can scale appropriately along with your data.

As with anything else in programming, there are a thousand ways to achieve that end. I’ll use a simple one. First, I’ll amend the line where we set each bar’s x position:

.attr("x", function(d, i) {
    return i * (w / dataset.length);
})

Notice how the x value is now tied directly to the width of the SVG (w) and the number of values in the data set (dataset.length). This is exciting, because now our bars will be evenly spaced, whether we have 20 data values

5

or only five:

6

Here’s the working code so far.

Now we should set the bar widths to be proportional, too, so they get narrower as more data is added, or wider when there are fewer values. I’ll add a new variable near where we set the SVG’s width and height

//Width and height
var w = 500;
var h = 100;
var barPadding = 1;  // <-- New!

and then reference that variable in the line where we set each bar’s width. Instead of a static value of 20, the width will now be set as a fraction of the SVG width and number of data points, minus a padding value:

.attr("width", w / dataset.length - barPadding)
7

It works! The bar widths and x positions scale correctly whether there are 20 points, only five

8

or even 100:

9

Finally, we encode our data as the height of each bar. You would hope it were as easy as referencing the d data value when setting each bar’s height:

.attr("height", function(d) {
    return d;
});
10

Hmm, that looks funky. Maybe we can just scale up our numbers a bit?

.attr("height", function(d) {
    return d * 4;  // <-- Times four!
});
11

Alas, it is not that easy — we want our bars to grow upward from the bottom edge, not down from the top — but don’t blame D3, blame SVG.

You’ll recall from the SVG primer that, when drawing rects, the x and y values specify the coordinates of the upper-left corner. That is, the origin or reference point for every rect is its top-left. For our purposes, it would be soooooo much easier to set the origin point as the bottom-left corner, but that’s just not how SVG does it, and, frankly, SVG is pretty indifferent about your feelings on the matter.

Given that our bars do have to “grow down from the top,” then where is “the top” of each bar in relationship to the top of the SVG? Well, the top of each bar could be expressed as a relationship between the height of the SVG and the corresponding data value, as in:

.attr("y", function(d) {
    return h - d;  //Height minus data value
})

Then, to put the “bottom” of the bar on the bottom of the SVG, each rects height can be just the data value itself:

.attr("height", function(d) {
    return d;  //Just the data value
});
12

Let’s scale things up a bit by changing d to d * 4. (Note: Later we’ll learn about D3 scales, which offer better ways to accomplish this.)

13

Here’s the working code for our growing-down-from-above bar chart.

Color

Adding color is easy. Just use attr() to set a fill:

.attr("fill", "teal");
14

Here’s an all-teal bar chart. But often, you’ll want a shape’s color to reflect some quality of the data. That is, you may want to encode the data as color. (In the case of our bar chart, that makes a dual encoding, in which the same data value is encoded in two different visual properties: height and color.)

Using data to drive color is as easy as writing a custom function that again references d. Here, we replace "teal" with a custom function:

.attr("fill", function(d) {
    return "rgb(0, 0, " + (d * 10) + ")";
});
15

Here’s that code. This is not a particularly useful visual encoding, but you can get the idea of how to translate data into color. Here, d is multiplied by 10, and then used as the blue value in an rgb() color definition. So the greater values of d (taller bars) will be more blue. Smaller values of d (shorter bars) will be less blue (closer to black).

Labels

Visuals are great, but sometimes you need to show the actual data values as text within the visualization. Here’s where value labels come in, and they are very, very easy to generate with D3.

You’ll recall from the SVG primer that you can add text elements to an SVG element. We‘ll start with:

svg.selectAll("text")
   .data(dataset)
   .enter()
   .append("text")

Look familiar? Just as we did for the rects, here we do for the texts. First, select what you want, bring in the data, enter the new elements (which are just placeholders at this point), and finally append the new text elements to the DOM.

We’ll extend that code to include a data value within each text element by using the text() method

   .text(function(d) {
        return d;
   })

and then extend it further, by including x and y values to position the text. It’s easiest if I just copy and paste the same x/y code we used above for the bars:

   .attr("x", function(d, i) {
        return i * (w / dataset.length);
   })
   .attr("y", function(d) {
        return h - (d * 4);
   });
16

Aha! Value labels! But some are getting cut off at the top. Let’s try moving them down, inside the bars, by adding a small amount to the x and y calculations:

   .attr("x", function(d, i) {
        return i * (w / dataset.length) + 5;  // +5
   })
   .attr("y", function(d) {
        return h - (d * 4) + 15;              // +15
   });
17

Better, but not legible. Fortunately, we can fix that:

   .attr("font-family", "sans-serif")
   .attr("font-size", "11px")
   .attr("fill", "white");
18

Fantasti-code! If you are not typographically obsessive, then you’re all done. If, however, you are like me, you’ll notice that the value labels aren’t perfectly aligned within their bars. That’s easy enough to fix. Let’s use the SVG text-anchor attribute to center the text horizontally at the assigned x value:

    .attr("text-anchor", "middle")

Then, let’s change the way we calculate the x position by setting it to the left edge of each bar plus half the bar width:

    .attr("x", function(d, i) {
        return i * (w / dataset.length) + (w / dataset.length - barPadding) / 2;
    })

And I’ll also bring the labels up one pixel for perfect spacing:

    .attr("y", function(d) {
        return h - (d * 4) + 14;  //15 is now 14
    })
19

Done! Now, let’s branch out from bar charts.

Next up: Making a scatterplot

Interactive Data Visualization for the WebThese tutorials have been expanded into a book, Interactive Data Visualization for the Web, published by O’Reilly in March 2013. Purchase the ebook and print editions from O’Reilly. A free, online version includes interactive examples. Download the sample code files and sign up to receive updates by email.

Follow me on Twitter or watch this RSS feed for other updates.

These tutorials have been generously translated to Catalan (Català) by Joan Prim, Chinese (简体中文) by Wentao Wang, French (Français) by Sylvain Kieffer, Japanese (日本語版) by Hideharu Sakai, Russian (русский) by Sergey Ivanov, and Spanish (Español) by Gabriel Coch.

Also see my video courses, “An Introduction to d3.js: From Scattered to Scatterplot” and “Intermediate d3.js: Charts, Layouts, and Maps.”