Center Layout Spacing Controls
This example demonstrates live spacing control for relation-graph's center layout on one fixed hierarchy. It compares a global distance coefficient with explicit per-level gap arrays, and wraps those controls in a draggable utility window that also exposes canvas settings and image export.
Tuning Center Layout Spacing at Runtime
What This Example Builds
This example builds a full-viewport relation graph demo for the center layout, with a floating control window layered above the canvas. The graph itself is a fixed branching hierarchy rendered as small teal circles connected by teal lines, so layout changes remain easy to see.
Users can switch between two spacing strategies. One mode changes a single distanceCoefficient value with a slider, and the other mode edits explicit per-level gaps through a comma-separated text field plus preset shortcuts. The same floating window also supports dragging, minimizing, opening a canvas-settings overlay, and exporting the current graph view as an image.
The main point is not the sample data. The main point is that the exact same hierarchy can be relaid out live with either a global spacing multiplier or a per-level spacing array.
How the Data Is Organized
The graph data is declared inline as one RGJsonData object with rootId: 'a', a nodes array, and a lines array. There is no preprocessing step for the graph payload before setJsonData() runs. Initialization simply loads the static dataset, centers the viewport, and fits the graph to the available space.
The only runtime preprocessing in this example is for layout parameters, not graph data. In level-distance mode, the levelDistance string is split on commas, converted with parseInt, and passed to layout.levelGaps.
In real projects, this same shape can stand in for organizational levels, product category trees, capability maps, dependency trees, or any hierarchy where different depth levels need different visual spacing.
How relation-graph Is Used
The page is wrapped in RGProvider, and both the example component and the shared settings panel read the active graph instance with RGHooks.useGraphInstance(). The graph is rendered through RelationGraph, using a small initial options object rather than custom node or line slots.
The initial graph options set layout.layoutName to center, disable debug output, and define the main visual defaults in code: 50x50 nodes, circular node shape, no node border, and teal colors for nodes and lines. After mount, the component loads the inline hierarchy with setJsonData(), then calls moveToCenter() and zoomToFit().
Runtime layout changes are handled by rebuilding only the layout portion of the options. When configType === 1, the code writes distanceCoefficient; otherwise it parses the comma-separated input and writes levelGaps. It then applies the layout with setOptions({ layout }) and explicitly calls doLayout().
The floating utility window comes from the shared DraggableWindow component. That component also uses RGHooks.useGraphStore() and setOptions() to switch wheelEventAction and dragEventAction, and it uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() during screenshot export.
This example does not use relation-graph slots, custom node templates, or editing APIs. A local stylesheet is imported and defines checked-state overrides under a .my-graph scope, but the visible customization in this demo mainly comes from graph options and the shared floating window.
Key Interactions
The primary interaction is the mode toggle between two spacing strategies. The selector shows either a range slider for distanceCoefficient or a text-driven levelGaps editor, never both at once.
In coefficient mode, dragging the slider updates React state and immediately triggers a new layout pass. In level-gap mode, users can type a comma-separated list such as 100,150,200,250,300, or click one of the preset strings to push a ready-made spacing pattern into the same state variable.
The graph data stays read-only, but the workspace itself remains interactive. The floating control window can be dragged and minimized, its settings overlay can switch wheel and drag behavior on the canvas, and the same overlay can export the current graph image.
Key Code Fragments
This fragment shows that the demo starts with the center layout and keeps the visual defaults intentionally simple so spacing changes remain the main signal.
const graphOptions: RGOptions = {
debug: false,
defaultNodeWidth: 50,
defaultNodeHeight: 50,
defaultLineColor: 'rgba(0, 186, 189, 1)',
defaultNodeColor: 'rgba(0, 206, 209, 1)',
defaultNodeShape: RGNodeShape.circle,
defaultNodeBorderWidth: 0,
layout: {
layoutName: 'center',
distanceCoefficient: 1
}
};
This fragment shows that the graph payload is a fixed inline hierarchy that is loaded once and then centered and fitted in the viewport.
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'a' },
{ id: 'b', text: 'b' },
{ id: 'b1', text: 'b1' },
// ...
],
lines: [
{ id: 'l-1', from: 'a', to: 'b' },
{ id: 'l-2', from: 'b', to: 'b1' },
// ...
]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment proves that the example compares two layout-spacing modes by rebuilding only the layout options and rerunning doLayout().
const layoutOptions: RGLayoutOptions = {
layoutName: 'center'
};
if (configType === 1) {
layoutOptions.distanceCoefficient = distanceCoefficient;
} else {
const _levelDistance = levelDistance.split(',').map(i => parseInt(i, 10));
layoutOptions.levelGaps = _levelDistance;
}
graphInstance.setOptions({
layout: layoutOptions
});
await graphInstance.doLayout();
This fragment shows how the UI keeps the two spacing controls mutually exclusive and connects both of them back to React state.
<SimpleUISelect
data={[
{ value: '1', text: 'Set by Distance Coefficient' },
{ value: '2', text: 'Set by Level Distance' }
]}
currentValue={configType}
onChange={(newValue: string) => { setConfigType(parseInt(newValue)); }}
/>
<div style={{ display: configType === 1 ? 'block' : 'none', paddingTop: '10px' }}>
<div className="py-1">Distance Coefficient: {distanceCoefficient}</div>
</div>
This fragment shows that the shared overlay is not only decorative; it directly drives relation-graph canvas behavior and the image-export flow.
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');
}
await graphInstance.restoreAfterImageGeneration();
What Makes This Example Distinct
Compared with nearby layout playgrounds, this example stays tightly focused on one question: how spacing behaves inside the center layout when you switch between distanceCoefficient and levelGaps. That makes it more specific than graph-angle-offset or switch-layout, which compare multiple layout families or orientation settings instead of going deep on one layout’s spacing rules.
Compared with layout-center, this demo avoids batch restyling of nodes and lines and keeps the graph appearance nearly fixed. The centered teal hierarchy works as a visual probe, so the visible differences come from spacing alone.
Compared with tree-distance, it applies the same runtime tuning pattern to the center layout rather than to a directional tree layout. The most distinctive combination here is a small read-only centered graph, two mutually exclusive spacing modes, raw and preset levelGaps strings, and a reusable floating utility window with canvas-mode switches and image export.
Where Else This Pattern Applies
This pattern transfers well to internal tooling where teams need to calibrate hierarchy spacing before they settle on final defaults. Examples include org-chart spacing experiments, skill-tree tuning, product taxonomy viewers, dependency explorers, and capability maps with uneven depth.
It also works as a reusable pattern for small graph workbenches: keep one representative dataset fixed, expose only a few layout parameters, and rerun layout against the live graph instance. That approach is useful when the goal is parameter validation or design review rather than full graph editing.