Smart Tree Layout
This example shows how to load one generic relationship dataset into relation-graph's smart-tree layout and switch the mounted graph between horizontal and vertical presets. It also demonstrates the extra runtime step of resizing existing nodes and resetting line shape before relayout, together with a shared viewer shell for mini-view, canvas settings, and image export.
Switching a Smart Tree Layout Between Horizontal and Vertical Views
What This Example Builds
This example builds a read-only smart-tree layout playground around one generic relationship dataset. The default view is a left-to-right tree with amber rectangular nodes and curved connectors, and the same mounted graph can be switched into a top-down preset from a selector in the floating window. In top-down mode, the nodes become tall vertical cards and the label text also turns vertical, so the change is about readability as much as direction.
The page includes a draggable description panel, a settings overlay for wheel and drag behavior, an embedded mini-view, and image export. The most important idea is that the example does not reload a different dataset when the layout changes. It keeps the graph mounted and updates the live graph so each orientation remains legible.
How the Data Is Organized
The graph data is an inline RGJsonData object with rootId: 'root', 41 nodes, and 40 directed lines. The structure is flat rather than nested: nodes are declared as an array of { id, text } objects, and relationships are declared as { from, to, text } edges. That makes the sample closer to generic directional relationship data than to a hand-authored tree JSON structure.
There is no preprocessing step before setJsonData() beyond constructing the object in initializeGraph(). The meaningful preprocessing happens before each relayout: the code picks the active preset, updates every existing node to the preset width and height, updates every existing line to the preset line shape, then reruns layout. In a real system, the same pattern can be applied to dependency chains, referral trees, ownership hierarchies, approval routes, or investigation paths that need multiple readable presentations of the same relationships.
How relation-graph Is Used
The example is wrapped in RGProvider, and both the main graph component and the shared floating utility panel obtain the live graph instance through RGHooks.useGraphInstance(). RelationGraph is mounted once with the horizontal preset, while runtime switching happens imperatively through relation-graph instance APIs.
Two RGOptions presets drive the main behavior. Both use the built-in smart-tree layout, but they do more than change layout.from. The horizontal preset uses a 120x30 rectangular node, RGJunctionPoint.lr, wide horizontal spacing, curved lines, and an amber node and line color. The vertical preset swaps to 30x120 nodes, RGJunctionPoint.tb, large vertical spacing, and the same curved connector style.
When the selector changes layoutFrom, the example calls setOptions() with the target preset, then uses getNodes() and updateNode() to rewrite every already-mounted node to the preset geometry. It also uses getLines() and updateLine() to reset line shape before calling sleep(100), doLayout(), moveToCenter(), and zoomToFit(). That sequence is the main technical lesson: preset defaults do not automatically restyle nodes that are already in the graph, so the live graph is normalized before layout runs again.
The example also uses RGSlotOnView to place RGMiniView inside the graph overlay layer. The shared CanvasSettingsPanel reads dragEventAction and wheelEventAction from RGHooks.useGraphStore(), updates them through setOptions(), and uses prepareForImageGeneration() plus restoreAfterImageGeneration() around a DOM-to-image export flow.
There is no structural editing workflow here. onNodeClick and onLineClick are wired, but they only log the clicked objects. Styling is customized locally through my-relation-graph.scss, which forces white node text in the default theme and switches labels to writing-mode: vertical-rl when the top-down layout class is active.
Key Interactions
- The layout selector switches between horizontal and vertical
smart-treepresets and triggers option replacement, live node and line normalization, relayout, recentering, and zoom-to-fit. - The description window can be dragged and minimized, so the explanatory shell can move out of the way without leaving the page.
- The settings overlay changes wheel behavior between scroll, zoom, and none, and canvas drag behavior between selection, move, and none.
- The
Download Imageaction exports the current graph view without rebuilding the graph.
Key Code Fragments
This fragment shows that the example defines a dedicated horizontal smart-tree preset instead of relying on default layout behavior.
const graphOptionsH: RGOptions = {
layout: {
layoutName: 'smart-tree',
treeNodeGapH: 300,
treeNodeGapV: 10,
from: 'left'
},
defaultNodeWidth: 120,
defaultNodeHeight: 30,
defaultJunctionPoint: RGJunctionPoint.lr
};
This fragment shows that the top-down preset changes geometry as well as direction.
const graphOptionsV: RGOptions = {
layout: {
layoutName: 'smart-tree',
treeNodeGapH: 10,
treeNodeGapV: 300,
from: 'top'
},
defaultNodeWidth: 30,
defaultNodeHeight: 120,
defaultJunctionPoint: RGJunctionPoint.tb
};
This fragment proves that the example updates already loaded nodes before rerunning layout.
const targetOptions = layoutFrom === 'left' ? graphOptionsH : graphOptionsV;
graphInstance.setOptions(targetOptions);
graphInstance.getNodes().forEach((node) => {
graphInstance.updateNode(node, {
width: targetOptions.defaultNodeWidth,
height: targetOptions.defaultNodeHeight
});
});
This fragment completes the relayout flow by resetting line shape, recalculating layout, and refitting the viewport.
graphInstance.getLines().forEach((line) => {
graphInstance.updateLine(line, {
lineShape: targetOptions.defaultLineShape
});
});
await graphInstance.sleep(100);
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment shows that the UI exposes the orientation switch as a direct two-option control.
<SimpleUISelect
data={[
{ value: 'left', text: 'Horizontal Tree' },
{ value: 'top', text: 'Vertical Tree' }
]}
currentValue={layoutFrom}
onChange={(newValue: string) => { setLayoutFrom(newValue); }}
/>
This fragment proves that top-down mode changes text flow, not only node placement.
.my-graph.my-layout-top {
.relation-graph {
.rg-node-peel {
.rg-node {
.rg-node-text {
writing-mode: vertical-rl;
}
}
}
}
}
This fragment shows how the shared utility shell wraps relation-graph’s image-generation APIs around screenshot export.
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();
What Makes This Example Distinct
Within the prepared comparison set, this example is not distinctive simply because it switches between horizontal and vertical tree views. Other examples do that too. What stands out here is that the built-in smart-tree layout itself is the subject of the demo, and the same generic relationship dataset stays mounted while the graph is adapted to different orientations.
Compared with layout-tree, this example is the more specific reference for applying smart-tree to flat directional relationship data rather than to a more conventional tree example. Compared with io-tree-layout, it stays focused on orientation-aware smart-tree relayout instead of post-layout per-link routing and anchor rewriting. Compared with bothway-tree and layout-diy, it avoids branch-polarity semantics and custom coordinates and keeps the lesson centered on readability across orientations.
The prepared comparison also highlights a rare combination: two smart-tree presets, live node and line normalization before doLayout(), orientation-specific node geometry, vertical label flow in top-down mode, curved connectors, and a reusable viewer shell with mini-view and export support. That makes this example a strong starting point when the problem is presentation switching rather than custom routing or editing.
Where Else This Pattern Applies
This pattern transfers well to cases where one directional graph needs both landscape and portrait presentations without duplicating the data model. Examples include approval chains that need a wide desktop view and a report-friendly export, dependency trees that switch between exploration and documentation modes, affiliate or ownership networks with long labels, and investigation or escalation graphs that need alternate orientations for different screens.
The reusable idea is simple: keep one RGJsonData payload, define a small set of orientation-specific presets, and normalize the live node and line geometry before relayout. When orientation changes also affect label direction, node aspect ratio, or connector style, this example shows the extra steps needed to make the result stay readable.