Collapse & Expand
In @joint/react, collapse and expand behavior is built from ordinary graph state plus Paper.cellVisibility, so you shape it to fit your data.
The pattern:
- store UI state such as
collapsedin elementdata - store actual visibility on cells with a top-level
hiddenproperty - filter rendered cells with
cellVisibility - traverse descendants through the
graphinstance and update them withuseGraph()'ssetCell()
This example uses a fixed tree with seven nodes and inline toggle buttons. Click the +/- buttons on branch nodes to collapse and expand subtrees.
How the pattern works​
When a user toggles a branch:
- The node reads its own id with
useCellId()and calls anonToggle()callback - That callback gets the cell from the
graphinstance viauseGraph() - It finds descendant elements with
graph.getSuccessors(cell) - It expands that set into a subtree with
graph.getSubgraph([cell, ...successors]) - It flips the current node's
data.collapsedflag withsetCell() - It sets
hiddenon every descendant cell in that subtree withsetCell() - It calls
wakeUp()fromusePaper()to tell Paper to re-evaluatecellVisibility
The toggled node stays visible. Only its descendants are hidden or shown.
Why cellVisibility matters​
hidden is just state. cellVisibility is what turns that state into visible behavior:
const cellVisibility = ({ model }: { model: dia.Cell }) => !model.get('hidden');
<Paper cellVisibility={cellVisibility} />
After updating hidden on cells, call wakeUp() from usePaper() to tell Paper to re-evaluate cellVisibility for all cells. Auto-layout diagrams get this for free because repositioning triggers view updates, but fixed-position trees need the explicit call.
const { wakeUp } = usePaper();
// ...after setCell() updates...
wakeUp();
This is more efficient than leaving every node rendered and trying to hide them only with CSS, because the hidden cells are skipped by the Paper renderer entirely.
Resetting nested branches​
When a parent branch changes state, the example resets data.collapsed on descendant elements as it shows them again:
setCell(subtreeCell.id, (previous) => {
if (!isElement(previous)) return previous;
return {
...previous,
hidden: false,
data: { ...previous.data, collapsed: false },
};
});
That keeps reopened branches predictable. A parent expand restores the whole subtree to a visible, expanded state instead of preserving older nested toggles.
If your diagram is auto-laid out​
This example uses fixed positions, so collapsing a branch only hides or shows part of the tree.
For auto-laid-out diagrams, rerun layout after toggling if you want the remaining visible nodes to close up around the reduced tree. If your layout is already managed elsewhere, keep the collapse logic the same and treat relayout as a separate step after the subtree visibility update.