JavaScript is required

Node, Line, and Canvas Context Menu

This example shows how one relation-graph `onContextmenu` handler can open a floating menu for nodes, lines, and empty canvas. It combines `RGSlotOnView` for the overlay, `RGSlotOnNode` for icon-only circular nodes, and a lazily mounted global toast layer to confirm placeholder actions without mutating the graph.

Building a Shared Context Menu for Nodes, Lines, and Canvas

What This Example Builds

This example builds a full-height relation graph with one floating context menu that appears at the pointer location when the user right-clicks a node, a line, or empty canvas. The visible graph stays small and read-only: three green circular icon nodes, two labeled links, and a white rounded menu card that changes its header and actions according to the clicked target.

Users can open a target-specific menu, choose an action row, and dismiss the overlay by clicking elsewhere. Every action closes the menu and shows a toast in the top-right corner, so the demo is about contextual UI scaffolding rather than graph mutation.

The key idea is the shared menu-state pattern: one graph event handler, one overlay surface, and branching content for three target types.

How the Data Is Organized

The data is assembled inline inside initializeGraph() as one RGJsonData object with rootId: '2', three node records, and two line records. Each node includes id, text, and data.myicon. The data.myicon value is not decorative metadata only; it drives the custom node slot so each node can render a different Lucide icon.

There is no preprocessing step before setJsonData(). The component constructs the final relation-graph payload directly in code, then loads it into the graph instance as-is. In real applications, the same shape could represent services, devices, ownership links, workflow steps, or dependency edges, while data.myicon could be replaced by type, status, or category fields that choose a custom renderer.

How relation-graph Is Used

index.tsx wraps the demo in RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() to access the active graph instance. A mount-time useEffect() calls initializeGraph(), which loads the inline dataset, recenters the graph, and fits it to the viewport.

The graph configuration is intentionally light. defaultNodeColor sets the green node fill, defaultNodeShape uses circular nodes, and defaultJunctionPoint attaches lines at node borders. The reviewed source does not define an explicit layout, so the graph relies on relation-graph’s default arrangement for this payload.

Two relation-graph slots carry the example. RGSlotOnNode replaces the usual node body with a circular icon renderer, and RGSlotOnView renders the floating context menu inside the graph view. The menu position is taken directly from eventPositionOnView, so the overlay follows the right-click location without additional wrapper-relative coordinate math.

The main graph event is onContextmenu. It provides the clicked target type, the target object when one exists, and coordinates on both the canvas and the view. This example stores the RGEventTargetType and optional target object in React state, then branches menu labels and action rows from that state. Even menu items such as Edit Node, Change Color, Add New Node, and Reset Zoom do not call graph mutation APIs here; they only trigger feedback.

Toast feedback is implemented outside relation-graph through the local SimpleGlobalMessage helper. That helper lazily mounts a global message host into document.body, and the companion message styles pin the toast stack to the top-right of the screen.

Key Interactions

  • Right-clicking a node opens Node Menu with View Details, Edit Node, and Delete.
  • Right-clicking a line opens Line Menu with View Details, Change Color, and Delete.
  • Right-clicking empty canvas opens Canvas Menu with View Details, Add New Node, and Reset Zoom.
  • Clicking any menu row shows a success toast and closes the menu.
  • Clicking outside the menu dismisses the overlay, while clicking inside the card stops propagation so users can select a menu item first.

Key Code Fragments

This options block shows that the example relies on built-in graph options for most of its visual setup.

const graphOptions: RGOptions = {
    defaultNodeColor: 'rgba(66,187,66,1)',
    defaultNodeShape: RGNodeShape.circle,
    defaultJunctionPoint: RGJunctionPoint.border
};

This inline dataset proves that the graph payload is assembled directly in component code and carries per-node icon metadata.

const myJsonData: RGJsonData = {
    rootId: '2',
    nodes: [
        { id: '1', text: 'Node-1', data: { myicon: 'star' } },
        { id: '2', text: 'Node-2', data: { myicon: 'settings' } },
        { id: '5', text: 'Node-5', data: { myicon: 'gift' } }
    ],
    lines: [
        { id: 'l1', from: '1', to: '2', text: 'Link 1' },
        { id: 'l2', from: '1', to: '5', text: 'Link 2' }
    ]
};

This initialization sequence shows the full graph-instance workflow used by the demo.

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

This handler shows how one relation-graph event becomes shared menu state and a view-relative overlay position.

const onContextmenu = (
    e: RGUserEvent,
    objectType: RGEventTargetType,
    object: RGNode | RGLine | undefined,
    eventPositionOnCanvas: RGCoordinate,
    eventPositionOnView: RGCoordinate
) => {
    setMenuTarget({ type: objectType, data: object });
    setMenuPosition({ x: eventPositionOnView.x, y: eventPositionOnView.y });
    setIsShowMenu(true);
};

This node slot shows that icon rendering comes from node data rather than static markup.

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="c-my-rg-node">
            <NodeIcon name={node.data?.myicon} />
        </div>
    )}
</RGSlotOnNode>

This view slot fragment shows one floating card adapting its header and actions from the current graph target.

<RGSlotOnView>
    {isShowMenu && (
        <div
            className="pointer-events-auto context-menu-panel"
            style={{ left: menuPosition.x, top: menuPosition.y }}
            onClick={(e) => e.stopPropagation()}
        >
            <div className="menu-header">
                {menuTarget?.type === 'node' && 'Node Menu'}
                {menuTarget?.type === 'line' && 'Line Menu'}
                {menuTarget?.type === 'canvas' && 'Canvas Menu'}
            </div>
            {/* ... menu items omitted ... */}
        </div>
    )}
</RGSlotOnView>

This message API fragment proves that menu actions enqueue toast feedback through a lazily mounted global host instead of mutating the graph.

showMessage: (messageObject: { duration?: number; message: string; type: string; }) => {
    messageManager.mount();
    setTimeout(() => {
        messageManager.myMessageUIRef?.addMessage(
            messageObject.type,
            messageObject.message,
            messageObject.duration
        );
    }, 0);
},

What Makes This Example Distinct

The comparison data places this example closest to node-menu-2, node-line-tips-contentmenu, node-tips, and built-in-slots, but its teaching focus is more specific than any of those neighbors. Against node-menu-2, the same icon-node and floating-card visual style is reused, but the control flow moves from node-slot DOM handlers to relation-graph’s graph-level onContextmenu, which lets one event path cover nodes, lines, and empty canvas. Against node-line-tips-contentmenu, the scope narrows from a broader tooltip-and-inspection workspace to a compact scaffold for contextual actions.

The unusual feature combination is not simply “a menu inside RGSlotOnView”. The comparison and rarity records emphasize a shared menu state keyed by RGEventTargetType, direct positioning from eventPositionOnView, and read-only action confirmation through a global toast layer. That makes the example especially useful when a team needs a reusable branching context-menu framework before wiring real business commands.

It is important not to overstate the uniqueness. Other examples also use RGSlotOnView and custom right-click overlays. What stands out here is the compact graph-level pattern that covers node, line, and canvas in one menu component while keeping the graph itself unchanged.

Where Else This Pattern Applies

This pattern transfers well to topology viewers, infrastructure maps, architecture diagrams, process inspectors, and relationship screens where the same contextual UI should work on nodes, links, and blank space. The placeholder actions in this demo can be replaced by real commands such as opening detail drawers, creating neighbor nodes, editing connection properties, or starting a canvas-level creation flow.

It also fits monitoring and approval tools that want lightweight confirmation before deeper implementation. The reusable part is the event contract and overlay structure: let relation-graph identify the target, place one view-layer menu near the pointer, and keep the actual action handlers application-specific.