Path Highlighting and Interaction

When a user hovers a node or edge, ApexSankey can highlight the entire connected flow path and dim everything else. This makes it easy to trace where a flow comes from and where it goes in a dense diagram. Clicking a node then pins that path, and the optional pathTrace plugin sends an animated pulse cascading along it — hover to skim, click to commit.

Connected-path highlighting

highlightConnectedPath is enabled by default. On hover, the connected upstream and downstream flows stay at full opacity while unrelated elements fade to dimOpacity.

OptionTypeDefaultDescription
highlightConnectedPathbooleantrueHighlight the connected flow path when hovering a node or edge
dimOpacitynumber0.15Opacity applied to unrelated (dimmed) elements while highlighting is active
const sankey = new ApexSankey(document.getElementById('chart'), {
  highlightConnectedPath: true,
  dimOpacity: 0.2,
})
sankey.render(data)

Disabling highlighting

Set highlightConnectedPath: false for a static diagram where hover has no effect:

const sankey = new ApexSankey(el, {
  highlightConnectedPath: false,
})

Tuning the dim level

Lower dimOpacity values make the highlighted path stand out more sharply against the dimmed background; higher values keep more context visible:

const sankey = new ApexSankey(el, {
  highlightConnectedPath: true,
  dimOpacity: 0.08,   // strong contrast — unrelated flows nearly vanish
})

Animated path tracing

The built-in highlight is static: the connected path lights up and holds. The pathTrace plugin adds motion on top of it — when a node is picked, a bright pulse cascades along the connected flow path, ribbon by ribbon, cueing the eye to where the flow goes. It composes with highlightConnectedPath (a click both pins the highlighted path and fires the pulse down it) and is skipped under prefers-reduced-motion.

Install it with use(), importing it as pathTrace or from ApexSankey.plugins:

const sankey = new ApexSankey(el, {
  highlightConnectedPath: true,
  dimOpacity: 0.1,
})
sankey.use(ApexSankey.plugins.pathTrace({ direction: 'downstream' }))
sankey.render(data)
OptionTypeDefaultDescription
trigger'click' | 'hover''click'What starts a trace
direction'downstream' | 'upstream' | 'both''downstream'Which way flow is traced from the picked node
colorstring'#ffffff'Pulse color — pick one that contrasts with your background
durationnumber700Milliseconds for a pulse to cross one ribbon
staggernumber220Milliseconds added per hop, so the trace cascades outward

Click to focus and isolate

Hover highlighting is transient: move the pointer away and the diagram returns to normal. Clicking a node or a flow instead pins its full upstream and downstream path and dims the rest, so a path stays isolated while you read it or point at it.

Clicking again releases it. The traversal is cycle-guarded, so a diagram with circular links terminates rather than walking a loop forever.

This works alongside onNodeClick and the node:click event; both still fire, so pinning a path and running your own handler are not mutually exclusive.

Draggable nodes

draggableNodes lets a reader reposition nodes by dragging, with mouse, touch and pen support. Connected flows follow the node live:

const sankey = new ApexSankey(el, {
  draggableNodes: true,
})

This is useful when the automatic layout puts two ranks in an order that fights the story, or when a reader wants to untangle a specific crossing to see it clearly.

A manual position holds until the next render() or update() recomputes the layout, so a dragged arrangement is a viewing aid rather than persisted state. Capture positions yourself if you need them to survive a data change.

Node click callback

onNodeClick fires when the user clicks a node. It receives a SankeyNode object describing the clicked node.

const sankey = new ApexSankey(el, {
  onNodeClick: (node) => {
    console.log('Clicked node:', node)
    console.log('Node data:', node.data)     // { id, title, color, ... }
    console.log('Flow value:', node.value)   // total value through the node
  },
})
sankey.render(data)

SankeyNode fields

FieldTypeDescription
dataNodeData | nullThe node's user data: { id, title, color, direction, forceY }
valuenumberTotal flow value passing through the node
ranknumberColumn index (0-based) of the node in the layout
x, ynumberComputed position of the node
dynumberComputed height of the node rectangle
directionstringFlow direction for the node
spaceAbove, spaceBelownumberWhitespace above/below the node

Building a drill-down interaction

Combine onNodeClick with your own state to drive a detail panel or filter:

const sankey = new ApexSankey(el, {
  highlightConnectedPath: true,
  dimOpacity: 0.15,
  onNodeClick: (node) => {
    const id = node.data?.id
    if (!id) return
    showDetailPanel(id, node.value)
  },
})
sankey.render(data)

function showDetailPanel(id, value) {
  document.getElementById('panel').innerHTML = `
    <h3>${id}</h3>
    <p>Total flow: ${value}</p>
  `
}

React example

In React, use the top-level onNodeClick prop (not options.onNodeClick, which the wrapper omits):

import { useState } from 'react'
import { ApexSankey } from 'react-apexsankey'
import type { SankeyNode } from 'react-apexsankey'

export default function InteractiveSankey() {
  const [selected, setSelected] = useState<string | null>(null)

  return (
    <div>
      {selected && <p>Selected: {selected}</p>}
      <ApexSankey
        data={data}
        options={{ highlightConnectedPath: true, dimOpacity: 0.15 }}
        onNodeClick={(node: SankeyNode) => setSelected(node.data?.id ?? null)}
      />
    </div>
  )
}

Vue example

<template>
  <div>
    <p v-if="selected">Selected: {{ selected }}</p>
    <ApexSankey :data="data" :options="options" @nodeClick="onNodeClick" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import ApexSankey from 'vue-apexsankey'

const selected = ref<string | null>(null)
const options = { highlightConnectedPath: true, dimOpacity: 0.15 }

const onNodeClick = (node) => {
  selected.value = node.data?.id ?? null
}
</script>