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

OptionTypeDefaultDescription
beforeComparisonPanelDatarequiredThe left panel's data
afterComparisonPanelDatarequiredThe right panel's data
optionsPartial<SankeyOptions>noneBase options shared by both panels
syncHighlightbooleantrueHighlight the twin node or flow in the other panel on hover
showDiffbooleantrueOutline added, removed and changed flows
showLegendbooleantrueRender the diff color legend below the panels
diffColorsComparisonDiffColorssee belowOverride 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:

StateMeaningDefault outline
addedOnly in after#16a34a green
removedOnly in before#dc2626 red
changedIn both, with a different value#d97706 amber
unchangedIn both, same valueno 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:

MemberTypeDescription
beforeApexSankeyThe left chart instance
afterApexSankeyThe right chart instance
diffGraphDiffThe computed structural difference
destroy()() => voidTear 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:

FieldTypeDescription
edgesEdgeDiff[]Every flow across both graphs, tagged with its status
addedNodesstring[]Node ids present only in after
removedNodesstring[]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

UseWhen
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.