Expand Tree to Selected Level
This example loads a fixed subsystem hierarchy into a left-to-right tree and lets the user switch the visible disclosure depth from a floating preset selector. It is a focused reference for using computed `node.lot.level` values with `updateNode()`, `doLayout()`, and viewport refit calls to reset expansion state across the whole graph.
Left-to-Right Tree with Preset Level Expansion
What This Example Builds
This example builds a read-only hierarchy viewer for a subsystem map. The graph opens as a left-to-right tree rooted at ALTXX, with curved labeled links and default rectangular nodes that keep the presentation close to the base relation-graph look.
Users can change the visible disclosure depth from a small floating selector, drag or minimize the helper window, open the shared canvas settings overlay, and download the current graph as an image. The most important behavior is not local branch clicking, but a global reset that redefines the collapse boundary for the whole tree from one preset level.
How the Data Is Organized
The data is declared inline inside initializeGraph() as one RGJsonData object. It uses rootId: '2', a flat nodes array, and a flat lines array with explicit line IDs and repeated Subsystem labels, so the hierarchy is encoded through parent-child links rather than nested children.
There is no preprocessing before setJsonData(). The example loads the dataset directly, then performs its important state logic afterward by reading computed node.lot.level values from the mounted graph and using them to decide which nodes become the collapse boundary for the selected depth.
In a real application, the same structure could represent a product breakdown, a bill of materials, a department hierarchy, a capability tree, or a process taxonomy where the source data already exists as IDs and links.
How relation-graph Is Used
The page is wrapped in RGProvider in index.tsx, and the graph logic uses RGHooks.useGraphInstance() inside MyGraph. The shared floating utility window also uses relation-graph hooks, so data loading, option changes, and image export all operate against the same active graph instance.
graphOptions configures a left-to-right tree with layout.layoutName = 'tree', from = 'left', and levelGaps = [400, 400, 400, 400]. It also keeps reLayoutWhenExpandedOrCollapsed = true, places expand holders on the right, uses rectangular nodes, and renders curved left-right connectors with line text on the path through RGLineShape.Curve2 and RGJunctionPoint.lr.
The runtime sequence is straightforward. On mount the component calls setJsonData(), applies the current preset through openByLevel(openLevel), then recenters and fits the viewport. Later depth changes reuse getNodes(), updateNode(), doLayout(), moveToCenter(), and zoomToFit() so the visible disclosure depth and viewport stay synchronized.
This demo does not add custom node slots, line slots, or canvas slots. It relies on the default RelationGraph renderer, while DraggableWindow contributes the floating shell, settings overlay, and image export controls. The local SCSS file is mostly selector scaffolding, so most of the visual result comes from graph options rather than custom styling.
Key Interactions
- The preset selector switches between
Root Node Onlyand levels1,2, and3. - Each preset change expands every node first, then collapses the nodes whose
Math.abs(node.lot.level)matches the selected level, making that level the new global boundary. - After each depth reset, the graph reruns layout and refits the viewport with
moveToCenter()andzoomToFit(). - The floating helper window can be dragged, minimized, and switched into a canvas-settings overlay.
- The shared settings overlay can change wheel behavior, change canvas drag behavior, and export the rendered graph as an image.
Key Code Fragments
This fragment shows that the graph is configured as a left-to-right tree with wide level spacing, curved connectors, and default rectangular nodes.
const graphOptions: RGOptions = {
reLayoutWhenExpandedOrCollapsed: true,
defaultExpandHolderPosition: 'right',
defaultNodeShape: RGNodeShape.rect,
defaultNodeBorderWidth: 1,
defaultLineShape: RGLineShape.Curve2,
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineTextOnPath: true,
layout: {
layoutName: 'tree',
from: 'left',
levelGaps: [400, 400, 400, 400]
}
};
This fragment shows that the example loads one inline flat hierarchy with explicit node and line records.
const myJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '2', text: 'ALTXX' }, { id: '3', text: 'CH2 TTN' }, { id: '4', text: 'CH1 AlCu' },
{ id: '5', text: 'MainFrame' }, { id: '6', text: 'TestMainSys' }, { id: '7', text: 'Automotive Parts' },
{ id: '8', text: 'Automotive Process' }, { id: '9', text: 'Process Quality Inspection' },
{ id: '10', text: 'Zhuoli Manufacturing' }, { id: '11', text: 'Piezoelectric Switch' }
],
lines: [
{ id: 'l1', from: '2', to: '5', text: 'Subsystem' },
{ id: 'l2', from: '2', to: '6', text: 'Subsystem' }
]
};
This fragment shows the core pattern of the example: depth is controlled after load by inspecting computed node.lot.level values and updating expansion state in batch.
const openByLevel = async (level: number) => {
graphInstance.getNodes().forEach((node: RGNode) => {
graphInstance.updateNode(node, { expanded: true });
});
graphInstance.getNodes().forEach((node: RGNode) => {
if (node.lot.level !== undefined && Math.abs(node.lot.level) === level) {
graphInstance.updateNode(node, { expanded: false });
}
});
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
This fragment shows that the main control surface is a small preset selector in the floating helper window, not a custom node slot or editor toolbar.
<DraggableWindow>
<div className="c-option-name">Expand to level:</div>
<SimpleUISelect
data={[
{ value: '0', text: 'Root Node Only' },
{ value: '1', text: '1' },
{ value: '2', text: '2' },
{ value: '3', text: '3' }
]}
currentValue={openLevel.toString()}
onChange={(newValue: string) => {
setOpenLevel(parseInt(newValue));
}}
/>
</DraggableWindow>
This fragment shows how the shared helper window turns the current canvas into a downloadable image without changing the graph data model.
const downloadImage = async () => {
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
if (imageBlob) {
downloadBlob(imageBlob, 'my-image-name');
}
};
What Makes This Example Distinct
Compared with expand-gradually, this example uses one preset selector to recompute the whole tree state instead of waiting for node clicks to reveal one collapsed branch at a time. That makes it a cleaner reference when the requirement is “jump directly to a chosen visible depth” rather than “incrementally open the next branch.”
Compared with industry-chain, it keeps the same general idea of using computed level metadata after load, but applies it in a much narrower way. node.lot.level is used only to define the collapse boundary and trigger relayout, not to recolor nodes, assign semantic level classes, or drive a custom card-based hierarchy view.
Compared with tree-data and generate-image-with-watermark, it goes beyond passive tree rendering or export-oriented scaffolding by making post-layout depth selection the main reusable pattern. The combination of an inline subsystem hierarchy, a left-to-right curved tree with 400-pixel level gaps, and a draggable floating control window is relatively uncommon in the example set, especially without adding custom slots or editing tools.
Where Else This Pattern Applies
This pattern transfers well to read-only hierarchy viewers where users need quick jumps between overview depth and detail depth without manually opening every branch.
Typical migration targets include subsystem decompositions, product module trees, service dependency breakdowns, policy or standards taxonomies, knowledge outlines, and organization capability maps. The reusable idea is to let relation-graph compute the structural level once, then use that derived level to reset disclosure state, relayout the tree, and refit the viewport after every preset change.