JavaScript is required

Node and Line Tooltips with Context Menu

This example shows how to attach custom hover tooltips and right-click menus to both nodes and lines in a relation-graph viewer. It uses graph hit-testing, view-relative overlay positioning, and slot-based rendering to keep those panels aligned inside the graph surface. A shared helper window also exposes canvas settings and image export.

Custom Tooltips and Right-Click Menus for Nodes and Lines

What This Example Builds

This example builds a viewer-style relation graph with two kinds of contextual overlays: hover tooltips and right-click menus. Users see a full-screen graph with circular icon-based nodes, curved labeled lines, a dotted canvas background, and a floating helper window. Hovering a node shows its label and icon key, hovering a line shows its label plus resolved source and target nodes, and right-clicking either target opens a dedicated menu near the pointer.

The most important point is not the sample dataset itself. The example is a compact reference for attaching custom inspection UI to both nodes and lines while keeping those panels in the graph view layer instead of hard-coding them into the page layout.

How the Data Is Organized

The graph data is declared inline as one RGJsonData object inside initializeGraph(). It uses a rootId, a flat nodes array, and a lines array. Each node carries a data.myicon field, and each line has both an explicit id and a text label.

There is no separate preprocessing pipeline before setJsonData(), and the example does not call doLayout(). The only preparation is structural: nodes are annotated with icon metadata, and lines are given stable ids and labels so the overlay layer can show meaningful details after hit-testing.

In a business dataset, the same shape could represent service dependencies, device connections, approval flows, team relationships, or product-to-feature mappings. The node data object can hold icon keys, status, risk level, or category tags, while line metadata can hold relationship type, direction, or SLA text.

How relation-graph Is Used

The page is wrapped in RGProvider, and the main component retrieves the provider-scoped instance with RGHooks.useGraphInstance(). On mount, it loads the inline graph by calling setJsonData(), then immediately calls moveToCenter() and zoomToFit() so the whole sample graph is visible without additional user action.

The graph options stay focused on presentation defaults rather than editing. defaultNodeShape is set to RGNodeShape.circle, defaultLineShape is RGLineShape.StandardCurve, and defaultJunctionPoint is RGJunctionPoint.border. The example does not define a custom layout algorithm or mutation workflow.

Two slot layers do most of the work. RGSlotOnNode replaces the built-in node body with icon-centered content derived from node.data.myicon, and RGSlotOnView renders all floating overlays: node tooltips, line tooltips, node menus, and line menus. This separation matters because the contextual panels are positioned in view coordinates, not embedded inside node markup.

The interaction logic depends on several graph instance APIs. isNode() and isLine() convert the current DOM target into graph objects during hover. getLinkByLine() resolves a rendered line back to a link so the line tooltip can show endpoint labels. getViewBoundingClientRect() converts browser client coordinates into graph-view-relative positions for overlays. setCheckedNode() highlights the right-clicked node, and clearChecked() removes that state when the user clicks the blank canvas.

The example also reuses a shared DraggableWindow helper. That helper is not the main subject of the demo, but it exposes RGHooks.useGraphStore() and setOptions() so users can switch wheel and drag behavior, and it uses prepareForImageGeneration() plus restoreAfterImageGeneration() to export the graph as an image.

Key Interactions

  • Hovering a node opens a dark tooltip showing the node text and icon key.
  • Hovering a line opens a dark tooltip showing the line text and the resolved from and to node labels.
  • Right-clicking a node opens a node-specific menu and checks that node visually.
  • Right-clicking a line opens a separate line-specific menu after the code resolves the line back to its link object.
  • Clicking a node or line closes any open overlay.
  • Clicking the blank canvas closes all overlays and clears checked state.
  • Opening the shared settings panel lets users switch wheel mode, switch drag mode, and export the graph image, but those controls come from shared viewer scaffolding rather than example-specific menu actions.

Key Code Fragments

This fragment shows the inline dataset shape, including the node icon metadata and labeled line records that the overlays later reuse.

const myJsonData: RGJsonData = {
    rootId: '2',
    nodes: [
        { id: '1', text: 'Node-1', data: { myicon: 'star' } },
        { id: '2', text: 'Node-2', data: { myicon: 'settings' } },
        { id: '3', text: 'Node-3', data: { myicon: 'settings' } },
        // ...
    ],
    lines: [
        { id: 'l1', from: '7', to: '71', text: 'Line text1' },
        { id: 'l2', from: '7', to: '72', text: 'Line text2' }
    ]
};

This fragment shows the initialization path: load the prepared JSON, then center and fit the graph.

useEffect(() => {
    initializeGraph();
}, []);

const initializeGraph = async () => {
    // ...
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

This fragment shows how the right-click menu is anchored inside the graph view instead of the page and how the handler branches by graph object type.

const viewRect = graphInstance.getViewBoundingClientRect();
const position = {
    x: event.clientX - viewRect.left + 10,
    y: event.clientY - viewRect.top + 10
};

if (objectType === 'node' && object) {
    const node = object as RGNode;
    graphInstance.setCheckedNode(node.id);
    setCurrentNode(node);
    setNodeMenuPanelPosition(position);
}

This fragment shows the hover hit-testing flow that switches between node and line tooltip state.

const el = $event.target as HTMLElement;
const node = graphInstance.isNode(el);
const viewRect = graphInstance.getViewBoundingClientRect();
const position = {
    x: $event.clientX - viewRect.left + 10,
    y: $event.clientY - viewRect.top + 10
};

if (node) {
    setCurrentNode(node);
    setNodeTipsPosition(position);
    setIsShowNodeTips(true);
}

This fragment shows that the overlays live in RGSlotOnView, while node rendering is customized separately with RGSlotOnNode.

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="w-12 h-12 flex place-items-center justify-center">
            {renderIcon(node.data?.myicon)}
            <div className="absolute top-[100%] px-2 py-0.5 text-xs rouned bg-gray-300">
                {node.text}
            </div>
        </div>
    )}
</RGSlotOnNode>
<RGSlotOnView>
    {isShowNodeTips && (
        <div className="c-tips" style={{ left: nodeTipsPosition.x, top: nodeTipsPosition.y }}>
            <div>NodeId: {currentNode?.text}</div>
            <div>Icon: {currentNode?.data?.myicon}</div>
        </div>
    )}
</RGSlotOnView>

This fragment shows the style split between dark tooltips, white context menus, and the dotted canvas surface.

.my-context-menu {
    background-color: #ffffff;
    border: #cccccc solid 1px;
    box-shadow: 0px 0px 8px #cccccc;
}

.c-tips {
    background-color: #333333;
    color: #ffffff;
}

What Makes This Example Distinct

Compared with nearby examples, this one is most useful when the requirement is contextual inspection UI for both nodes and lines in the same viewer. The comparison record shows that node-menu is the closest menu-oriented neighbor, but that example centers on right-click menus, while this one adds hover tooltips for both target types and keeps separate node and line menu states.

It also differs from workspace-oriented interaction demos such as canvas-event and drag-and-wheel-event. Those examples emphasize canvas drag or wheel behavior, while this one emphasizes object-level hit-testing, overlay anchoring, and a shared dismissal path for four overlay states: node tooltip, line tooltip, node menu, and line menu.

Compared with the broader node styling example, the custom node slot is secondary here. The distinctive combination is a static icon-based graph, circular nodes, labeled lines, a dotted utility canvas, and view-layer overlays driven by isNode(), isLine(), getLinkByLine(), and getViewBoundingClientRect(). That makes it a focused starting point for read-only contextual UI, not for graph editing or canvas command systems.

Where Else This Pattern Applies

This pattern transfers well to monitoring dashboards where hovering a connection should show health details and right-clicking should open quick diagnostics. It also fits asset maps, dependency graphs, and network topology viewers where users need lightweight inspection panels without entering an edit mode.

The same approach can be extended to security relationship views, workflow observability tools, or service maps. In those cases, the node tooltip can show live status or ownership, the line tooltip can show throughput or policy data, and the right-click menus can open domain actions such as drill-down, trace lookup, or exception review while still using the same view-layer positioning strategy.