Switchable Node Slot Templates
This example keeps one centered relation-graph dataset and switches the visible node body across several custom slot templates at runtime. It combines `RGSlotOnNode`, graph-wide `getNodes()` plus `updateNode(...)` rewrites of `node.type`, wrapper-class theming, `RGMiniView`, and a floating utility window with canvas settings and image export. The result is a compact reference for teams that need alternate node presentations without rebuilding the graph data.
Switching Node Slot Templates Across One Graph
What This Example Builds
This example builds a centered relation-graph scene that reuses one small people dataset while swapping the visible node body across several custom slot templates. The viewer sees the same topology rendered as a green badge, a holographic profile card, an icon pill, a yellow metrics card, or a circular borrower badge, with a floating mini view and a floating utility window above the canvas.
The graph is structurally read-only. The main interaction is template switching: tabs in the helper window rewrite every live node’s type, then the graph recenters and refits so large templates such as the 300 x 450 profile card remain readable. The most important lesson is that the demo treats node-slot customization as a runtime orchestration pattern, not as five separate graph examples.
How the Data Is Organized
The example imports one shared RGJsonData payload from users01.json. It uses rootId: 'a', defines seven nodes, and connects them with six lines in a hub-and-spoke structure from a to b through g.
There is no preprocessing before setJsonData(). The JSON is handed directly to relation-graph, and the slot renderers later read node.data.pic, node.data.name, and node.data.myicon for avatars, labels, and icon selection. After the data is loaded, the runtime updates only change node.type; the graph structure and business fields stay fixed.
In a real product, the same structure could represent employees, customers, assets, devices, or cases. The custom data fields could hold avatar URLs, display names, role icons, scores, or status metadata that need several visual treatments on one graph.
How relation-graph Is Used
The example keeps layout and graph mechanics inside relation-graph while moving almost all node appearance into slots and SCSS. MyGraph.tsx configures a center layout with explicit levelGaps, centered alignment, force_node_repulsion, force_line_elastic, and maxLayoutTimes, while the default node body is made transparent with RGNodeShape.rect and defaultNodeBorderWidth: 0. That makes the slot renderer the visible node surface.
RGHooks.useGraphInstance() drives the lifecycle. On mount, it loads the imported JSON with setJsonData(), then calls moveToCenter() and zoomToFit(). The same instance later powers graph-wide getNodes() plus updateNode(...) mutations, temporary canvas animation during size changes, and the shared export flow in CanvasSettingsPanel.
Customization is split across two slots. RGSlotOnNode branches on node.type and selects NodeSlot1 through NodeSlot5, while RGSlotOnView mounts RGMiniView as a viewport overlay. Around the graph, RGProvider supplies context, DraggableWindow hosts the selector and settings shell, SimpleUIVTabs renders the slot tabs, and the wrapper class slot-style-${slotTeamplateId} activates slot-specific checked-state and theme overrides in SCSS.
This remains a viewer example, not an editor. Runtime APIs change presentation and interaction options, but they do not add, remove, or reconnect graph elements.
Key Interactions
- Choosing
Slot2,Slot3,Slot4,Slot5, orRandomrewrites every live node’stype, then recenters and refits the graph after a short animation-assisted delay. Randommode samples from the same tab list, so some nodes can still fall back to the defaultNodeSlot1branch when the sampled value is the literalrandom.- The floating window can be dragged from its header, minimized, and switched into a settings overlay without unmounting the graph.
- The settings overlay changes
wheelEventActionanddragEventActionon the live graph instance, so the same canvas can switch between scroll, zoom, selection, move, or no input handling. - The
Download Imageaction prepares the graph DOM for export, renders it to a blob withmodern-screenshot, downloads it, and restores the graph afterward. NodeSlot3reacts to relation-graph’scheckedflag and adds an animated gradient plus a custom halo, so selection feedback changes with the active slot family.RGMiniViewgives the larger card modes a stable navigation aid when node dimensions expand substantially.
Key Code Fragments
This fragment shows that the example hides the built-in node surface so the slot renderer becomes the visible body.
const graphOptions: RGOptions = {
debug: true,
defaultJunctionPoint: RGJunctionPoint.border,
defaultNodeColor: 'transparent',
defaultNodeShape: RGNodeShape.rect,
defaultNodeBorderWidth: 0,
defaultLineShape: RGLineShape.StandardStraight,
// ...
};
This fragment shows the centered layout configuration that gives the template gallery enough spacing for large node bodies.
layout: {
layoutName: 'center',
levelGaps: [500, 500, 500],
alignItemsX: 'center',
alignItemsY: 'center',
force_node_repulsion: 2,
force_line_elastic: 0.1,
maxLayoutTimes: Number.MAX_VALUE
}
This fragment shows that the shared dataset is loaded once and the viewport is normalized before any slot switching happens.
const initializeGraph = async () => {
const myJsonData: RGJsonData = graphJsonData;
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
await changeNodeSlot(slotTeamplateId);
};
This fragment shows the graph-wide mutation pattern: every live node gets a new renderer type, then the graph refits after size changes settle.
const allNodes: RGNode[] = graphInstance.getNodes();
for (const node of allNodes) {
const randomSlot = allSlotIds[Math.floor(Math.random() * allSlotIds.length)];
graphInstance.updateNode(node, {
type: newSlotId === 'random' ? randomSlot.value : newSlotId
});
}
graphInstance.enableCanvasAnimation();
await graphInstance.sleep(50);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment shows that one RGSlotOnNode switch fans out into five unrelated node bodies instead of one fixed custom node.
<RGSlotOnNode>
{({ node, checked }: RGNodeSlotProps) => {
switch (node.type) {
case 'slot2': return <NodeSlot2 node={node} />;
case 'slot3': return <NodeSlot3 node={node} checked={checked} />;
case 'slot4': return <NodeSlot4 node={node} />;
case 'slot5': return <NodeSlot5 node={node} />;
default: return <NodeSlot1 node={node} />;
}
}}
</RGSlotOnNode>
This fragment shows that selection styling in slot 3 depends on relation-graph’s checked state, not on a separate local flag.
export const NodeSlot3: React.FC<RGNodeSlotProps> = ({ node, checked }) => {
return (
<div className={`my-slot-3-content ${checked ? 'my-node-animation-01 text-white' : ''}`}>
<IconSwitcher iconName={node.data?.myicon} size={60} />
</div>
);
};
This fragment shows how the wrapper class changes the graph chrome and checked-state treatment for the active slot family.
.slot-style-slot2 {
.relation-graph {
background-color: #050505;
font-family: 'Orbitron', 'Noto Sans SC', sans-serif;
--rg-checked-item-bg-color: rgba(244, 60, 229, 0.3);
.rg-toolbar {
background-color: rgba(248, 246, 246, 0.53);
border: none;
}
.rg-miniview {
background-color: #050505;
}
}
}
This fragment shows that the export flow uses relation-graph’s preparation and restore APIs instead of screenshotting the canvas blindly.
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 places this example near node, hand-drawn-style, css-theme, and custom-line-style, but its emphasis is different from each of those neighbors. Its clearest distinguishing trait is that it rewrites the loaded graph’s node.type across every live node at runtime, then recenters and refits after slot-driven size changes settle.
Against node, the main lesson is not a side-by-side catalog of per-node JSON styling tricks. node-slot-list goes further into graph-wide template switching, where one mounted graph can move among five custom node bodies plus a mixed random mode without rebuilding the dataset.
Against hand-drawn-style, the reusable idea is renderer orchestration more than one themed skin. Against css-theme and custom-line-style, the example is not primarily about recoloring built-in graph surfaces or mutating line presets. It is about swapping node markup itself while keeping the same centered dataset, minimap, and floating workspace shell.
The strongest feature combination is the transparent rectangular base node, center-layout tuning, wrapper-class-driven checked-state themes, and graph-wide updateNode(...) switching on one people dataset. That combination makes the example work more like a runtime node-template gallery than a static custom-node demo.
Where Else This Pattern Applies
This pattern transfers well to products that need multiple node presentations for the same relationships. Examples include staff maps that switch between compact badges and profile cards, risk graphs that switch between icon states and scorecards, or infrastructure views that switch between service icons and detail tiles.
It is also useful for design review and white-label workflows. A team can keep one prepared graph instance, compare alternate node densities or branded skins, and verify how a minimap, selection styling, and export behave before choosing a production template.
The same structure can serve as a regression board for slot systems. Because the topology stays fixed while the renderer changes, it is a practical way to test whether new node templates still fit the layout, selection treatment, and export pipeline.