Basic Custom Node and Minimap
This example is a compact starter for a relation-graph viewer with provider-scoped initialization, custom node rendering, and an embedded minimap. It loads one inline `RGJsonData` payload, renders the root as an animated circular badge, maps child nodes to icon circles with labels below, and keeps node and line clicks diagnostic rather than editable.
Building a Basic Custom-Node Viewer with a Persistent Minimap
What This Example Builds
This example builds a full-height relation graph viewer for a small fictional company network. The finished scene shows one larger root node in the middle, rendered as an animated circular badge, while the surrounding nodes become smaller icon circles with their labels placed underneath. Link labels such as Invest and Executive are drawn on the connection paths, and a minimap stays visible inside the graph view.
Users can inspect the network immediately because the graph is loaded, centered, and fitted on mount. The most important teaching point is not the sample business labels. It is the compact combination of provider-scoped graph startup, custom node rendering, and an embedded overview widget in one very small React example.
How the Data Is Organized
The data lives in one inline staticJsonData constant inside MyGraph.tsx. It uses the standard RGJsonData shape with rootId, nodes, and lines. The reviewed payload contains 19 nodes and 18 labeled lines. Most nodes rely on the global default size of 60 x 60, while the root node sets width: 100 and height: 100 directly in the data so the custom root renderer has more space.
There is no preprocessing step before setJsonData(). The component declares the final graph payload in code and passes it directly into the graph instance during startup. The reusable part is the per-node metadata pattern: each node stores an icon key in data.myicon, and the custom node component turns that field into different Lucide icons. In a real application, the same structure could represent organizations, systems, people, assets, or workflow steps, while data.myicon could be replaced by type, status, or category information.
How relation-graph Is Used
index.tsx wraps the example in RGProvider, and MyGraph.tsx calls RGHooks.useGraphInstance() to access the active instance from provider context. A mount-time useEffect() runs initializeGraph(), which loads the inline JSON, then calls moveToCenter() and zoomToFit() so the viewer opens in a usable state without extra user setup.
The graph options keep the setup narrow but explicit. The example enables debug mode, uses circular nodes, sets defaultNodeWidth and defaultNodeHeight to 60, places line text on the path, and configures the built-in center layout with maxLayoutTimes: 3000. It also sets defaultExpandHolderPosition: 'right' and reLayoutWhenExpandedOrCollapsed: true, even though the local example remains a read-only viewer rather than an editor.
The main customization comes from two relation-graph slots. RGSlotOnNode replaces the default node body with CustomNode, which branches between a dedicated root-node template and an icon-based child-node template. RGSlotOnView mounts RGMiniView, so the minimap lives inside the graph scene instead of in an external panel. Local SCSS then overrides relation-graph’s checked-state classes to recolor selected nodes, node labels, line strokes, and line labels with a magenta accent.
Key Interactions
- The graph initializes itself on mount by loading the JSON payload, centering the canvas, and fitting the viewport.
- A permanent
RGMiniViewgives the user an always-visible overview and a second navigation surface inside the same graph scene. - Node clicks and line clicks are wired for inspection only. They log the clicked item to the console and return
true, but they do not update React state or mutate the graph.
Key Code Fragments
This wrapper shows that the example depends on provider context before any graph instance API is used.
const MyApp: React.FC = () => {
return (
<RGProvider>
<MyGraph />
</RGProvider>
);
};
This inline dataset proves that the graph payload is assembled directly in the component and that icon selection comes from node metadata.
const staticJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '2', text: 'Initrode', width: 100, height: 100, data: { myicon: 'delivery_truck' } },
{ id: '1', text: 'Paper Street Soap Co.', data: { myicon: 'fries' } },
{ id: '3', text: 'Cyberdyne Systems', data: { myicon: 'football' } },
// ...
],
lines: [
{ from: '7', to: '71', text: 'Invest' },
This startup sequence is the core bootstrapping pattern: load the JSON, then center and fit the graph after mount.
const graphInstance = RGHooks.useGraphInstance();
const initializeGraph = async () => {
await graphInstance.setJsonData(staticJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
useEffect(() => {
initializeGraph();
}, []);
This options block shows the example’s layout and default graph styling choices.
const graphOptions: RGOptions = {
debug: true,
defaultLineShape: 1,
defaultNodeShape: RGNodeShape.circle,
defaultNodeWidth: 60,
defaultNodeHeight: 60,
defaultLineTextOnPath: true,
layout: { layoutName: 'center', maxLayoutTimes: 3000 },
defaultExpandHolderPosition: 'right',
reLayoutWhenExpandedOrCollapsed: true
};
This graph body shows that custom node rendering and the minimap are both attached through relation-graph slots.
<RelationGraph
options={graphOptions}
onNodeClick={onNodeClick}
onLineClick={onLineClick}
>
<RGSlotOnNode>
{({ node, checked, dragging }: RGNodeSlotProps) => (
<CustomNode node={node} checked={checked} dragging={dragging} />
)}
</RGSlotOnNode>
<RGSlotOnView>
<RGMiniView />
</RGSlotOnView>
</RelationGraph>
This branch in CustomNode proves that the root node and child nodes intentionally use different visual templates.
const CustomNode: React.FC<RGNodeSlotProps> = ({ node }) => {
if (node.id === '2') {
return (
<div className="my-node-animation-01 z-[555] h-full w-full rounded-full relative text-lg flex place-items-center justify-center overflow-hidden">
<div className="py-2 w-full text-center text-white bg-gray-100 bg-opacity-40 border-t border-b border-gray-500">
{node.text}
</div>
</div>
);
}
This child-node fragment shows how the same slot renderer turns node.data.myicon into icon-based circular nodes with detached labels below.
return (
<div className="h-full w-full rounded-full flex place-items-center justify-center shadow-md">
<IconSwitcher iconName={node.data?.myicon} size={30} />
<div
className="bg-gray-200 text-black px-2 rounded-lg absolute my-node-text"
style={{ marginTop: '100%', transform: 'translateY(15px)' }}
>
{node.text}
</div>
</div>
);
This SCSS fragment proves that the example customizes relation-graph’s checked-state styling instead of leaving the default selection colors unchanged.
.rg-node-peel.rg-node-checked {
.rg-node {
color: #f43ce5;
.my-node-text {
color: #f43ce5;
}
}
}
.rg-line-peel.rg-line-checked {
.rg-line {
stroke: #f43ce5;
What Makes This Example Distinct
The comparison data describes this example as a low-friction starter rather than a menu demo, tooltip demo, or toolbar demo. Its most distinctive trait is the compact combination of provider-scoped startup, one inline RGJsonData payload, root-versus-child custom node rendering, checked-state style overrides, and an always-mounted minimap in the same read-only viewer.
Compared with node-menu-2 and node-menu, this example uses RGSlotOnView as a passive navigation aid through RGMiniView, not as a contextual action surface. Compared with node-tips, it emphasizes persistent overview navigation and a stronger visual identity instead of hover inspection. Compared with toolbar-buttons, its main lesson is custom node composition rather than custom graph chrome or minimap toggling.
It is important to keep the claim narrow. The comparison record does not support saying this is the only example with custom node slots or the only example with a minimap. What it does support is that this example is a particularly compact reference when a team wants one small baseline that combines those pieces without also introducing menu state, tooltip state, or toolbar state.
Where Else This Pattern Applies
This pattern transfers well to lightweight ownership maps, service dependency viewers, ecosystem maps, organization diagrams, and product-relationship screens where the immediate need is a readable branded viewer rather than an editor. The same inline-data-plus-slot structure can be replaced with API data while keeping the startup sequence unchanged.
It also scales into richer interfaces. A team could keep the same RGProvider setup, graph-instance initialization flow, and RGSlotOnNode customization, then add detail drawers, hover panels, filter controls, or business-specific click actions later. The example is useful precisely because those extensions are not implemented yet, so the baseline remains easy to copy and extend.