D3 Sankey Chart
When a stacked bar would lie about the flow, I draw a sankey. Here's one in D3 with the plugin the core build leaves out.
A D3 sankey chart is how I show “this much went there” when a stacked bar would lie. Energy mixes, budget splits, onboarding funnels: the width of the ribbon is the number. People read it without a legend if you label the nodes.
Core D3 will not do this. You add d3-sankey. Same SVG story as the D3.js page. If the flow is geographic, start from the map example instead.
When a sankey helps
Use it when conservation matters: the sum of the inflows should match the outflows, give or take a “loss” node. Don't use it for a time series. That's a line, and I already have D3 graph examples for that.
A D3 sankey chart
Dummy energy: solar, wind, and gas into a grid, then homes, industry, and loss. Hover a ribbon for the value. The numbers are made up. The layout is the real API.
Preview
The code
Clone nodes and links before you pass them in. The layout mutates the objects. If you skip the clone, the second render is a mess.
Code
const data = {
nodes: [{ name: "Solar" }, { name: "Grid" }, { name: "Homes" }],
links: [
{ source: "Solar", target: "Grid", value: 24 },
{ source: "Grid", target: "Homes", value: 24 },
],
};
const layout = d3.sankey()
.nodeId((d) => d.name)
.nodeWidth(14)
.nodePadding(20)
.extent([[4, 12], [636, 328]]);
const graph = layout({
nodes: data.nodes.map((d) => ({ ...d })),
links: data.links.map((d) => ({ ...d })),
});
svg.selectAll("path")
.data(graph.links)
.join("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("fill", "none")
.attr("stroke", "#e8b84a")
.attr("stroke-opacity", 0.35)
.attr("stroke-width", (d) => Math.max(1, d.width));
Gotchas
-
nodeId. If you identify nodes by
name, tell the layout. The default is index, and a typo insourcefails silently-ish. - Cycles. d3-sankey wants a directed acyclic graph. A loop and the layout gives up.
-
Tiny values.
stroke-widthcan go under 1px. I clamp it.
Load the plugin after D3, or d3.sankey is undefined and
the preview is a blank box. That's always the bug.
Questions I get
What is a D3 sankey chart?
A flow diagram. Nodes are steps or buckets. Links are amounts moving between them. Width of a link is the value. D3 does not include this in the core build; you add d3-sankey.
Why is d3.sankey undefined?
Core d3.js has no sankey. Load d3-sankey after d3. The UMD build hangs sankey and sankeyLinkHorizontal on the d3 global.
Can a sankey have cycles?
Not in d3-sankey. The layout assumes a DAG. If your data loops, drop the cycle or pick a different diagram.