Sizing
nodeSize is required, and it is declared rather than measured:
nodeSize: Size | ((item: NodeData) => Size) // Size = { w: number; h: number }Every DOM-based org chart can mount a node, read its getBoundingClientRect(), and lay the tree out around whatever size it turned out to be. This one cannot, and that is not an oversight.
Layout runs inside a Web Worker. There is no DOM there: no element to mount, nothing to measure, no getBoundingClientRect to call. Moving layout off the main thread is a large part of why a 50,000-node tree stays interactive, and declaring sizes is the price. Your content fits the box you declare; it does not decide it.
One size, or a size per node
// Every node the same.
nodeSize: { w: 180, h: 64 }
// Or per node, from its own data.
nodeSize: (item) => (item.parentId === undefined ? { w: 240, h: 88 } : { w: 180, h: 64 })The function is called once per node per layout, not per frame.
When a card changes height
A card that opens a detail pane, grows a badge row, or switches to a taller variant has changed a number the layout was built from. Tell the chart to re-read it:
item.detailOpen = true
chart.api.refresh()refresh() re-reads nodeSize and label for every node and lays out again, keeping expand/collapse state, the camera and the highlight exactly as they are.
Not update()
update(data, options) replaces the data and resets the tree's open state — it throws away exactly what the user was looking at. It is the right call when the data genuinely changed; it is the wrong one for a re-measure.
refresh() snaps rather than animating: it is a re-measure, not a toggle, and there is no single node for a transition to be anchored on.
Animating a size change
Because nodeSize is read at layout time, animating a node's size means animating the number nodeSize returns and re-measuring as it changes:
const OPEN_H = 132
const CLOSED_H = 72
const options = {
nodeSize: (item) => ({
w: 232,
h: CLOSED_H + (OPEN_H - CLOSED_H) * Number(item.slide ?? 0),
}),
}
// Then ease `item.slide` from 0 to 1 over ~200ms, calling `chart.api.refresh()`
// on each frame.That is a full relayout per frame for the duration of the slide. On a few dozen nodes it is free; on a large tree it is not, and the distinction is deliberate — the library's own expand/collapse transition interpolates positions it has already computed, precisely so it never relayouts per frame. Animate sizes on the nodes a user is looking at, not on the whole org.
Spacing
spacing controls the gaps the layout leaves between nodes, in world units:
spacing: { x: 16, y: 48 } // the defaultsx separates siblings, y separates levels — and they swap meaning with a horizontal orientation, so y is always "along the direction the tree grows".