Center and Tree Layout Composition
This example loads two disconnected relation-graph datasets into one fixed canvas and places them with separate built-in layout passes. It is a focused reference for a bounds-based handoff pattern: center one subgraph first, measure it, then offset a left-to-right tree beside it while keeping the whole scene in one viewer.
Compose Center and Tree Layouts on One Fixed Canvas
What This Example Builds
This example builds a read-only relation-graph viewer that places two disconnected subnetworks inside one canvas. One subgraph is drawn as a center-focused cluster, and the other is drawn as a left-to-right tree positioned to the right of that cluster.
Users see two clearly different visual regions in the same viewport: circular semi-transparent nodes with straight links on the left, and wider rectangular nodes with curved black links on the right. They can pan and zoom the graph, use the built-in bottom-right toolbar, drag or minimize the floating helper window, switch canvas interaction modes from its settings panel, and export the current view as an image. The main lesson is the layout choreography: one fixed host canvas, two separate layout passes, and one bounds-based handoff between them.
How the Data Is Organized
The data is declared directly in MyGraph.tsx as two inline RGJsonData objects: treeJsonData with root a, and centerJsonData with root 2. The example does not call setJsonData(...) on one merged structure. Instead, it inserts each component into the same graph instance with separate addNodes(...) and addLines(...) calls.
Both datasets are preprocessed before layout. Tree nodes are given opacity = 0.5 and color = 'transparent' so the link geometry stays easy to inspect. Center-cluster nodes are restyled more aggressively into 80x80 circles with smaller text, and center-cluster lines are switched to straight border-attached connectors. That preprocessing is what makes the two subnetworks read as different layout regions instead of one accidental disconnected graph.
In a real application, the same shape could represent a central device cluster plus a dependency tree, a core service map plus downstream ownership branches, or one focal entity plus a secondary hierarchy. The demo labels are placeholders, but the data model transfers directly to business IDs, names, and relationship labels.
How relation-graph Is Used
index.tsx wraps the example in RGProvider, and MyGraph.tsx gets the live graph through RGHooks.useGraphInstance(). The graph starts in layoutName: 'fixed', which is the key architectural choice here: the host canvas stays fixed so the example can place each connected component with its own built-in layout pass instead of turning the whole scene over to one global layout.
The graph options also define the shared viewer behavior: 170x40 default nodes, curved left-right link routing by default, on-path line labels limited to 10 characters, black link color, and a built-in toolbar positioned in the bottom-right corner. There are no custom node, line, or canvas slots in this example. Visual differentiation comes from mutating node and line properties before insertion, not from custom render templates.
At mount time, initializeGraph() inserts the tree component first, restyles and inserts the center component, and then calls doLayoutAll(). That layout routine creates a center layouter for the center cluster, expands the cluster with getNetworkNodesByNode(...), places it, measures its bounds with getNodesRectBox(...), and shifts the tree root to maxX + 100. Only after that handoff does it create a left-origin tree layouter for the second component. The combined scene is finished with moveToCenter() and zoomToFit().
The floating DraggableWindow is a local helper component reused across demos, but it still matters here because it exposes graph instance APIs in a practical way. CanvasSettingsPanel reads the current option state with RGHooks.useGraphStore(), switches wheelEventAction and dragEventAction at runtime through setOptions(...), and wraps screenshot export with prepareForImageGeneration() and restoreAfterImageGeneration(). Styling is minimal: my-relation-graph.scss mostly leaves selectors empty except for one checked-line label color override, so most of the example’s appearance is driven by graph options and per-subgraph mutation.
Key Interactions
- The graph behaves as a viewer, not an editor: users inspect a prepared mixed-layout scene rather than creating or reconnecting nodes.
- The floating helper window can be dragged, minimized, and switched into a settings overlay with the gear button.
- The settings overlay changes
wheelEventActionlive betweenscroll,zoom, andnone. - The same panel changes
dragEventActionlive betweenselection,move, andnone. - The built-in relation-graph toolbar remains available in the bottom-right corner of the canvas.
Download Imageprepares the graph DOM for capture, exports the current view, and then restores the graph state.
Key Code Fragments
This fragment shows that the host graph is intentionally kept in fixed mode, with shared defaults that the two subgraphs later override selectively:
const graphOptions: RGOptions = {
defaultNodeWidth: 170,
defaultNodeHeight: 40,
toolBarDirection: 'h',
toolBarPositionH: 'right',
toolBarPositionV: 'bottom',
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineTextOnPath: true,
lineTextMaxLength: 10,
defaultLineColor: '#000000',
layout: { layoutName: 'fixed' }
};
This fragment shows that the two datasets are restyled before insertion so each subgraph gets a different visual grammar:
treeJsonData.nodes.forEach(node => {
node.opacity = 0.5;
node.color = 'transparent';
});
centerJsonData.nodes.forEach(node => {
node.opacity = 0.5;
node.nodeShape = RGNodeShape.circle;
node.width = 80;
node.height = 80;
node.fontSize = 10;
node.color = 'transparent';
});
This fragment shows the edge-level mutation that makes the center cluster use straight border-attached links instead of the default curved routing:
centerJsonData.lines.forEach(line => {
line.lineShape = RGLineShape.StandardStraight;
line.fromJunctionPoint = RGJunctionPoint.border;
line.toJunctionPoint = RGJunctionPoint.border;
});
graphInstance.addNodes(centerJsonData.nodes);
graphInstance.addLines(centerJsonData.lines);
doLayoutAll();
This fragment shows the core placement recipe: center the first component, measure it, offset the second root, and then run the tree layout:
const myCenterLayout = graphInstance.createLayout({
layoutName: 'center'
});
const centerNodes = graphInstance.getNetworkNodesByNode(centerRootNode);
myCenterLayout.placeNodes(centerNodes, centerRootNode);
const nodesRectInfo = graphInstance.getNodesRectBox(centerNodes);
treeRootNode.x = nodesRectInfo.maxX + 100;
const myTreeLayout = graphInstance.createLayout({
layoutName: 'tree',
from: 'left',
treeNodeGapH: 200,
treeNodeGapV: 30
});
This fragment shows that the floating utility panel updates live graph options rather than rebuilding the scene:
<SettingRow
label="Canvas Drag Event:"
options={[
{ label: 'Selection', value: 'selection' },
{ label: 'Move', value: 'move' },
{ label: 'none', value: 'none' },
]}
value={dragMode}
onChange={(newValue: string) => { graphInstance.setOptions({ dragEventAction: newValue }); }}
/>
This fragment shows the shared screenshot flow that prepares the relation-graph canvas, captures it, and restores the graph afterward:
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
The comparison data supports a narrow distinction: this is a compact reference for composing two built-in layouts inside one fixed graph instance without introducing a custom layout manager. It loads two disconnected datasets, runs one center pass and one left-to-right tree pass, and connects those passes with a single measured-bounds handoff. That makes it a cleaner starting point than broader mixed-layout examples when the goal is just one simple two-stage composition.
Compared with gap-of-line-and-node, the same center-plus-tree scene is not used here as a runtime line-tuning board. Compared with canvas-caliper, the scene is left free of rulers and viewport overlays so the placement pipeline is easier to study directly. Compared with mix-layout and organizational-chart, this example stays smaller and more copyable because it does not introduce metadata-driven group managers, custom node slots, or relayout logic after expand and collapse.
Another useful distinction is the amount of visual contrast achieved with minimal machinery. The center cluster becomes circular, semi-transparent, and straight-linked, while the tree keeps wider rectangular nodes, curved black connectors, and truncated on-path labels. The result is a mixed-layout scene that stays visually legible without needing custom rendering layers.
Where Else This Pattern Applies
This pattern is a good fit when one graph needs two structural grammars on one canvas but does not need a full mixed-layout framework. Common transfer cases include equipment topology plus a maintenance tree, a central service cluster plus downstream dependencies, a knowledge hub plus a classification branch, or a workflow entry node plus an approval hierarchy.
It is also useful when teams need a one-time layout handoff from one subgraph to another. The same approach can be extended into dashboards that place a summary cluster first and then attach one or more directional trees beside it, while still keeping the overall graph instance simple and read-only.