JavaScript is required

Force Layout Icon Button Nodes

This example shows a force-layout viewer where every node is rendered as the same layered icon button, with `node.data.icon` controlling both the Lucide glyph and the gradient skin. It also reuses the shared floating utility window for runtime canvas settings and image export while keeping the graph itself read-only.

Force Layout Icon Button Nodes

What This Example Builds

This example builds a styling-focused relationship viewer on top of a force-directed relation-graph. The canvas fills the screen with a mustard background, the links are semi-transparent white straight lines, and every visible node is replaced with the same layered icon-button treatment instead of the library’s default label body.

Users can inspect the graph visually, hover or focus a node to reveal its label, open a floating settings panel, switch wheel and drag behavior, and export the canvas as an image. The main point is not graph editing; it is how one reusable node slot can turn a generic graph dataset into a consistent, product-like icon surface.

How the Data Is Organized

The graph data is declared inline as one RGJsonData object. It uses a simple rootId, a flat nodes array, and a flat lines array with from and to references. Each node also carries data.icon, which is the key piece of metadata for this example because it drives both the rendered icon and the CSS modifier class.

There is no preprocessing step before setJsonData(...). The example loads the literal dataset directly, then centers and fits the graph. In a real application, the same shape could represent service categories, device groups, product families, feature clusters, or any entity network where each node type needs a branded but consistent visual skin.

How relation-graph Is Used

RGProvider wraps the example, and RGHooks.useGraphInstance() provides the live graph instance. The local graphOptions switch the layout to force, raise maxLayoutTimes to 80000, make default node chrome transparent, keep the default node shape rectangular, set the background color, and place the built-in toolbar horizontally at the lower-right corner.

The key rendering hook is RGSlotOnNode. Instead of mixing several slot branches, the example uses one slot template for every node. That template reads node.data.icon, adds it to the button class name, and passes the same value into MyNodeIcon, where a Lucide icon map selects the glyph. The result is metadata-driven theming with one renderer rather than many node-specific components.

The surrounding UI comes from shared helper components. DraggableWindow adds the floating description shell, minimize behavior, and a settings overlay. Inside that helper, CanvasSettingsPanel reads graph state from the store, updates runtime options with setOptions(...), prepares the graph for image generation, captures the canvas DOM, and restores the graph afterward. SCSS overrides then remove built-in node borders, soften toolbar chrome, and define the layered icon-button visuals and hover motion.

Key Interactions

The most visible interaction is on the node itself: hover or keyboard focus rotates the back plate, pushes the front plate forward in 3D space, and fades in the text label below the icon. That interaction is what makes the graph feel like a button-based viewer instead of a static network.

The example also binds onNodeClick and onLineClick, but those handlers only log ids to the console. They are inspection hooks, not state-changing features. The floating helper window is more functional: it can be dragged, minimized, opened into a settings overlay, used to switch wheel behavior between scroll, zoom, and none, used to switch canvas drag behavior between selection, move, and none, and used to download an image of the graph.

Key Code Fragments

This fragment shows the core slot renderer: one node template uses node.data.icon for both theming and icon selection.

const CustomNodeComponent: React.FC<RGNodeSlotProps> = ({ node }) => {
    return (
        <div>
            <button className={`icon-btn icon-btn--${node.data?.icon}`} type="button">
                <span className="icon-btn__back"></span>
                <span className="icon-btn__front">
                    <MyNodeIcon color="#ffffff" size="30px" name={node.data?.icon} />
                </span>
                <span className="icon-btn__label">{node.text}</span>
            </button>
        </div>
    );
};

This fragment shows how the graph skin is configured at the library level before any custom slot rendering happens.

const graphOptions: RGOptions = {
    debug: true,
    defaultLineColor: 'rgba(255, 255, 255, 0.6)',
    defaultNodeColor: 'transparent',
    defaultNodeShape: RGNodeShape.rect,
    backgroundColor: '#d2a904',
    toolBarDirection: 'h',
    toolBarPositionH: 'right',
    toolBarPositionV: 'bottom',
    defaultLineShape: RGLineShape.StandardStraight,
    layout: {
        layoutName: 'force',
        maxLayoutTimes: 80000
    }
};

This fragment shows that the example keeps the data model simple and uses data.icon as reusable presentation metadata.

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a', data: { icon: 'football' } },
        { id: 'b', text: 'b', data: { icon: 'fries' } },
        { id: 'b1', text: 'b1', data: { icon: 'delivery_truck' } },
        { id: 'b1-1', text: 'b1-1', data: { icon: 'burger' } }
        // ...
    ],
    lines: [
        { from: 'a', to: 'b', text: '' },
        { from: 'b', to: 'b1', text: '' }
        // ...
    ]
};

This fragment shows the mount-time graph initialization workflow.

if (graphInstance) {
    console.log('Instance ID:', graphInstance.options.instanceId);

    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
}

This fragment shows the SCSS rules that create the hover-revealed label and depth effect.

.icon-btn:focus-visible .icon-btn__back, .icon-btn:hover .icon-btn__back {
    transform: rotate(22.5deg)
}

.icon-btn:focus-visible .icon-btn__front, .icon-btn:hover .icon-btn__front {
    transform: translateZ(3em) rotateX(20deg) rotateY(20deg)
}

.icon-btn:focus-visible .icon-btn__label, .icon-btn:hover .icon-btn__label {
    opacity: 1;
    transform: translateY(20%)
}

This fragment shows the shared runtime controls that change canvas behavior and trigger image export.

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

What Makes This Example Distinct

According to the comparison data, this example stands out less because it introduces many different node templates and more because it stays disciplined around one repeatable pattern. Compared with node-style4 and node-style, it keeps a single icon-button family and lets node.data.icon control the variation, instead of branching into several unrelated slot designs or a fixed chip theme.

Compared with node-style2, it moves the same general viewer shell away from CSS refinement of built-in tree nodes and toward full node-body replacement with RGSlotOnNode on a force layout. Compared with the broader node example, it is a narrower, more product-oriented reference: one metadata-driven slot family, one mustard showcase canvas, hover-only labels, and icon-specific gradient back plates rather than a catalog of styling techniques.

The shared floating helper, runtime settings, and image export are useful here, but they are not the distinguishing claim because the comparison data shows those utilities appear in nearby examples too. The more defensible distinction is the combination of metadata-driven icon mapping, icon-specific gradients, transparent graph chrome, and 3D-like hover motion inside one coherent node renderer.

Where Else This Pattern Applies

This pattern transfers well to any viewer where nodes need a small set of recognizable visual identities without turning the graph into a full editor. Examples include service maps where each service category gets its own icon treatment, product navigation graphs where each family uses a branded chip, restaurant or retail catalogs where item groups need a compact visual marker, and device or capability maps where hover can reveal labels only when the user needs detail.

It also fits teams that want a reusable rendering contract between data and design. If a backend can provide a stable metadata key such as icon, the frontend can keep one RGSlotOnNode implementation and expand the icon map and SCSS theme library over time without changing the graph structure itself.