Sankey Diagrams in JavaScript: D3, ECharts, and Plotly Compared
The fastest way to render a Sankey diagram in JavaScript is ECharts or Plotly.js โ both ship a built-in sankey type that turns a list of (source, target, value) links into an interactive chart in under 30 lines. D3's d3-sankey gives you full control but only computes the layout; you draw the SVG yourself.
Quick answer: For a JavaScript Sankey diagram, use Apache ECharts (series: [{ type: 'sankey' }]) or Plotly.js (type: 'sankey') โ both are one-config-object solutions with tooltips and drag built in. Use d3-sankey when you need a custom design or a framework-native component: it calculates node and link positions, and you render them as SVG in React or Svelte.
All three libraries consume the same data shape โ nodes plus weighted links โ so you can prototype in one and switch later. If you're unsure whether a Sankey is even the right chart, the Sankey diagram guide covers when a funnel or bar chart reads better.
How Do You Make a Sankey Diagram with ECharts?
ECharts has a first-class sankey series: pass data (nodes) and links (flows), and you get layout, hover tooltips, and draggable nodes for free. It's the best default for dashboards because the styling looks finished out of the box.
<div id="chart" style="width:700px;height:400px"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script>
const chart = echarts.init(document.getElementById('chart'));
chart.setOption({
tooltip: { trigger: 'item' },
series: [{
type: 'sankey',
emphasis: { focus: 'adjacency' }, // highlight connected flows on hover
data: [
{ name: 'Organic' }, { name: 'Paid' }, { name: 'Email' },
{ name: 'Landing' }, { name: 'Signup' }, { name: 'Exit' }
],
links: [
{ source: 'Organic', target: 'Landing', value: 5000 },
{ source: 'Paid', target: 'Landing', value: 3000 },
{ source: 'Email', target: 'Landing', value: 1200 },
{ source: 'Landing', target: 'Signup', value: 2100 },
{ source: 'Landing', target: 'Exit', value: 7100 }
],
lineStyle: { color: 'gradient', curveness: 0.5 }
}]
});
</script>
Nodes are referenced by name in the links, so there's no index bookkeeping. See the ECharts tool review for how it compares beyond Sankeys.
How Do You Make a Sankey Diagram with Plotly.js?
Plotly.js uses a single trace with parallel arrays: label for node names, then source/target/value arrays where source and target are node indices. Slightly clunkier data format than ECharts, but identical to Plotly's Python API โ handy if your team prototypes in Python notebooks.
<div id="chart"></div>
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
<script>
Plotly.newPlot('chart', [{
type: 'sankey',
orientation: 'h',
node: {
pad: 15,
thickness: 20,
label: ['Organic', 'Paid', 'Email', 'Landing', 'Signup', 'Exit']
},
link: {
source: [0, 1, 2, 3, 3], // indices into label[]
target: [3, 3, 3, 4, 5],
value: [5000, 3000, 1200, 2100, 7100]
}
}], { title: 'Traffic to Signup', font: { size: 12 } });
</script>
Watch the index arrays: a source index pointing at the wrong label is the classic Plotly Sankey bug, and it fails silently by drawing a plausible-looking wrong chart.
When Should You Use d3-sankey Instead?
Use d3-sankey when the chart is a product feature, not a dashboard widget โ custom node shapes, animated transitions, or brand-exact styling. It's a layout algorithm, not a chart: you feed it nodes and links, it computes x0/x1/y0/y1 for each node and a path generator for links, and you render the SVG yourself.
import { sankey, sankeyLinkHorizontal } from 'd3-sankey';
const generator = sankey()
.nodeWidth(20)
.nodePadding(12)
.extent([[0, 0], [700, 400]]);
const { nodes, links } = generator({
nodes: [{ name: 'A' }, { name: 'B' }, { name: 'C' }],
links: [
{ source: 0, target: 2, value: 40 },
{ source: 1, target: 2, value: 60 }
]
});
// nodes[i].x0, .x1, .y0, .y1 โ draw <rect> elements
// sankeyLinkHorizontal()(link) โ "d" attribute for <path> elements
Because the output is plain coordinate data, d3-sankey is also the cleanest path to framework-native components.
What About React and Svelte Sankey Diagrams?
You have two patterns. Pattern one: wrap a full charting library โ echarts-for-react, react-plotly.js, or in Svelte just call echarts.init() inside an effect on a bound div. Quickest, but the chart is a black box to your framework. Pattern two: use d3-sankey for math only and render the rects and paths as JSX or Svelte markup โ the layout call is pure JavaScript with no DOM, so it plays perfectly with declarative rendering.
<!-- Svelte 5: d3-sankey for layout, Svelte for rendering -->
<script>
import { sankey, sankeyLinkHorizontal } from 'd3-sankey';
let { data } = $props();
const layout = sankey().nodeWidth(18).extent([[0,0],[700,400]]);
const graph = $derived(layout({
nodes: data.nodes.map(d => ({ ...d })),
links: data.links.map(d => ({ ...d }))
}));
</script>
<svg viewBox="0 0 700 400">
{#each graph.links as link}
<path d={sankeyLinkHorizontal()(link)} fill="none"
stroke="#8884" stroke-width={Math.max(1, link.width)} />
{/each}
{#each graph.nodes as node}
<rect x={node.x0} y={node.y0} width={node.x1 - node.x0}
height={node.y1 - node.y0} fill="#4c78a8" />
{/each}
</svg>
Note the .map(d => ({ ...d })) copies โ d3-sankey mutates its input, which breaks frameworks that expect immutable props.
Which JavaScript Sankey Library Should You Pick?
Pick ECharts for dashboards, Plotly.js for Python-team consistency, and d3-sankey for custom components. Google Charts also has a Sankey but it wraps an old D3 version and is rarely worth it today.
| Library | Bundle cost | Effort | Best for | Watch out for |
|---|---|---|---|---|
| Apache ECharts | ~350 KB (tree-shakeable) | Low | Dashboards, polished defaults | Large full bundle โ import only the sankey chart |
| Plotly.js | ~1 MB (partial bundles exist) | Low | Teams already using Plotly in Python | Index-based links; heavy bundle |
| d3-sankey | ~30 KB with d3 deps | High | Custom React/Svelte components | Layout only โ you write all rendering |
| Google Charts | Loader-based | Low | Quick internal pages | Dated styling, limited customization |
Pro Tip: Whichever library you choose, validate the conservation rule in code before rendering: sum the values into each middle node and compare with the sum out. A five-line check catches the mismatched-flow bugs that make Sankeys silently lie โ and it's much easier than spotting a too-thin band by eye.
โ Back to Visualization Tips