There’s a lot more to D3! This is just a quick tour of some other stuff D3 has to offer.

Layouts and SVG Helpers

Some of these examples make use of D3’s layout helpers.

Some layouts convert our original data into descriptions of the shapes we want to draw. For example, the pie layout converts numbers into arcs (start and end angles for pie slices). This keeps our drawing code simple. Other layouts help us group our data so we can draw useful shapes like stacked stacked or trees.

D3 also provides helpers to make some of the more complex SVG shapes easier to draw. The path helper can build curves that interolate between data points. The arc helper can take the angles generated by the pie layout and draw arcs (pie slices).

A Pie Chart

Let’s start out by walking through using D3 to draw a simple pie chart.

First we get our source data:

var sales = [
    { product: 'Hoodie',  count: 12 },
    { product: 'Jacket',  count: 7 },
    { product: 'Snuggie', count: 6 },
];
    

We want each product to be represented as a pie slice in our pie chart, which involves calculating the associated angles. We’ll use the d3.pie helper for that:

var pie = d3.pie()
  .value(d => d.count)

var slices = pie(sales);
// the result looks roughly like this:
[
    {
        data: sales[0],
        endAngle: 3.0159289474462017,
        startAngle: 0,
        value: 12
    },
    {
        data: sales[1],
        startAngle: 3.0159289474462017,
        endAngle: 4.775220833456486,
        value: 7
    },
    {
        data: sales[2],
        startAngle: 4.775220833456486,
        endAngle: 6.283185307179587,
        value: 6
    }
]
    

Now we have our data in angles (radians), so we can turn them into something visual. The next tool D3 gives us is the d3.arc which helps to create SVG <path> tags for arcs. This is where we provide all the information relevant to actually drawing, such as the radius size.

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

// helper that returns a color based on an ID
var color = d3.scaleOrdinal(d3.schemeCategory10);

var svg = d3.select('svg.pie');
var g = svg.append('g')
    .attr('transform', 'translate(200, 50)')

g.selectAll('path.slice')
    .data(slices)
    .join(
        enter => {
            enter.append('path')
            .attr('class', 'slice')
            .attr('d', arc)
            .attr('fill', d => color(d.data.product))
        }
    );

// building a legend is as simple as binding
// more elements to the same data. in this case,
// <text> tags
svg.append('g')
    .attr('class', 'legend')
        .selectAll('text')
        .data(slices)
        .join(
            enter => {
                enter.append('text')
                .text(d => '' + d.data.product)
                .attr('fill', d => color(d.data.product))
                .attr('y', (d, i) => 20 * (i + 1));
            }
        );
    
Again, we snuck in a new helper, and it's another type of ordinal scale.
The d3.scaleOrdinal helper gives us back a function. This function takes in values (typically IDs) and gives back a value in its domain. The same ID gets the same color, and it will rotate through its domain. apart. We initalize it with d3.schemeCategory10 which is a list of10 colors that are pretty easy to tell apart.

Stacked Bars

One of the most common charts to draw is some variation of stacked bars. These are deceptively complex, because after the first layer, each new layer of bars depends on layout of the previous one.

The data requirements are also different because stacked bars need to have dense data sources. In most graphs, we could omit an empty value because it won’t be drawn, but in a stacked layout, that still could affect the layout of the next layer.

Let’s start we have sales of our products over multiple days.

Sales
Date Hoodie Jacket Snuggie
2014-01-01 6 2 3
2014-01-02 7 5 2
2014-01-03 8 7 3

Transformed into a dense array, our data looks like this:

var sales = [
    { date: "2014-01-01", hoodies: 6, jackets: 2, snuggies: 3 },
    { date: "2014-01-02", hoodies: 7, jackets: 5, snuggies: 2 },
    { date: "2014-01-03", hoodies: 8, jackets: 7, snuggies: 3 }
];
    

Now we can take advantage of the d3.stack to do the work of stacking our layers on top each other. While normally a bar graph would have one y value, a stacked one has two:

  • where a segment starts (“baseline”)
  • where the segment ends

For the first layer stacked bar chart (at the bottom), the baseline is typically 0. It can be other values for things like streamgraphs, which are a whole other topic.

var stack = d3.stack()
    .keys(["hoodies", "jackets", "snuggies"]);

var stacked = stack(sales);
    

Now, stacked will be a set of nested arrays containing the hights of the data in sales, stacked, which will come in handy when it’s time to draw these. For examples, the stacked data now looks like this:

stacked
[
  [[0, 6],  [0, 7],   [0, 8  ]],
  [[6, 8],  [7, 12],  [8, 15 ]],
  [[8, 11], [12, 14], [15 18 ]]
]
   

But the data is not a plain array! It also has a few useful properties. The “rows” have key and index and the computed start/end arrays also have data – the original data.

stacked
// [Array[3], Array[3], Array[3]]
stacked[0]
// [Array[2], Array[2], Array[2]]
Object.keys(stacked[0])
// ["0", "1", "2", "key", "index"]
stacked[0].key
// "hoodies"
stacked[0][0].data
// {date: "2014-01-01", hoodies: 6, jackets: 2, snuggies: 3}
    

Ok so let’s get to drawing! We’ll bring back our good friends d3.scaleLinear and d3.scaleTime.

var height = 200;
var width = 200;

// we need to calculate the maximum y-value
// across all our layers, so we find the biggest
// end value
var maxY = d3.max(stacked, d => d3.max(d, d => d[1]));

var y = d3.scaleLinear()
    .range([height, 0])
    .domain([0, maxY]);

var x = d3.scaleTime()
    .range([0, width])
    .domain(d3.extent(sales, d => new Date(Date.parse(d.date))))
    .nice(4);

var svg = d3.select('svg.stack');
var color = d3.scaleOrdinal(d3.schemeCategory10);

// bind a <g> tag for each layer
var layers = svg.selectAll('g.layer')
    .data(stacked, d => d.key)
    .join(
        enter => {
            enter.append('g')
            .attr('class', 'layer')
            .attr('fill', d => color(d.key));
        }
    );

// bind a <rect> to each value inside the layer
layers.selectAll('rect')
    .data(d => d)
    .join(
        enter => {
            enter.append('rect')
            .attr('x', d => x(new Date(Date.parse(d.data.date))))
            .attr('width', width / 3)
                // remember that SVG is y-down while our graph is y-up!
                // here, we set the top-left of this bar segment to the
                // larger value of the pair
            .attr('y', d => y(d[1]))
                // since we are drawing our bar from the top downwards,
                // the length of the bar is the distance between our points
            .attr('height', d => y(d[0]) - y(d[1]));
        }
    );
    

There are a few things that make this graph a little more complex. One of the hardest parts is realizing that D3 is really only going to hint at how we should stack the bars: D3 gives us stacked results in our data space, but not in SVG's coordinate system. We have to deal with the same confusing Y-axis coordinate flip.

Onward!

D3 has a lot to offer, and our goal here was to give a brief tour and cover some core concepts! There’s much more to learn about D3, but hopefully this tutorial has given you enough so that you can teach yourself the rest.

There are lot of great resources for learning D3 out there:

  1. First and foremost, D3’s own wiki. This is a great starting point for any D3-related exploration

  2. Nestled inside that wiki, the D3 API Reference is great for remembering what APIs there are and what the various parameters mean.

  3. For more examples of what is possible with D3 check out the D3 examples by creator of D3, Mike Bostock.

  4. The D3 graph gallery is a collection of examples for dozens of different types of charts made with D3. It is maintained by Yan Holtz.

But don’t stop there! Google searches are a great way to discover things too. Happy visualizing!