D3.js Step by Step: A Basic Pie Chart

Setting up a bare-bones pie chart

UPDATE (July 18, 2016): The code and API links in these tutorials have been updated to target D3 v4, which was a complete rewrite. The D3 wiki contains a breakdown of the changes from v3.

TL;DR

This post is part of a series that explores some key concepts in D3.js by building up an example, step by step, from a bare-bones pie chart to an interactive, animated donut chart that loads external data. For the enough-with-the-jibber-jabber-show-me-the-code types out there, here's a breakdown of the steps we'll be covering:

NOTE: Because we're building things up step by step, the source code contains NEW, UPDATED and REMOVED comments to annotate the lines that have been added, altered or deleted relative to the previous step.

Why a Pie?

A few readers will invariably be thinking to themselves, "A pie chart? Really? Pie charts are a terrible way to visualize data. Why would you ever use a pie chart?" Those readers have a valid point. Pie charts are generally not as effective as bar charts because, as humans, comparison by angle is harder than comparison by length. However, this post is concerned with the demonstration of fundamental concepts in D3 as opposed to the promotion of one type of visualization over another. For our purposes, the pie/donut chart provides certain advantages; for instance, we won't need to any CSS for the initial steps. It also provides a natural excuse to introduce colour scales. I plan to look at other plot types, such as the venerable bar plot, in another series of posts.

To get the ball rolling, we're going to start with a basic pie chart:

If you were to inspect the markup generating that chart, you would see the following (ignoring the stuff in the d attribute for now):

<div id="chart">
  <svg width="360" height="360">
    <g transform="translate(180,180)">
      <path d="M0,-180A..." fill="#393b79"></path>
      <path d="M105.801..." fill="#5254a3"></path>
      <path d="M171.190..." fill="#6b6ecf"></path>
      <path d="M-105.80..." fill="#9c9ede"></path>
    </g>
  </svg>
</div>

That doesn't look too bad. It's actually pretty easy to see how that corresponds to the pie chart above:

  • There's a div with the id chart.
  • There's a svg wrapper that defines the width and height.
  • There's a g element that centers the chart (note that 180 is half of 360).
  • There are four path elements that correspond to the four segments in the pie chart, each of which defines a fill colour and a d attribute that specifies the shape and position.

If the d attribute wasn't so complicated you could code the whole thing by hand. Luckily D3 is there to do the heavy lifting for you, so let's look at how we can build this up.

We'll start by defining some data. Later on we'll load in a more realistic dataset but for now let's just create a simple array with four objects, each of which has a label and a count:

var dataset = [
  { label: 'Abulia', count: 10 },
  { label: 'Betelgeuse', count: 20 },
  { label: 'Cantaloupe', count: 30 },
  { label: 'Dijkstra', count: 40 }
];

These four entries correspond to the four segments in our chart and to the four path elements in the SVG code above. We could have used a simple array of numbers for this step, but this mimics the structure of the dataset we'll be using later on and, more importantly, with the datasets you're apt to meet in the wild.

Next we'll define some dimensions for our chart:

var width = 360;
var height = 360;
var radius = Math.min(width, height) / 2;

The width and height are self-explanatory. Since pie charts are round, we need a radius, which we set to half of the smaller of the two dimensions. In this case the width and height are the same, but this way we could change one of those without having to also update the radius. Now it's time to put D3 to work for us:

var color = d3.scaleOrdinal(d3.schemeCategory20b);

This line defines a colour scale for us. D3 provides a few different colour scales. If we had more than twenty entries in our dataset, D3 would start to reuse colours. Alternatively we could have used the d3-scale-chromatic module or defined our own palette by providing an array of colours:

// Alternative
var color = d3.scaleOrdinal()
  .range(['#A60F2B', '#648C85', '#B3F2C9', '#528C18', '#C3F25C']);

Next we'll create the svg and g elements that we saw in the markup earlier:

var svg = d3.select('#chart')
  .append('svg')
  .attr('width', width)
  .attr('height', height)
  .append('g')
  .attr('transform', 'translate(' + (width / 2) +  ',' + (height / 2) + ')');

This might look like a lot of steps, but the key thing—indeed, the main thesis of these posts—is that:

In D3, each method is specific: it does one thing and it does that one thing very well. This allows it to be highly composable, which is the origin of its flexibility, and that, in turn, is the source of its power.

Let's look at that code line by line:

  1. First we use D3's select method to retrieve the DOM element with id chart. If you've used jQuery or something similar this should seem familiar.
  2. Next we append an svg element to the element we've selected.
  3. Because we're chaining these calls, we now have a reference to the new svg element, so we set its width to be the width we defined earlier on.
  4. After the width we set the height.
  5. Now we append a g element to the svg element.
  6. Finally, because our reference is now to the g element, we center it in the containing svg element.

All of those steps correspond exactly to the markup we examined above. It's exactly the order in which you'd do it if you were doing it by hand. There's no mystery here .

With those details out of the way, we can get onto the pie chart itself, for which we need ways of defining two things:

  1. the radius, which determines the size of the overall chart
  2. the start and end angles of each segment

To define the radius we use D3's arc() with the innerRadius() set to 0 (more on this in Step 2) and its outerRadius() set to the radius we defined earlier:

var arc = d3.arc()
  .innerRadius(0)
  .outerRadius(radius);

For the start and end angles of the segments, we use D3's pie():

var pie = d3.pie()
  .value(function(d) { return d.count; })
  .sort(null);

For it we need to define how to extract the numerical data from each entry in our dataset; this is where the count property of the objects we defined earlier comes into play. If we had just had an array of numbers, then instead of returning d.count we could just have returned d. We specify sort(null) to disable sorting of the entries, because a) we have them in the order we want and b) later on sorting will mess with our animation. By default it will sort in order of descending value.

Now that we have functions for the radius and the angles, we can take the final step of creating our chart:

var path = svg.selectAll('path')
  .data(pie(dataset))
  .enter()
  .append('path')
  .attr('d', arc)
  .attr('fill', function(d, i) {
    return color(d.data.label);
  });

I'll grant that this isn't quite as obvious as the earlier steps—there's a little magic going on here—but again let's take it line by line:

  1. First we select all path elements inside our svg (or more specifically, inside the g element). At present these don't exist but we're going to create them in a moment.
  2. The data() method is how we associate our dataset with the path elements we're about to create. We do so by passing it through our pie function, which knows how to extract the values and give them meaning in the context of a pie chart.
  3. The enter() method creates placeholder nodes for each of the values in our dataset.
  4. Next we use append() to replace our placeholders with path elements.
  5. We define the d attribute—the complicated one in the markup—using our arc function.
  6. Finally we make use of our colour scale to define the fill for each path by associating a colour with each label in the dataset.

This is probably the hardest part of the entire post, but there is a silver lining:

In D3 examples you'll see the .selectAll(el).data(data).enter().append(el) pattern again and again. It's a fundamental set of steps that occurs in many of the most common chart types.

In other words, this is not specific to this example; it's representative of something you'll see often when you start digging into D3 code. For me it's a kind of lodestone that helps orient me in a new piece of code.

Our basic pie chart is now complete. We can now move on to the next step or pause to consider the code in its entirety along with its accompanying HTML:

comments powered by Disqus