System Architecture Diagram with Custom Layout
This example renders a layered system architecture diagram from an embedded hierarchical dataset. It flattens the source tree into RGJsonData, hides structural links, repacks each group's children with a custom post-layout pass, and adds viewer controls such as depth presets, palette changes, a minimap toggle, and image export.
System Architecture Diagram with Custom Post-Layout Packing
What This Example Builds
This example turns one hierarchical platform description into a layered system architecture board. The finished view is not a conventional node-link diagram. Instead, it reads as stacked architecture sections, with large colored group containers for higher levels and white bordered cards for leaves.
Users can expand or collapse groups, switch the visible depth preset, toggle the minimap, show or hide expand tabs, change the level color palette, double-click any node to select its full descendant subtree, and open a floating settings panel to change canvas behavior or export an image.
The main implementation highlight is the two-step layout strategy. RelationGraph first builds fixed-layout hierarchy metadata, then the example runs its own packing pass to rearrange children inside each container and resize the parent box around the measured result. Combined with hidden links, that makes the graph look like a technical architecture board instead of a visible relationship network.
How the Data Is Organized
The shipped demo starts from an embedded nested tree in ai-json-data.ts. Each item contains text, approximate x and y coordinates, and optional children. At runtime, parseJsonDataByAI() flattens that tree into RGJsonData, generates node ids, preserves the source coordinates, stores depth metadata in node.data, and creates one parent-child line for every non-root relationship.
There are two important preprocessing steps before the final view is shown. First, every generated line is marked as hidden before setJsonData() runs, and the later style pass hides them again after the graph is loaded. Second, after loading, the example recolors nodes by level, expands the hierarchy to an initial threshold, and then performs its own post-layout packing pass before centering and fitting the viewport.
In a real system, the same structure could represent capability maps, product modules, technical stacks, platform layers, business domains, or any other hierarchy where approximate initial coordinates already exist and visible connectors would add more noise than value.
How relation-graph Is Used
The entry component wraps the demo in RGProvider, then MyGraph uses RGHooks.useGraphInstance() as the main control surface. Initialization uses loading(), setJsonData(), moveToCenter(), zoomToFit(), and clearLoading() to load and present the prepared graph. Later runtime behavior depends on instance methods such as getNodes(), updateNodeData(), updateNode(), getNodesRectBox(), updateNodesVisibleProperty(), doLayout(), getDescendantNodes(), setEditingNodes(), and clearChecked().
The graph options deliberately start from a fixed base. The example enables layoutName: 'fixed', rectangular nodes, curved lines, border junction points, left-side expand holders, relayout on expand or collapse, wheel scrolling, canvas dragging, and debug mode. That fixed layout is not the final visual arrangement. It is the intermediate stage that gives the code access to node.lot.level and node.lot.childs, which the custom layout pass then uses to repack children and resize each group container.
Slots carry most of the visual transformation. RGSlotOnNode replaces default nodes with two branches: leaf nodes become white cards, while non-leaf nodes become framed containers with a title region. RGSlotOnView adds an optional RGMiniView and an always-on RGEditingReferenceLine. The shared floating helper window also uses RGHooks.useGraphStore() so its wheel and drag controls reflect the live graph options, and it uses prepareForImageGeneration() plus restoreAfterImageGeneration() to export the current canvas.
Styling is as important as the layout code. The SCSS moves expand holders to the left edge, styles them as colored tabs through var(--rg-node-color), renders level 0 and level 1 titles vertically on the left rail, and keeps leaf cards visually separate from group boxes. Because the example also hides every line, those slot and style overrides become the primary presentation layer.
Key Interactions
- The depth selector shows labels
2through6, maps them to thresholds1through5, reruns expansion, reapplies the custom layout, and fits the viewport again. - Double-clicking either a leaf card or a group container selects the clicked node together with all of its descendants through
getDescendantNodes()andsetEditingNodes(). - A minimap checkbox conditionally mounts or removes
RGMiniView. - A second checkbox updates
defaultExpandHolderPositionat runtime so expand or collapse tabs can stay visible on the left edge or disappear completely. - The palette editor can apply preset color schemes or change individual level colors, and each update recolors the graph by
node.lot.level. - The floating settings overlay can switch wheel behavior, switch canvas drag behavior, and export the current graph as an image.
- Clicking the canvas clears checked state and clears the current editing-node selection.
Key Code Fragments
This recursive branch shows how the nested source tree becomes graph lines and keeps flattening deeper levels.
if (parentNode) {
links_collect.push({
id: `${parentNode.id}-to-${nodeJson.id}`,
from: parentNode.id,
to: nodeJson.id
});
}
if (hasChildren) {
flatTreeData(_childs, nodeJson, nodes_collect, links_collect, deep + 1);
}
This initialization sequence hides connectors, loads the generated graph data, applies expansion and the custom layout, then centers and fits the result.
const initializeGraph = async () => {
const myJsonData: RGJsonData = await parseJsonDataByAI();
myJsonData.lines.forEach(line => {
line.hidden = true;
});
graphInstance.loading();
await graphInstance.setJsonData(myJsonData);
updateGraphStyles();
await openByLevel(8);
await doMyLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
graphInstance.clearLoading();
};
This part of the post-layout pass measures each child cluster and resizes the parent group around the packed children.
const childrenNodesSize = graphInstance.getNodesRectBox(childrenNodes);
const padding = 10;
const leftTitleWidth = node.lot.level <= 1 ? 150 : 0;
const titleHeight = leftTitleWidth > 0 ? 0 : 30;
const groupMinWidth = 160;
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 slot renderer switches between leaf cards and group containers, and both branches attach the subtree-selection double-click behavior.
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => {
const level = node.lot?.level || 0;
const isLeaf = node.rgChildrenSize === 0;
return (
isLeaf ?<div
className={`my-leaf-node min-w-[150px] px-4 py-1 bg-white text-sm`}
onDoubleClick={() => {selectAllDescendantNodes(node);}}
>
{node.text}
</div>
This view slot adds viewer-oriented utilities without turning the example into a full editing workbench.
<RGSlotOnView>
{showMiniView && <RGMiniView />}
<RGEditingReferenceLine adsorption={true} />
</RGSlotOnView>
What Makes This Example Distinct
Compared with system-architecture-diagram-designer, this example keeps the same architecture-board foundation but stays on the presentation side of the pattern. It does not add inline text editing, resize handles, or runtime node creation, so it is the cleaner reference when the goal is to render an architecture board rather than author one.
Compared with use-d3-layout, the main lesson is not external algorithm swapping. The distinctive technique here is to let RelationGraph produce fixed hierarchy metadata, then use that metadata to run a shelf-style child packing pass, resize group containers around descendant bounds, and keep every connector hidden.
Compared with slot-oriented viewer examples such as node-slot-list and node, the slots and styles here are not a general styling gallery. They all serve one narrow visual outcome: a layered architecture board with left-rail titles, depth-based colors, grouped containers, and level-based reveal controls.
The rare combination is the important part: embedded hierarchical JSON flattening, hidden parent-child lines, depth-coded groups, vertical section titles, runtime palette tuning, preset expansion levels, and a custom relayout pass on the same loaded diagram. That combination makes this example a strong starting point for architecture viewers where grouping matters more than visible edges.
Where Else This Pattern Applies
This pattern transfers well to technical capability maps, platform architecture overviews, layered product or service inventories, AI pipeline breakdowns, and enterprise system landscape summaries. It is especially useful when a hierarchy should stay visually grouped, when level-based reveal is helpful, and when explicit links would make the screen harder to read.
It can also be adapted to compliance control maps, operating model diagrams, responsibility stacks, or module dependency summaries where the underlying relationships still matter structurally but do not need to stay visible all the time. The reusable idea is to load a real hierarchy into relation-graph, keep the structural links for logic, and present the result as nested panels with custom packing and slot rendering.