Expand/Collapse Relayout Toggle (Top-Down Tree)
A relation-graph demo that isolates `reLayoutWhenExpandedOrCollapsed` on a top-down tree. It loads a fixed inline hierarchy, lets users switch between relayout and non-relayout behavior from a draggable floating panel, and inherits secondary canvas settings and image export controls from a shared helper window.
Live Expand/Collapse Relayout in a Top-Down Tree
What This Example Builds
This example builds a full-height top-down tree for comparing what happens after a user clicks relation-graph’s built-in expand or collapse control. The canvas shows a narrow cyan hierarchy with curved teal connectors, bottom-positioned expand holders, and a floating white helper window that stays above the graph.
Users can expand or collapse branches directly on the graph, switch the post-expand behavior between Re-layout and Do not re-layout, drag the helper window to another screen position, minimize it, and open a secondary settings overlay. That overlay is inherited from a shared local helper and adds canvas wheel-mode, canvas drag-mode, and image-download controls, but the main lesson of this example is the runtime toggle for reLayoutWhenExpandedOrCollapsed, not custom transition timing.
How the Data Is Organized
The data is declared inline inside initializeGraph() as one RGJsonData object. It uses rootId: 'a', defines 16 hard-coded nodes, and connects them with 15 explicit lines. The structure is a simple fixed hierarchy rather than a domain model with extra metadata, which keeps attention on expand or collapse behavior instead of on content semantics.
There is no preprocessing step before setJsonData(...). The component does not fetch remote data, normalize records, or derive a second structure for layout. In a real application, the same shape could represent an org subtree, a product taxonomy, a folder tree, a dependency breakdown, or any other hierarchy where the product team needs to decide whether expanding one branch should trigger a fresh layout for the whole visible tree.
How relation-graph Is Used
index.tsx wraps the example in RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() to drive the graph through the instance API. The graph options configure a tree layout that grows from the top, uses treeNodeGapH: 40 and treeNodeGapV: 200, renders narrow rectangular nodes, and connects them with RGLineShape.StandardCurve plus RGJunctionPoint.tb. The visual result is a tall one-direction hierarchy where branch movement is easy to notice when nodes are expanded or collapsed.
The component loads data with setJsonData(...), then immediately calls moveToCenter() and zoomToFit() so the full tree starts centered in view. A second effect watches the React relayout state and pushes it into the mounted graph with updateOptions({ reLayoutWhenExpandedOrCollapsed }). That is the core relation-graph technique in this demo: the layout rule changes at runtime without rebuilding the dataset or remounting the graph component.
There are no custom node, line, canvas, or viewport slots in this example, and there are no explicit graph event handlers in the example-specific code. Expand or collapse behavior comes from relation-graph’s built-in expand holder, which is positioned at the bottom of each node through defaultExpandHolderPosition: 'bottom'. The example also does not implement editing, authoring, or manual layout logic.
The floating control surface comes from the local DraggableWindow helper. That helper is not unique to this example, but it matters here because it makes the relayout selector movable and also provides a shared settings overlay. Inside that overlay, CanvasSettingsPanel uses RGHooks.useGraphStore() to read the current wheelEventAction and dragEventAction, then calls graphInstance.setOptions(...) to update those values on the live graph. It also uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() to support a download action. The local SCSS file only defines empty wrapper selectors, so nearly all visible styling comes from graph options and the shared floating window rather than from custom relation-graph CSS overrides.
Key Interactions
- Clicking a built-in expand holder opens or closes a branch in the tree.
- Clicking
Re-layoutorDo not re-layoutupdatesreLayoutWhenExpandedOrCollapsedon the mounted graph, so the next expand or collapse action either repacks the tree or preserves the current branch positions. - The helper window can be dragged by its title bar, which keeps the control surface movable instead of occupying a fixed part of the canvas.
- The helper window can be minimized, so the example can switch between a teaching mode with visible controls and a cleaner viewing mode.
- The settings button opens a secondary overlay that can switch wheel behavior, switch canvas drag behavior, and download the current graph as an image. Those controls come from the shared helper rather than from the example’s own tree logic.
Key Code Fragments
This fragment shows that the example is deliberately configured as a top-down tree with wide vertical spacing and bottom expand controls so branch reflow is easy to observe:
const graphOptions: RGOptions = {
layout: {
layoutName: 'tree',
from: 'top',
treeNodeGapH: 40,
treeNodeGapV: 200
},
// ... visual defaults omitted
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.tb,
defaultExpandHolderPosition: 'bottom'
};
This fragment shows that the hierarchy is assembled inline rather than fetched or transformed from another source:
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'a' },
{ id: 'b', text: 'b' },
{ id: 'b1', text: 'b1' },
// ... more nodes
],
lines: [
{ id: 'l1', from: 'a', to: 'b' },
// ... more lines
]
};
This fragment shows the mount-time loading pattern: load the data once, then center and fit the result:
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment shows the runtime option-synchronization pattern that makes the example distinct:
const syncOptionsToGraph = () => {
graphInstance.updateOptions({
reLayoutWhenExpandedOrCollapsed: relayout
});
};
useEffect(() => {
syncOptionsToGraph();
}, [relayout]);
This fragment shows that the UI exposes the relayout choice as a direct boolean selector instead of as a rebuild or reset action:
<SimpleUISelect
data={[
{ value: true, text: 'Re-layout' },
{ value: false, text: 'Do not re-layout' }
]}
currentValue={relayout}
onChange={(newValue: boolean) => {
setRelayout(newValue);
}}
/>
This fragment shows how the shared floating window can reveal a secondary settings surface above the main selector:
{
showSettingPanel &&
<div className="absolute w-full h-full left-0 top-[35px]">
<div className="absolute z-10 w-full h-full left-0 bg-white/90" onClick={() => { setShowSettingPanel(false); }}></div>
<div className="absolute z-20 w-full bg-gray-50 border-b border-gray-200">
<CanvasSettingsPanel />
</div>
</div>
}
This fragment shows that the shared overlay updates graph interaction modes through the live instance rather than through a page reload:
const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;
<SettingRow
label="Wheel Event:"
value={wheelMode}
onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>
What Makes This Example Distinct
The comparison data shows that this example is closest to expand-animation2, open-all-close-all, expand-gradually, and layout-tree, but its scope is narrower than each of them. Against expand-animation2, it applies the same relayout decision to a one-direction top-down tree instead of a root-centered layout. Against open-all-close-all, it does not script timed whole-graph playback and does not teach animation orchestration. Against expand-gradually, it does not focus on initial collapse rules. Against layout-tree, it does not compare multiple layout presets or rewrite node properties before layout.
What makes it stand out is the combination of a live reLayoutWhenExpandedOrCollapsed toggle, a top-down tree with bottom expand holders, top-bottom junction points, wide level spacing, and a minimal yes-or-no control panel. That combination makes it a strong starting point when a product team already knows it wants a vertical hierarchy and only needs to decide whether expanding a branch should preserve current positions or trigger a fresh reflow.
The comparison artifact also makes one boundary clear: this is not evidence of custom animation timing, easing, or transition code. The example is about post-expand relayout behavior in relation-graph, not about authoring bespoke animations.
Where Else This Pattern Applies
This pattern transfers well to any hierarchical UI where disclosure behavior has to be chosen deliberately instead of accepted as a default. Examples include org charts, category trees, workflow breakdowns, policy trees, troubleshooting trees, and dependency views where users repeatedly open and close branches while keeping spatial orientation.
It is also a practical reference for configuration-heavy graph products. The same approach can be extended into user-facing behavior toggles, admin settings for hierarchy views, A/B comparisons of disclosure rules, or QA harnesses that verify how a tree feels when relayout is enabled versus disabled without changing the underlying dataset.