How to create a stacked area chart with D3
Stacked graphs are useful for displaying data, which you would usually want to be grouped together. A good example is energy usage, which is split into utilities, although we want to know the sum of all the utilities, (gas and electric for example), its helpful to see at a quick glance, how much a particular utility is using.
Prerequisites
Prerequisites are that you have some basic D3.js knowledge, if not then I would recommend checking out Scrimba for their free D3 course with interactive videos: https://scrimba.com/g/gd3js
So this is what we are going to make:
This contains a set of fake data:
const data = [
{
year: 2000,
aData: 50,
bData: 50
},
{
year: 2001,
aData: 150,
bData: 50
},....
]
With this data, we want both aData
and bData
to be displayed on the same chart, by stacking them on top of each other. D3 has a very helpful function to do this for us: d3.stack
The stack function takes a set of keys which we are interested in and calculates the stacked values for each of the stacks. So if aData is on the bottom of the chart, and its value is 50, its values for the area would be [0, 50] so when we want to add bData on top of this, which is also 50 it would give us [0 + 50, 50 + 50] so that they can be stacked on top of each in the chart.
const stack = d3.stack().keys(["aData", "bData"]);
const stackedValues = stack(data);
// stackedValues returns
// [
// [[0, 50], [0, 150], [0, 240] ...] // aData
// [[50, 100], [150, 200], [240, 290] ...] // bData
// ]
Now we want to create a new array, which will be an array of objects containing the stackedValues and the year which it relates to.
const stackedData = [];// Copy the stack offsets back into the data.
stackedValues.forEach((layer, index) => {
const currentStack = [];
layer.forEach((d, i) => {
currentStack.push({
values: d,
year: data[i].year
});
});
stackedData.push(currentStack);
});// stackedData returns
// [
// {
// values: [0, 50],
// year: 2000
// },
// ...
// ]
So we have done the hard part
Now we can add a new group to the SVG, for every series in our stackedData
const series = grp
.selectAll(".series")
.data(stackedData)
.enter()
.append("g")
.attr("class", "series");
In our case, we have two groups, aData group and bData group.
On each series, we want to add a new path, because we have already given the Series
our data, this will correctly use the stackData[index] as the current data. So in our case, the dataValue which is passed to our area function will be one of the objects in our array.
series
.append("path")
.attr("d", dataValue => area(dataValue));// dataValue exmample:
// {
// values: [0, 50],
// year: 2000
// }
For our area function, we pass the values to our scales (either x or y), so this will return the X and Y co-ordinated relative to our SVG.
const area = d3
.area()
.x(dataPoint => xScale(dataPoint.year))
.y0(dataPoint => yScale(dataPoint.values[0]))
.y1(dataPoint => yScale(dataPoint.values[1]));
My next blog will be looking at creating a pattern fill for an area chart, this comes in handy if you want to show linked information. Such as having actual and estimated utility readings on the same chart.
If you have any questions or found any mistakes please leave a comment.
Thanks for reading 👋