JavaScript is required

Whole-Graph Expand/Collapse Playback

A relation-graph demo for replaying whole-graph expand and collapse sequences over a fixed centered hierarchy. It loads one inline 39-node dataset, auto-fits the graph, auto-runs expand playback on mount, exposes global `Expand All` and `Collapse All` controls in a draggable helper window, and inherits secondary canvas settings plus image export utilities from a shared helper.

Whole-Graph Expand/Collapse Playback in a Centered Hierarchy

What This Example Builds

This example builds a full-height centered hierarchy viewer that replays branch visibility for the entire graph instead of leaving expansion to isolated node clicks. The canvas shows a root-centered subsystem-style diagram with orange circular nodes, straight gray labeled connectors, and a floating white helper window layered above the graph.

Users can replay Expand All and Collapse All, drag the helper window, minimize it, and open a secondary settings overlay. That overlay comes from a shared local helper and adds wheel-mode, drag-mode, and image-download controls, but the main point of this example is the scripted whole-graph playback: it resets the hierarchy, animates top-down expansion, and also supports leaf-first collapse over the same dataset.

How the Data Is Organized

The data is declared inline inside initializeGraph() as one RGJsonData object. It uses rootId: '2', defines 39 hard-coded nodes, and connects them with 38 explicit lines. Node records only need id and text, while line records only need id, from, to, and the repeated label Subsystem.

There is no preprocessing step before setJsonData(...). The component does not fetch remote data, normalize source records, or derive a second structure for playback. In a real application, the same shape could represent a subsystem tree, a product architecture map, an equipment decomposition, a manufacturing capability hierarchy, or any other fixed root-centered breakdown where a team wants one control to reveal or collapse the full graph in sequence.

How relation-graph Is Used

index.tsx wraps the example in RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() to control the live graph through instance APIs. The graph options configure layoutName: 'center', place expand holders on the right, keep relayout enabled when branches change, use compact 50x50 circular nodes, and render edges with RGLineShape.StandardStraight. The SCSS file then overrides node and line styling to produce the white canvas, orange nodes, checked-state glow, gray strokes, and small gray line labels.

The component loads the inline hierarchy with setJsonData(...), immediately recenters the graph with moveToCenter() and zoomToFit(), and then calls openAll() on mount. That startup path matters because the example is not just a manual demo. It actively resets the graph to a collapsed state and then replays the reveal sequence as soon as the data is ready.

The example-specific logic is built around relation-graph instance APIs rather than custom slots or event handlers. openAll() enables canvas animation, collapses every branch node, gets the root node, and runs a depth-first deepExpandNode(...) traversal. During that traversal, each child is first moved to the parent coordinates with updateNode(...), then the parent branch is expanded with expandNode(...), followed by a fixed sleep(400) pause and recursive descent into the layout-computed children. closeAll() performs the reverse lesson: it marks branch nodes as expanded, gets the root, and runs deepCloseNode(...), which descends first and then calls collapseNode(...) bottom-up with the same timed pause and repeated zoomToFit().

There are no custom node, line, canvas, or viewport slots in this example, and there is no editing workflow. The floating control surface comes from the shared DraggableWindow helper. That helper is not unique to this example, but it matters because it provides the draggable and minimizable shell plus a settings overlay. Inside that overlay, CanvasSettingsPanel uses RGHooks.useGraphStore() to read the active wheelEventAction and dragEventAction, updates them through graphInstance.setOptions(...), and supports image export through prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration().

Key Interactions

  • Clicking Expand All replays a top-down whole-graph reveal from the root instead of expanding only one node.
  • Clicking Collapse All replays a leaf-first collapse sequence for the full hierarchy.
  • The playing state disables both playback buttons while a sequence is running, which prevents overlapping expand and collapse commands.
  • The helper window can be dragged by its title bar, so the action panel does not consume a fixed page region.
  • The helper window can be minimized, which makes it possible to inspect the graph with less UI chrome.
  • The settings button opens a shared overlay that can switch wheel behavior, switch canvas drag behavior, and download the current graph as an image. Those controls are secondary inherited utilities rather than the example’s main lesson.

Key Code Fragments

This fragment shows that the graph is intentionally configured as a centered hierarchy with compact circular nodes, right-side expand holders, and relayout enabled during visibility changes:

const graphOptions: RGOptions = {
    defaultExpandHolderPosition: "right",
    reLayoutWhenExpandedOrCollapsed: true,
    defaultNodeWidth: 50,
    defaultNodeHeight: 50,
    defaultNodeShape: RGNodeShape.circle,
    debug: false,
    layout: {
        layoutName: 'center',
    },
    defaultLineShape: RGLineShape.StandardStraight
};

This fragment shows that the hierarchy is assembled inline and passed directly to relation-graph without any preprocessing step:

const myJsonData: RGJsonData = {
    "rootId": "2",
    "nodes": [
        { "id": "2", "text": "ALTXX" }, { "id": "3", "text": "CH2 TTN" },
        { "id": "4", "text": "CH1 AlCu" }, { "id": "5", "text": "MainFrame" },
        // ... more nodes omitted
    ],
    "lines": [
        { "id": "l1", "from": "2", "to": "5", "text": "Subsystem" },
        { "id": "l2", "from": "2", "to": "6", "text": "Subsystem" }
        // ... more lines omitted
    ]
};

This fragment shows the mount-time loading sequence, including the automatic playback trigger after the graph is centered and fitted:

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
await openAll();

This fragment shows the expand-side playback technique: pre-stage child positions at the parent, expand the branch, wait, then recurse through the computed children:

const deepExpandNode = async (node: RGNode) => {
    if (node.rgChildrenSize === 0) return;
    for (const cnode of node.lot.childs) {
        graphInstance.updateNode(cnode, { x: node.x, y: node.y});
    }
    graphInstance.expandNode(node);
    await graphInstance.sleep(400);
    for (const cnode of node.lot.childs) {
        await deepExpandNode(cnode);
    }
    graphInstance.zoomToFit();
};

This fragment shows that openAll() resets branch visibility before replaying a root-driven expand sequence with canvas animation enabled:

const openAll = async () => {
    setPlaying(true);
    graphInstance.enableCanvasAnimation();
    graphInstance.getNodes().forEach(n => {
        if (n.rgChildrenSize > 0) {
            graphInstance.collapseNode(n);
        }
    });
    const rootNode = graphInstance.getRootNode();
    if (rootNode) await deepExpandNode(rootNode);
    graphInstance.disableCanvasAnimation();
    setPlaying(false);
};

This fragment shows the inverse traversal order for collapse playback: descend first, then collapse on the way back up:

const deepCloseNode = async (node: RGNode) => {
    if (node.rgChildrenSize === 0) return;
    for (const cnode of node.lot.childs) {
        await deepCloseNode(cnode);
    }
    await graphInstance.sleep(400);
    graphInstance.collapseNode(node);
    graphInstance.zoomToFit();
};

This fragment shows that the user-facing controls are global playback buttons, and that they are locked while a sequence is running:

<DraggableWindow>
    <div className="py-2 text-sm">Actions:</div>
    <div className="flex gap-3">
        <SimpleUIButton disabled={playing} onClick={openAll}>Expand All</SimpleUIButton>
        <SimpleUIButton disabled={playing} onClick={closeAll}>Collapse All</SimpleUIButton>
    </div>
</DraggableWindow>

This fragment shows that the shared overlay can still change canvas interaction modes and export the current graph image through the live graph instance:

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 makes the main distinction clear: this is not primarily a relayout-toggle example like expand-animation2 or expand-animation. It is a stronger reference for orchestrating whole-graph playback over time. The unusual part is not just that the graph can expand or collapse, but that both directions are exposed as replayable actions from one control surface.

Compared with nearby examples, this one combines several rare features in the same centered hierarchy: automatic reset-to-collapsed startup, immediate autoplay after mount, dedicated Expand All and Collapse All buttons, top-down recursive reveal, leaf-first recursive collapse, child position pre-staging through updateNode(...), fixed sleep(400) pacing, and canvas animation wrapped around the full sequence. The comparison artifact also makes an important boundary explicit: the floating helper window, wheel-mode switching, drag-mode switching, and image export are shared scaffolding, not the unique point of the sample.

That combination makes this example a better starting point than multiple-expand-buttons when the requirement is to reset and replay the visibility state of the whole hierarchy instead of controlling only one side of the root. It is also a better starting point than drag-and-wheel-event when the goal is async branch sequencing rather than canvas interaction settings.

Where Else This Pattern Applies

This pattern transfers well to guided product tours, architecture walkthroughs, equipment decomposition viewers, subsystem maps, dependency explorers, and presentation-oriented knowledge graphs where users need one command to reveal or collapse the full hierarchy in a controlled order. It is especially useful when instant state flips feel too abrupt and the product needs a readable sequence instead.

The same approach can also be extended into reset flows, onboarding sequences, demo kiosks, QA playback tools, or analyst workbenches that alternate between a fully revealed graph and a fully collapsed baseline. The important point is that these are transfer scenarios, not already-implemented features in the sample.