Stacked Area charts
A stacked area chart is an evolution of an area chart used to display the evolution of several groups in a dataset. This section explains how to build it with d3.js
and react
. It focus on stacking, so make sure to read the area chart section first.
The Data
Most of the time the input dataset is an array where each item is an object.
Each object provides information for a step on the X axis. It has a value like x
that provides the exact position on the X axis. It then has several numeric values, one for each group of the dataset.
Here is a minimal example:
const data = [
{
x: 1,
groupA: 38,
groupB: 19,
},
{
x: 2,
groupA: 16,
groupB: 14,
},
...
];
→ Wide and Long formats
The format described above is often called the wide format. Another common format is the long format, where each object in the array provides information for 1 group only. (The array becomes way longer 🙃)
If your dataset is formatted using the long format, you can transform it using the pivotWider
function below:
Pivot function
type LongDataItem = {
date: string;
group: string;
value: number;
};
type WideDataItem = {
date: string;
} & { [key: string]: number }
const pivotWider = (data: LongDataItem[]) => {
const result: WideDataItem[] = [];
data.forEach((item) => {
const existingEntry = result.find((entry) => entry.date === item.date);
if (existingEntry) {
existingEntry[item.group] = item.value;
} else {
const newEntry = { date: item.date };
newEntry[item.group] = item.value;
result.push(newEntry);
}
});
return result;
}
→ .csv
data
If your data is in .csv
format, you can translate it thanks to the csvParse()
function of d3. I'll write a blogpost soon on how to deal with the csv format. Subscribe to the project to know when it is ready!
ToDoAdd some more hints on how to type those data objects
Component skeleton
The goal here is to create a StackedAreaGraph
component that will be stored in a StackedAreaGraph.tsx
file. This component requires 3 props to render: a width
, a height
, and some data
.
The shape of the data
is described above. The width
and height
will be used to render an svg
element in the DOM, in which we will insert the stacked area graph.
To put it in a nutshell, that's the skeleton of our StackedAreaGraph
component:
import * as d3 from "d3"; // we will need d3.js
type WideDataItem = {
date: string;
} & { [key: string]: number }
type StackedAreaGraphProps = {
width: number;
height: number;
data: WideDataItem[];
};
export const StackedAreaGraph = ({ width, height, data }: StackedAreaGraphProps) => {
// read the data
// find the list of groups to display
// stack the data
// build the shapes
return (
<div>
<svg width={width} height={height}>
// render all the shapes
</svg>
</div>
);
};
It's fundamental to understand that with this code organization, d3.js will be used to prepare the SVG circle
, but it's React that will render them in the return()
statement. We won't use d3 methods like append
that you can find in usual d3.js examples.
Stacking
The trickiest part of a stacked area chart creation is probably the stacking step.
Series are displayed one on top of each other and you have to compute their positions on the Y axis. Fortunately d3.js
is here to the rescue with a d3.stack()
function. This is what you need to do to stack your data:
→ Build a stack generator
d3.stack()
returns a stack generator that we call stackSeries
here. d3.stack()
is a function that returns a function.
.keys()
is used to pass the list of groups that we want to stack on top of each other. Those keys are the ones used in the input dataset described in the data section.
const stackSeries = d3
.stack()
.keys(["groupA", "groupB"])
// stackSeries is now a function that takes the kind of
// dataset above and stack the series
→ Use the generator
Now that this stack generator is available, we just have to run it on our dataset to get the stacked values
const series = stackSeries(data);
That's it. series
contains the stacked values that we can transform in coordinates for the shapes we need to draw.
→ Output
The output has kind of an usual shape and it's important to understand how it's formatted since shapes will be drawn from it.
Our generated stacked series
object is an array. It has 1 item per group in the dataset.
For each group, there are 3 things:
- a
key
prop that provides the group key - a
index
prop that provides its index 🤷 - Several arrays of length 2. Each array describes the position of the group for a timestamp. First item in the array provides the bottomposition, second item provides the top.
[
// First group of the dataset: at the very bottom of the stack
[
[0, 38, data: {…}], // First timestamp of the dataset: shape goes from 0 to 38 on the Y axis
[0, 16, data: {…}], // Second timestamp: shape goes from 0 to 16
... // 1 entry per timestamp
key: 'groupA', // group name
index: 0 // index
],
// Second group of the dataset on top of the first one
[[38, 57, data: {…}], ..., key: 'groupB', index: 1],
//Third group
[[57, 72, data: {…}], ..., key: 'groupC', index: 2],
...
]
Basic stacked area chart
The series
object described above has all the information we need to draw a stacked area chart. We can loop through it and draw a path
for each group, one by one.
Note that for each group the area()
function of d3 is invoked. The usage of this function is deeply described in the area section of the gallery.
Here is a minimal code example wrapping the whole process.
basic stacked area chart with react and d3.js
Responsive Stacked Area with react
The component above is not responsive. It expects 2 props called width
and height
and will render a Stacked Area of those dimensions.
Making the Stacked Area responsive requires adding a wrapper component that gets the dimension of the parent div
, and listening to a potential dimension change. This is possible thanks to a hook called useDimensions
that will do the job for us.
useDimensions
: a hook to make your viz responsive
export const useDimensions = (targetRef: React.RefObject<HTMLDivElement>) => {
const getDimensions = () => {
return {
width: targetRef.current ? targetRef.current.offsetWidth : 0,
height: targetRef.current ? targetRef.current.offsetHeight : 0
};
};
const [dimensions, setDimensions] = useState(getDimensions);
const handleResize = () => {
setDimensions(getDimensions());
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useLayoutEffect(() => {
handleResize();
}, []);
return dimensions;
}
I'm in the process of writing a complete blog post on the topic. Subscribe to the project to know when it's ready.
Stacked Area inspiration
If you're looking for inspiration to create your next Stacked Area, note that dataviz-inspiration.com showcases many examples. Definitely the best place to get ... inspiration!
dataviz-inspiration.com showcases hundreds of stunning dataviz projects. Have a look to get some ideas on how to make your Stacked Area looks good!
visitOffset and Curve types
Stacked area charts can easily be customized to use other offset and smoothing algorithm. This process can be used to create streamgraphs which are a varation of the stacked area graph.
The offset type controls how the data are stacked. You can read about the offset options available in the official documentation or play with the little example below.
The curve type controls how the smoothing of each shape is made. There are a myriad of options described in the official documentation.
Try d3.js various options to offset the data and smooth shapes. See a smooth transition between options.
The animation uses react-spring
to run. I'll publish a full blogpost on the topic soon!
Contact
👋 Hey, I'm Yan and I'm currently working on this project!
Feedback is welcome ❤️. You can fill an issue on Github, drop me a message on Twitter, or even send me an email pasting yan.holtz.data
with gmail.com
. You can also subscribe to the newsletter to know when I publish more content!