Navigating
A large chart is mostly off screen. What matters is how you get to a specific node and how you keep your bearings once you are there.
Go to a node
chart.api.focus('lead-42')focus opens every collapsed ancestor on the way, then centres the node. It works from a fully collapsed chart, which is the case it exists for: it waits for the layout that expanding produced rather than reading a position that does not exist yet. When nothing needed expanding, the move happens immediately.
Add a confirmation flash on arrival:
chart.api.focus('lead-42', { ring: true })The ring fires when the camera gets there, not when it sets off, so the whole flash happens where you are looking. It is off by default — stepping through search results in a loop should move the camera, not strobe.
Showing the way
pathTo returns the chain of ids from the root down to a node, inclusive:
chart.api.pathTo('lead-42') // ['ceo', 'cto', 'eng', 'lead-42']Which is exactly what highlight wants:
chart.api.highlight(chart.api.pathTo('lead-42'))
chart.api.focus('lead-42', { ring: true })A connector is drawn in the highlight colour when both of its endpoints are highlighted. For a root-to-node chain that is precisely the route and nothing else — a highlighted node's other children are not themselves highlighted, so their connectors stay quiet. Scattered highlights (a search result) light the nodes without inventing a path between them.
highlight(null) clears it — and so does Esc.
Showing one branch
chart.api.fitSubtree('eng') // point the camera at it
chart.api.isolate('eng') // make it the chart
chart.api.isolate(null) // and backThe difference matters once a chart is large. fitSubtree moves the camera and leaves everything else where it was, just off screen. isolate re-roots the tree: the layout has one branch to arrange, the minimap shows that branch rather than a speck inside a company, Tab walks it instead of the org, and an export is a picture of it.
Where the viewer is, is yours to say — pathTo(id) returns the chain from the real root, which is a breadcrumb:
const trail = chart.api.pathTo('eng') // ['ceo', 'cto', 'eng']Saving a view
const view = chart.api.getView() // { camera, open, highlighted }
chart.api.setView(view) // arrive there
chart.api.setView(view, { animate: true }) // fly thereWhere the viewer is, as one plain object: the camera, which branches are open, and what is lit. It is serialisable and names nodes by id, so it goes in a URL or a saved report and still works after the data is refetched or grown. Ids it names that have since left the tree are ignored rather than throwing.
Search
const results = chart.api.search('chen')
// [{ id, item, path }, ...]Substring match on the node's label by default, or pass your own predicate:
chart.api.search((item) => item.department === 'Design' && item.level > 3)Each result carries its own path, so a result list can show where a match sits without a second call.
Camera
| Call | What it does |
|---|---|
fit() | Zooms out far enough to show the whole visible tree. |
fitSubtree(id) | Frames one branch instead. On a large chart this is the useful one — fitting everything means a zoom level where nothing can be read. |
isolate(id) | Shows that branch as the chart; isolate(null) puts the rest back. |
reset() | Back to the opening view. |
zoomIn() / zoomOut() | One step, about the centre. |
zoomTo(k) | An exact scale. |
focus(id, opts?) | Centres a node, opening the way to it. |
Every one of them eases rather than jumping, and every one is interrupted the instant a user's hand touches the canvas — dragging always wins immediately.
Keyboard
Click the chart, or Tab to it, and the camera answers to the keyboard:
| Key | |
|---|---|
| Arrows | Pan. Hold Shift for a stride rather than a step. |
+ / - | Zoom about the middle of the view. |
f | Fit the whole chart. |
0 | Back to the opening view. |
Home | Centre the root. |
Esc | Clear the highlight. |
The chart is a tab stop, and the first one inside itself — so "Tab, then arrows" works without walking past every card's own buttons to get there. Keys are left alone when the focus is inside something using them already: an input or <select> on one of your cards, or a row of the accessibility tree, which has its own arrow keys for moving between NODES rather than moving the view.
Set keyboard: false if the surrounding app binds these itself, or if the host must not take focus.
Gestures
| Gesture | |
|---|---|
| Drag with the primary button, or one finger | Pan. A release with speed coasts to a stop. |
| Wheel or trackpad scroll | Zoom about the pointer. |
| Two fingers | Pinch to zoom about the midpoint. |
| Right or middle button | Nothing. Yours — a right-click reaches your own contextmenu handler with the chart holding still under it. |
The host element is given touch-action: none while a chart is mounted, and it is handed back on destroy(). That is what makes a one-finger drag pan the chart instead of scrolling the page, and a pinch zoom the camera instead of the whole document. Text selection is suppressed on the host for the same reason — a pan that starts on a card would otherwise drag-select its label — while buttons, links and form controls inside a card keep working normally.
Expanding and collapsing
chart.api.expand('cto') // just this node
chart.api.expand('cto', true) // and everything below it
chart.api.collapse('cto', true)
chart.api.expandAll()
chart.api.collapseAll()
chart.api.expandTo('lead-42') // open the ancestors, without moving the cameraA single-node toggle keeps that node pinned exactly where it was on screen while the rest of the layout moves around it — to the pixel, on both the worker and main-thread paths. Turn it off with autoPanOnToggle: false if you would rather the camera stayed still and the tree moved under it.
The minimap
minimap: true
minimap: { position: 'top-left', width: 200, height: 140 }
minimap: { silhouetteColour: '#94a3b8' } // for a dark hostIt draws a silhouette of the occupied area rather than a shrunken chart — at that scale individual boxes fall below a pixel — with the current viewport as a rectangle over it. Click or drag inside it to pan.
The plate, its border and the viewport rectangle are ordinary DOM — style them from your own CSS via .klad-minimap. The silhouette is not: it is written pixel by pixel into a canvas, so it is the one part that needs an option, silhouetteColour. The default slate reads well on a light plate and disappears on a dark one, so a dark theme should set it. Only the colour's RGB is used; each pixel's alpha is the silhouette's own coverage.
Its frame is held steady across an expand or collapse rather than refitting to whatever is currently open: a minimap whose scale lurched on every toggle would be a zoom rather than a map, and nothing would stay where you last saw it.