Sankey Diagram
A Sankey Diagram display flows. Several entities (nodes) are represented by rectangles or text. Directed links are represented with arrows or arcs that have a width proportional to the importance of the flow.
This tutorial explains how to use React
, D3.js
and the d3-sankey
plugin to build a Sankey diagram. It comes with explanations and code sandboxes to play along with the suggested implementation.
The Data
Two layers of information are required to build a Sankey diagram: a list of nodes to build the rectangles and a list of links to build the paths between them.
Many different data structures can be used to store such information. In this tutorial I suggest to start with the following:
const data = {
nodes: [
{ node: 0, name: "node0" },
{ node: 1, name: "node1" },
{ node: 2, name: "node2" },
{ node: 3, name: "node3" },
],
links: [
{ source: 0, target: 2, value: 2 },
{ source: 1, target: 2, value: 2 },
{ source: 1, target: 3, value: 2 },
]
}
data
is an object with 2 properties: nodes
and links
.
nodes
is an array where each node is an object defined by its index (node
) and itsname
. Note that any other feature can be added to nodes here.links
is another array listing the connections. They are defined by asource
and atarget
and optionnaly with avalue
.
ToDoExplain how to build this data structure from various initial formats
Component skeleton
The goal here is to create a Sankey
component that will be stored in a Sankey.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 Sankey.
To put it in a nutshell, that's the skeleton of our Sankey
component:
import * as d3 from "d3"; // we will need d3.js
type SankeyProps = {
width: number;
height: number;
data: number[];
};
export const Sankey = ({ width, height, data }: SankeyProps) => {
// read the data
// create a color scale for the nodes
// compute node position thanks to the d3.sankey() function
// build the rectangles and arcs
return (
<div>
<svg width={width} height={height}>
// render all the <rect> and <path>
</svg>
</div>
);
};
It's fundamental to understand that with this code organization, d3.js will be used to layout (rect
and path
positions), 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.
Draw the nodes
To draw the nodes we first need to compute their positions on the SVG area. This is where the d3-sankey plugin gets helpful with its sankey()
function.
The sankey()
function must be called with a set of options described below as inline comments:
const sankeyGenerator = sankey() // Main function of the d3-sankey plugin that computes the layout
.nodeWidth(26) // width of the node in pixels
.nodePadding(29) // space between nodes
.extent([ // chart area:
[MARGIN_X, MARGIN_Y], // top-left coordinates
[width - MARGIN_X, height - MARGIN_Y], // botton-right coordinates
])
.nodeId((node) => node.id) // Accessor function: how to retrieve the id that defines each node. This id is then used for the source and target props of links
.nodeAlign(sankeyCenter); // Algorithm used to decide node position
We now have a function called sankeyGenerator
that computes the sankey layout from our dataset. We can use it as follow:
const { nodes, links } = sankeyGenerator(data);
And that's it. We now have 2 objects called nodes
and links
that provide all the necessary information about nodes and links. nodes
is an array. For each item we havex0
, y0
, x1
and y1
that are the coordinates of the top-left and bottom right corners of the rectangle. We are ready for the drawing! ✏️
First step of our ongoing sankey diagram: the nodes are displayed using rectangles.
Draw the connections
The other object we got from the sankey()
function is links
. It is an array where each item provides detail about a link.
Each item is an object with several properties. Among them:
width
→ provides the width of the arc we will build. We will pass it to thestrokeWidth
property of the SVGpath
.source
andtarget
→ provide a lot of details about the source and target nodes, including their positions. It makes it possible to pass a link to thesankeyLinkHorizontal()
function to get the SVG path we will give as thed
argument.
To put it in a nutshell, we can loop through the links
array and draw apath
as follow:
const allLinks = links.map((link, i) => {
const linkGenerator = sankeyLinkHorizontal();
const path = linkGenerator(link);
return (
<path
key={i}
d={path}
stroke="#a53253"
fill="none"
strokeOpacity={0.1}
strokeWidth={link.width}
/>
);
});
Resulting in a first Sankey diagram 🎉
First step of our ongoing arc diagram: the nodes are displayed at the bottom of the figure.
Responsive Sankey with react
The component above is not responsive. It expects 2 props called width
and height
and will render a Sankey of those dimensions.
Making the Sankey 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.
Sankey inspiration
If you're looking for inspiration to create your next Sankey, 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 Sankey looks good!
visitApplication to a real dataset
This Sankey diagram visualizes the flow of energy: supplies are on the left, and demands are on the right. It is a reproduction of this famous observable example. Links show how varying amounts of energy are converted or transmitted before being consumed or lost.
The code is very similar to the example above. On top of it, a color scale is used for the node and connection colors, and some text labels have been added.
A Sankey diagram showing the flow of energy. Supplies on the left, demands on the right.
ToDoAdd hover effect to highlight links
ToDoAdd gradient along links
ToDoFix types
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!