System Architecture Diagram Editor
This example renders a layered system architecture board from a hardcoded hierarchical dataset while keeping the structural parent-child links hidden. It combines fixed-layout graph loading, custom post-layout group packing, inline node text editing, resize and snap overlays, group toolbar actions, depth presets, palette controls, and image export to create a lightweight architecture editor.
System Architecture Diagram Editor with Group Repacking
What This Example Builds
This example builds a layered system architecture board that users can adjust directly on the canvas. The visible result is a stack of nested section containers with left-rail titles, depth-based background colors, and white leaf cards, while the structural parent-child lines stay hidden.
Users can switch visible depth presets, toggle the minimap, show or hide expand buttons, change the level color palette, open a shared canvas settings panel, export an image, select one node, select an entire subtree, rename nodes inline, resize selected groups, refit group boxes, relayout child items, and append a new child item.
The main implementation highlight is that the example does not rely on a stock automatic layout alone. It loads fixed-position hierarchy data into relation-graph, waits for hierarchy metadata such as node.lot.level and node.lot.childs, then runs a custom packing and refit pass so the graph reads like an architecture workbench instead of a normal node-link diagram.
How the Data Is Organized
The shipped data source is a hardcoded nested tree named aiOutputJson in ai-json-data.ts. Each item carries text, x, y, and optional children, so the source structure is already hierarchical and already contains approximate placement hints.
parseJsonDataByAI() converts that tree into RGJsonData by recursively flattening every item into a node, generating an id, preserving the original coordinates, storing hasChildren and deep in node.data, and creating one parent-child line for each non-root relationship. The graph still loads those lines because the hierarchy logic depends on them, but the example hides them before and after load so the presentation stays box-oriented.
There are also two important preprocessing consequences after setJsonData(). First, relation-graph generates the node.lot hierarchy metadata that the rest of the example depends on. Second, the custom actions layer can reopen the graph to a chosen depth, repack each group’s children, and resize ancestor containers around descendant bounds.
In a real product, the same data shape could represent a platform architecture, capability map, layered product modules, enterprise system landscape, service taxonomy, or any other prepared hierarchy where grouped sections matter more than visible connectors.
How relation-graph Is Used
The entry component wraps the demo with RGProvider, and MyGraph uses RGHooks.useGraphInstance() as the main imperative control surface. The initial RGOptions configure layout.layoutName = 'fixed', rectangular nodes, zero default node border, curved lines, border junction points, left-side expand holders, relayout on expand or collapse, wheel scrolling, canvas dragging, and debug mode.
Initialization uses relation-graph as a staged loader. The example calls loading(), setJsonData(), moveToCenter(), zoomToFit(), and clearLoading(), while MyGraphActions calls doLayout(), updateNodesVisibleProperty(), getNodesRectBox(), updateNodeData(), updateNode(), addNodes(), addLines(), generateNewNodeId(), getDescendantNodes(), and updateEditingControllerView() to keep the editable board coherent.
Slots do most of the visual adaptation. RGSlotOnNode replaces the default graph node with either a leaf card or a grouped container, both rendered through MyEditableNode. RGSlotOnView mounts RGMiniView, RGEditingReferenceLine, RGEditingNodeController, RGEditingResize, and the custom MyNodeToolbar, so editing affordances live in the graph overlay layer instead of in a side form.
The shared DraggableWindow component adds the presentation controls around the graph. It exposes depth presets, minimap and expand-button toggles, color palette changes, wheel-mode and drag-mode switches through setOptions(...), and image export through prepareForImageGeneration() and restoreAfterImageGeneration().
Styling is also part of the relation-graph usage story. The SCSS restyles expand buttons with var(--rg-node-color), gives leaf nodes a bordered card appearance, makes grouped nodes look like padded containers, and moves the top-level and level-1 labels into a left-rail title treatment, including a large vertical root label.
Key Interactions
- Single-clicking a node makes it the active editing selection through
setEditingNodes([node]). - Double-clicking a leaf wrapper or group wrapper selects that node together with all descendants through
getDescendantNodes(...). - Double-clicking the text inside
MyEditableNodeenters inline editing; blur or Enter saves withupdateNode(...), and Escape cancels. - Clicking the canvas clears both checked state and the current editing selection.
- The depth preset selector reruns
openByLevel(...), the custom layout pass, andzoomToFit()so the same board can be inspected at different hierarchy depths. - The floating controls can show or hide
RGMiniView, switch expand-holder visibility, and recolor the board by changing the level palette. - The shared settings panel can switch wheel mode, switch canvas drag mode, and download the current graph as an image.
- When one selected node finishes resizing,
onResizeEndtriggers a child relayout for that node. - When a selected node has children, the overlay toolbar exposes three direct actions: relayout child items, fit the container around descendants, and append a new child item.
Key Code Fragments
This recursive helper proves that the shipped source starts from a nested tree and converts it into flat graph nodes and lines before loading relation-graph.
const nodeJson = {
id: 'n-' + nodes_collect.length,
text: item.text,
x: item.x,
y: item.y,
data: { hasChildren, deep }
};
nodes_collect.push(nodeJson);
if (parentNode) {
links_collect.push({ id: `${parentNode.id}-to-${nodeJson.id}`, from: parentNode.id, to: nodeJson.id });
}
This initialization flow hides structural connectors, loads the prepared graph, then applies depth expansion, custom layout, and viewport fitting.
const initializeGraph = async () => {
const myJsonData: RGJsonData = await parseJsonDataByAI();
myJsonData.lines.forEach(line => {
line.hidden = true;
});
graphInstance.loading();
await graphInstance.setJsonData(myJsonData);
updateGraphStyles();
await myGraphActions.current.openByLevel(8);
await myGraphActions.current.doMyLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
This custom layout pass repacks each non-leaf group and then normalizes the width of the level-1 sections.
for (const node of allNodes) {
if (node.rgChildrenSize > 0) {
const childrenNodes = node.lot.childs;
const nodesWithNewXy = adjustNodeLayout({
nodes: childrenNodes,
width: node.lot.level < 2 ? 1600 : 200,
gap: 20
});
nodesWithNewXy.forEach(newXy => {
const cnode = childrenNodes.find(n => n.id === newXy.nodeId);
if (cnode) this.moveNodeTo(cnode, newXy.newX, newXy.newY);
});
this.updateNodeSizeByChildrenSize(node);
}
}
This refit step shows how a group’s bounding box is recomputed from descendant bounds and then propagated upward to ancestor containers.
const childrenNodesSize = graphInstance.getNodesRectBox(childrenNodes);
const padding = 10;
const leftTitleWidth = node.lot.level <= 1 ? 250 : 0;
const titleHeight = leftTitleWidth > 0 ? 0 : 30;
graphInstance.updateNode(node, {
x: childrenNodesSize.minX - padding - leftTitleWidth,
y: childrenNodesSize.minY - padding - titleHeight,
width: Math.max(groupMinWidth, childrenNodesSize.width + padding * 2) + leftTitleWidth,
height: childrenNodesSize.height + padding * 2 + titleHeight
});
This edit handler shows that inline renaming is a real implemented behavior, not just a visual input mockup.
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
finishEditing();
} else if (e.key === 'Escape') {
setEditingText(node.text || '');
setIsEditing(false);
}
};
const finishEditing = () => {
if (editingText !== node.text) {
onNodeTextChange(node, editingText);
}
setIsEditing(false);
};
This view slot is where the example attaches the minimap, snap reference lines, resize handles, and the group-action toolbar.
<RGSlotOnView>
{showMiniView && <RGMiniView />}
<RGEditingReferenceLine adsorption={true} />
<RGEditingNodeController>
<MyNodeToolbar
onLayoutItemsButtonClick={onLayoutItemsButtonClick}
onFitContentButtonClick={onFitContentButtonClick}
onAddChildrenButtonClick={onAddChildrenButtonClick}
/>
<RGEditingResize />
</RGEditingNodeController>
</RGSlotOnView>
What Makes This Example Distinct
According to the comparison data, the closest viewer-style neighbor is system-architecture-diagram. Both examples share the same hidden-link architecture-board foundation, fixed-layout hierarchy loading, depth-based coloring, left-rail titles, and post-layout container packing. The difference is that this example adds the editing overlay layer: inline renaming, resize handles, snap reference lines, subtree selection, fit-content refit, and add-child actions on top of the same grouped visual language.
Compared with editor-oriented neighbors such as undo-redo-example and freely-draw-lines-on-canvas, this example is narrower and more scene-specific. It does not broaden into history management, generic line authoring, or freehand creation. Instead, it keeps one prepared layered hierarchy and focuses on architecture-aware mutations such as repacking descendants, refitting ancestor boxes, and extending a selected section with one more child item.
Compared with industry-chain, the reusable lesson is also different. Both examples support depth-based inspection and a floating workbench shell, but industry-chain is primarily a read-only hierarchy viewer. This example goes further by combining depth presets with direct manipulation on grouped containers while still keeping the screen visually organized through hidden connectors.
The distinctive combination is therefore not any one feature in isolation. It is the combination of fixed-position hierarchy flattening, hidden-link grouped presentation, custom post-layout repacking, recursive group refit, inline text editing, subtree multi-selection, resize-driven relayout, and lightweight runtime controls on the same canvas.
Where Else This Pattern Applies
This pattern transfers well to semi-editable capability maps, platform architecture boards, layered product inventories, enterprise application landscapes, and operating model diagrams where the hierarchy is known in advance but teams still need limited in-canvas maintenance.
It also fits internal planning tools for AI pipelines, service domains, module ownership maps, compliance control stacks, and technical workbench screens where grouped sections should remain readable without constant visible connectors. The main reusable idea is to keep real structural graph data for logic and editing, while presenting the result as nested architecture panels rather than as a conventional edge-heavy graph.