JavaScript is required

Click-Driven Related Node Group Highlights

This example builds a force-layout viewer that outlines a clicked node and its related neighborhood with a canvas-layer rounded rectangle. It seeds three example groups on load, deduplicates repeated selections, and keeps only the newest three overlays visible for comparison.

Click-Driven Related Node Group Highlights with Canvas Rectangles

What This Example Builds

This example builds a force-layout network viewer that marks neighborhoods with persistent canvas-layer rectangles. The graph starts with three seeded highlight groups, and each saved group is drawn as a rounded translucent outline around one core node plus the related nodes returned by relation-graph.

Users can click more nodes to add another highlighted neighborhood, compare up to three groups at once, and continue interacting with the graph because the overlays do not capture pointer input. The main point of the example is that highlighting is implemented as a separate SVG overlay layer instead of by mutating node and line styles directly.

How the Data Is Organized

The base graph is one inline RGJsonData object with rootId, a flat nodes array, and a flat lines array. The content is a generic branching network rooted at a, so the reusable lesson is not domain-specific data modeling but the runtime grouping pattern placed on top of a normal relation-graph dataset.

Before setJsonData(...) runs, the only preprocessing step is adding id: line-${idx} to each line object. The visible highlight regions are not encoded in RGJsonData; they live in a separate myNodeGroups state array where each item stores a generated groupId, the derived groupNodes, and one random translucent stroke color.

That separation matters in real applications. The same structure can sit on top of service dependencies, investigation graphs, organizational relationships, or recommendation networks where the graph data is stable but temporary neighborhood emphasis is derived from user actions.

How relation-graph Is Used

The page is wrapped in RGProvider, and MyGraph uses RGHooks.useGraphInstance() as the main runtime API surface. The example loads data with setJsonData(...), sets the initial zoom with setZoom(30), derives groups through getNodeById(...) plus getNodeRelatedNodes(...), and stops the ongoing force layout with stopAutoLayout() during cleanup.

The graph options keep the built-in rendering simple: layoutName: 'force', straight lines, border junction points, purple circular nodes, and 60-by-60 default node size. There are no editing APIs, no custom node template, and no custom line renderer. The customization point is RGSlotOnCanvas, which mounts one GroupRectBackground component for each saved group.

GroupRectBackground uses RGHooks.useGraphStore() and recalculates one bounding rectangle from the current x, y, el_W, and el_H of every grouped node. Because the rectangle is derived from live node geometry, the overlay stays aligned while the graph renders and moves. The SCSS file contains only empty selectors, so the final behavior depends mostly on relation-graph options, the full-height wrapper, and the custom SVG overlay component.

Key Interactions

The first meaningful interaction happens automatically on mount. After the graph data loads and the zoom is set, the example seeds groups for d1, b4, and c1, so users immediately see how neighborhood overlays look before clicking anything.

The main user interaction is node click. Clicking a node forwards that node id into createNodeGroup(...), which asks the graph instance for the clicked node and its related nodes, then stores one new group if that core node is not already represented.

Group retention is intentionally limited. Duplicate group ids are ignored, and when a fourth distinct group is added, the oldest saved group is removed so only the newest three overlays remain visible.

The overlays do not block graph interaction because the SVG container uses pointerEvents: 'none'. That makes the rectangles a visual annotation layer rather than a competing interaction layer.

Key Code Fragments

This fragment shows the default graph configuration: a built-in force layout, straight border-attached links, and 60-pixel circular nodes.

const graphOptions: RGOptions = {
    debug: false,
    defaultNodeBorderWidth: 0,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultNodeColor: 'rgb(130,102,246)',
    defaultNodeShape: RGNodeShape.circle,
    defaultNodeWidth: 60,
    defaultNodeHeight: 60,
    layout: {
        layoutName: 'force',
        maxLayoutTimes: Number.MAX_SAFE_INTEGER
    },
    defaultJunctionPoint: RGJunctionPoint.border
};

This fragment proves that the dataset is loaded imperatively, the initial zoom is set after load, and three example groups are seeded immediately.

await graphInstance.setJsonData(myJsonData);
graphInstance.setZoom(30);

createNodeGroup('d1');
createNodeGroup('b4');
createNodeGroup('c1');

This fragment shows the runtime grouping logic: find the core node, derive related nodes from the graph instance, ignore duplicates, and keep only the newest three saved groups.

const createNodeGroup = (nodeId: string) => {
    const groupCoreNode = graphInstance.getNodeById(nodeId);
    if (!groupCoreNode) return;
    const relatedNodes = graphInstance.getNodeRelatedNodes(groupCoreNode);
    const groupNodes = [groupCoreNode, ...relatedNodes];
    const groupId = 'my-group-' + groupCoreNode.id;

    setMyNodeGroups(prev => {
        if (prev.some(g => g.groupId === groupId)) return prev;
        const nextGroups = [...prev, {
            groupId,
            groupNodes,
            mainColor: generateRandomPastelColor()
        }];
        return nextGroups.length > 3 ? nextGroups.slice(1) : nextGroups;
    });
};

This fragment shows that the custom overlays are rendered through relation-graph’s canvas slot instead of through node or line style mutation.

<RelationGraph
    options={graphOptions}
    onNodeClick={onNodeClick}
>
    <RGSlotOnCanvas>
        {myNodeGroups.map(thisGroup => (
            <GroupRectBackground key={thisGroup.groupId} group={thisGroup} />
        ))}
    </RGSlotOnCanvas>
</RelationGraph>

This fragment shows how the overlay rectangle is derived from live node bounds with extra padding so the outline follows graph movement.

const groupBounds = useMemo(() => {
    if (!group.groupNodes || group.groupNodes.length === 0) return null;
    let minX = Infinity, minY = Infinity;
    let maxX = -Infinity, maxY = -Infinity;

    group.groupNodes.forEach(node => {
        minX = Math.min(minX, node.x);
        minY = Math.min(minY, node.y);
        maxX = Math.max(maxX, node.x + (node.el_W || 0));
        maxY = Math.max(maxY, node.y + (node.el_H || 0));
    });

    const padding = 10;

This fragment proves that the rectangle is an unfilled, non-interactive SVG outline rather than a filled area region.

<svg
    className="my-group-svg-bg"
    style={{
        position: 'absolute',
        left: 0,
        top: 0,
        width: '20px',
        height: '20px',
        pointerEvents: 'none',
        overflow: 'visible'
    }}
>
    <rect
        x={groupBounds.x}
        y={groupBounds.y}
        width={groupBounds.width}
        height={groupBounds.height}
        rx="10"
        ry="10"
        fill="none"
        stroke={group.mainColor}
        strokeWidth="10"
    />
</svg>

What Makes This Example Distinct

The comparison data shows that this example is not just another force-layout demo. Its main distinguishing pattern is the combination of RGSlotOnCanvas, runtime related-node lookup, and one dedicated GroupRectBackground component that wraps each saved neighborhood with a layout-aware SVG rectangle.

Compared with nodes-group-area-background, the data-loading flow and grouping logic are very similar, but the geometry and visual weight are different. This example always renders one padded rounded outline rectangle with fill="none", while the sibling example emphasizes filled area coverage and more complex region geometry. That makes the present demo a better starting point when the graph interior must stay visible.

Compared with layout-force-options and layout-force, the force solver is supporting infrastructure rather than the subject of the page. The reusable lesson here is click-driven neighborhood annotation on a live graph, including seeded default groups, deduplicated state, and a capped rolling history of three overlays.

The rarity data also highlights an unusual combination: purple 60-pixel circular nodes, continuous force motion, click-driven related-node grouping, random translucent outline colors, and rectangle bounds recomputed from current node geometry. Together, those choices make the example a focused reference for lightweight multi-neighborhood comparison rather than force-layout tuning or graph editing.

Where Else This Pattern Applies

This pattern transfers well to service-topology viewers where a user clicks one service and wants to keep several impact neighborhoods visible at once without recoloring the whole graph.

It also fits fraud review, investigation, and security triage tools where analysts compare a few related-node clusters side by side while preserving the original node and edge styling.

The same approach is useful in knowledge graphs, recommendation graphs, and internal dependency maps when the requirement is temporary local emphasis that follows live layout changes but remains visually lighter than a filled region or a full selection-management system.