Chord Diagrams
A layered Sankey reads well when flow moves in one direction through stages. It falls apart when everything connects to everything: ranks stop being meaningful and the ribbons cross into noise. type: 'chord' draws the same model as a ring instead, with nodes as arcs and flows as ribbons across the interior.
Switching projection
const sankey = new ApexSankey(document.getElementById('chart'), {
type: 'chord',
})
sankey.render(data)
Nothing about the data changes. The same { nodes, edges } you would pass to a Sankey renders as a chord diagram, so switching is a one-option change:
const data = {
nodes: [
{ id: 'north', title: 'North' },
{ id: 'south', title: 'South' },
{ id: 'east', title: 'East' },
{ id: 'west', title: 'West' },
],
edges: [
{ source: 'north', target: 'south', value: 42, type: 'transfer' },
{ source: 'south', target: 'north', value: 18, type: 'transfer' },
{ source: 'east', target: 'west', value: 30, type: 'transfer' },
{ source: 'west', target: 'north', value: 12, type: 'transfer' },
],
}
sankey.render(data)
When a chord fits better
| Use a Sankey when | Use a chord when |
|---|---|
| Flow moves through stages in one direction | Everything connects to everything |
| Ranks are meaningful (source → grid → consumer) | There are no natural ranks |
| You want to read volume at each stage | You want to read pairwise relationships |
| Node count is modest per rank | Node count is modest overall, but density is high |
Migration between regions, transfers between accounts, traffic between services and co-occurrence between categories are all cases where a chord reads better: the relationship is mutual, so forcing it into left-to-right ranks misrepresents it.
Bidirectional flows
A chord handles both directions between the same pair. Give each direction its own edge:
edges: [
{ source: 'north', target: 'south', value: 42, type: 'transfer' },
{ source: 'south', target: 'north', value: 18, type: 'transfer' },
]
Each node's arc length is the total of everything entering and leaving it, so an arc's size reads as that node's overall involvement.
Rounded arc corners
arcCornerRadius (default 6) rounds the outer corners of each node arc. The inner edge, where the ribbons meet, stays flush so no gap opens between an arc and the flows attached to it:
const sankey = new ApexSankey(el, {
type: 'chord',
arcCornerRadius: 10, // 0 for sharp corners
})
The value is clamped to the ring band width, so an over-large radius degrades to a fully rounded cap rather than producing artifacts. This option is ignored for type: 'sankey'.
What carries over
Because a chord is a projection of the same model rather than a separate chart, most of the library applies unchanged:
- Theming.
themeandnodePalettework the same way. See Themes. - Tooltips.
tooltipTemplateandnodeTooltipTemplatereceive the same context. See Custom Tooltips. - Interaction. Hover highlighting, click-to-focus and the
node:*/edge:*events all fire. See Plugins and Events. - Motion.
update()springs between datasets. See Data Updates and Morphing. - Accessibility. The same labelled structure and keyboard model. See Accessibility.
Two options are Sankey-specific and do not apply: orientation (a ring has no rank axis) and axisTitles.
Complete example
import { ApexSankey } from 'apexsankey'
const sankey = new ApexSankey(document.getElementById('chart'), {
type: 'chord',
arcCornerRadius: 8,
theme: 'mint',
edgeOpacity: 0.5,
enableTooltip: true,
})
sankey.render({
nodes: regions.map((r) => ({ id: r.id, title: r.label })),
edges: transfers.map((t) => ({
source: t.from,
target: t.to,
value: t.count,
type: 'transfer',
})),
})