Focus Mode and the Active Path
Two related ways to isolate part of a large tree: focus mode dims everything outside a node's lineage and subtree, and the active path flows a marching dash along the route from the root to a node. They work independently or together.
Focus mode
graph.focus(id) spotlights a node. Everything outside its lineage (its ancestors up to the root) and its visible subtree dims, and the camera springs to frame what is left:
const graph = tree.render(data)
graph.focus('eng') // spotlight
graph.getFocusedNodeId() // 'eng'
graph.clearFocus() // restore
focus() returns false when the node is unknown or not currently rendered, which includes a node hidden inside a collapsed ancestor. Use the return value rather than assuming the call landed:
if (!graph.focus(nodeId)) {
graph.expandToDepth(3)
graph.focus(nodeId)
}
Escape, a re-click on the focused node, or clearFocus() all restore the full view. Focus survives collapse, expand and layout changes for as long as the node stays rendered.
Click to focus
const tree = new ApexTree(el, {
focus: { clickToFocus: true, dimOpacity: 0.7 },
})
focus.clickToFocus (default false) focuses a node when its card is clicked. Clicks on the expand/collapse button keep toggling the subtree as usual, so the two gestures do not fight.
focus.dimOpacity (default 0.7) sets how strongly the rest of the tree is dimmed, from 0 (no dim) to 1 (fully hidden).
The dim is applied by paint order rather than by setting opacity on the cards themselves, which keeps Safari's foreignObject rendering intact.
The active path
graph.setActivePath(ids) lights up the lineage from the root down to each given node. Those edges recolor and a dashed pulse flows along them:
graph.setActivePath('ada') // one lineage: root → ada
graph.setActivePath(['ada', 'kai']) // two lineages
graph.getActivePath() // ['ada', 'kai']
graph.clearActivePath()
graph.setActivePath([]) // same as clearActivePath()
The path is re-asserted after every render, so it survives collapse and expand.
Styling the flow
edgeFlow configures the marching dash:
| Option | Type | Default | Description |
|---|---|---|---|
edgeFlow.color | string | '#5C6BC0' | Stroke color of a flowing edge |
edgeFlow.width | number | 2 | Stroke width in pixels |
edgeFlow.speed | number | 60 | Flow speed in SVG units per second |
edgeFlow.dashLength | number | 8 | Length of each dash, in pixels |
edgeFlow.gapLength | number | 6 | Gap between dashes, in pixels |
edgeFlow.direction | 'toChild' | 'toParent' | 'toChild' | Which way the dashes travel |
edgeFlow.followFocus | boolean | false | Set the active path automatically when focus mode spotlights a node |
const tree = new ApexTree(el, {
edgeFlow: {
color: '#0EA5E9',
width: 3,
speed: 90,
direction: 'toParent',
},
})
'toChild' flows root to leaf, which reads as delegation flowing down. 'toParent' flows leaf to root, which reads as an escalation path.
Combining the two
edgeFlow.followFocus: true wires them together: focusing a node also traces its lineage, and clearFocus() clears the trace.
const tree = new ApexTree(el, {
focus: { clickToFocus: true },
edgeFlow: { followFocus: true },
})
const graph = tree.render(data)
// clicking any card now dims the rest AND flows its root path
Reduced motion
The flow is plain SVG plus injected keyframes, and it respects reduced motion: with the apextree-reduced-motion class on the container the path still highlights in edgeFlow.color, it simply does not animate. See Motion and Animation.
Complete example
const tree = new ApexTree(document.getElementById('chart'), {
direction: 'top',
nodeWidth: 160,
nodeHeight: 60,
focus: { clickToFocus: true, dimOpacity: 0.8 },
edgeFlow: { color: '#5C6BC0', speed: 70, followFocus: true },
})
const graph = tree.render(data)
document.getElementById('search').oninput = (e) => {
const matches = graph.findNodesByQuery(e.target.value)
if (matches.length) {
graph.setActivePath(matches)
graph.centerOnNode(matches[0])
} else {
graph.clearActivePath()
}
}