Comparison Split-view
An animation between two datasets shows that something changed. It is poor at showing exactly what. ApexSankey.compare() renders both states side by side instead, outlines every flow by how it changed, and links the panels so hovering one highlights its twin in the other.
Rendering a comparison
import { ApexSankey } from 'apexsankey'
const cmp = ApexSankey.compare(document.getElementById('chart'), {
before: { nodes, edges: edges2024, title: '2024' },
after: { nodes, edges: edges2025, title: '2025' },
})
Both panels go into the one host element you pass. Each side is { nodes, edges, title? }, where title becomes that panel's heading.
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
before | ComparisonPanelData | required | The left panel's data |
after | ComparisonPanelData | required | The right panel's data |
options | Partial<SankeyOptions> | none | Base options shared by both panels |
syncHighlight | boolean | true | Highlight the twin node or flow in the other panel on hover |
showDiff | boolean | true | Outline added, removed and changed flows |
showLegend | boolean | true | Render the diff color legend below the panels |
diffColors | ComparisonDiffColors | see below | Override the diff outline colors |
options is shared by both panels, so theme, tooltips and fonts are set once. Each panel manages its own width.
const cmp = ApexSankey.compare(el, {
before: { nodes, edges: edges2024, title: '2024' },
after: { nodes, edges: edges2025, title: '2025' },
options: { theme: 'mint', enableTooltip: true },
diffColors: { added: '#16a34a', removed: '#dc2626', changed: '#d97706' },
})
The diff
Every flow lands in one of four states, computed from the two graphs:
| State | Meaning | Default outline |
|---|---|---|
added | Only in after | #16a34a green |
removed | Only in before | #dc2626 red |
changed | In both, with a different value | #d97706 amber |
unchanged | In both, same value | no outline |
Each panel outlines the flows it is responsible for: removed flows are marked on the left, added ones on the right, and changed ones on both. Flows are matched on (source, target, type), so a stable type matters as much here as it does for data updates.
The legend below the panels explains the three colors. Turn it off with showLegend: false when the page already explains them.
The returned handle
compare() returns a SankeyComparison:
| Member | Type | Description |
|---|---|---|
before | ApexSankey | The left chart instance |
after | ApexSankey | The right chart instance |
diff | GraphDiff | The computed structural difference |
destroy() | () => void | Tear down both charts and remove the injected DOM. Idempotent |
Both panels are ordinary ApexSankey instances, so everything else in the library reaches them:
cmp.after.on('node:click', ({ id }) => showDetail(id))
cmp.before.use(pathTrace())
diff is available without touching the DOM, which is useful for driving a summary beside the chart:
| Field | Type | Description |
|---|---|---|
edges | EdgeDiff[] | Every flow across both graphs, tagged with its status |
addedNodes | string[] | Node ids present only in after |
removedNodes | string[] | Node ids present only in before |
Each EdgeDiff carries source, target, type, status, and the beforeValue / afterValue pair (either is undefined when the flow did not exist on that side):
const { edges, addedNodes } = cmp.diff
const added = edges.filter((e) => e.status === 'added')
const grew = edges.filter((e) => e.status === 'changed' && e.afterValue > e.beforeValue)
document.getElementById('summary').textContent =
`${added.length} new flows, ${grew.length} grew, ${addedNodes.length} new nodes`
Cleanup
Call destroy() before removing the container. It tears down both instances (running their plugin teardowns) and removes the DOM it injected:
// React
useEffect(() => {
const cmp = ApexSankey.compare(ref.current, config)
return () => cmp.destroy()
}, [])
Choosing between comparison and animation
| Use | When |
|---|---|
compare() | The difference is the subject. A reader needs to see exactly which flows appeared, vanished or shifted |
update() | The new state is the subject. Continuity matters more than an itemized diff, as in a live feed or a timeline |
For an ordered series rather than two states, the timePlayback plugin steps through frames with a scrubber. See Plugins and Events.