JavaScript is required

Around-Node Quick Actions

This example shows how to mount custom quick-action trays around the nodes that are currently in relation-graph's editing state. It combines seeded selection, modifier-based multi-select, marquee retargeting, a dotted editor-style canvas, and a floating settings window, while keeping the action chips themselves as placement-focused placeholders.

Eight-Direction Quick Actions Around Edited Nodes

What This Example Builds

This example builds a lightweight graph editing workspace where custom quick-action trays are attached to the nodes that are currently in relation-graph’s editing state. The page shows a full-height dotted canvas, a floating helper window, and white action groups positioned above, below, left, right, and at all four corners of the edited nodes.

Users can click a node, toggle more nodes into the editing set with modifier keys, complete a marquee selection to retarget the overlay, and click empty canvas space to clear the current editing state. The main point is not business logic for the buttons, but a precise reference for how to place contextual controls around one or many nodes.

How the Data Is Organized

The graph data is declared inline as one RGJsonData object with a rootId, a flat nodes array, and a flat lines array. In this sample, the dataset is small: 11 nodes and 11 lines with simple id, text, from, and to fields.

There is no external fetch or transformation pipeline before setJsonData(...). The only preparation step happens inside initializeGraph(): the example builds the JSON object, loads it into the graph instance, fits the viewport, and then resolves nodes d and a so the editing overlay is visible immediately on first render.

In a real product, the same structure could represent people and reporting lines, workflow steps and transitions, services and dependencies, or any entity graph where selected nodes need contextual operations around them.

How relation-graph Is Used

The entry component wraps the page in RGProvider, and MyGraph reads the provider-scoped instance through RGHooks.useGraphInstance(). That instance handles initial data loading, viewport fitting, editing-node updates, editing-line reset, selection-box resolution, and checked-state clearing.

The example does not define a custom layout object. It relies on relation-graph’s normal layout handling during setJsonData(...), then calls zoomToFit() so the seeded graph fills the viewport.

RelationGraph is configured with showToolBar: false, which removes the built-in toolbar and leaves the page’s interaction shell to custom UI. The component registers onNodeClick, onCanvasSelectionEnd, and onCanvasClick, and all three callbacks feed the same editing-state model through instance APIs such as toggleEditingNode(...), setEditingNodes(...), getNodesInSelectionView(...), and setEditingLine(null).

The overlay itself is the key relation-graph technique. RGSlotOnView mounts RGEditingNodeController, and inside that controller the example renders custom HTML trays with absolute positioning classes. Those trays are not built-in relation-graph presets; they are ordinary <div> blocks that inherit their placement from the controller and use transforms to sit on different sides of the edited node.

The shared DraggableWindow adds a second integration layer. Its settings panel reads RGHooks.useGraphStore() to reflect the current wheel and drag modes, updates those modes with setOptions(...), and exports the canvas through prepareForImageGeneration() and restoreAfterImageGeneration(). That helper window is useful, but it is secondary to the around-node overlay pattern.

Key Interactions

  • Two nodes are seeded into the editing state after initialization, so the quick-action trays are visible immediately.
  • Clicking a node without modifiers replaces the editing-node set with that node and clears any active editing line.
  • Clicking a node with Shift, Ctrl, or Meta toggles that node in the editing selection instead of replacing the whole set.
  • When a selection box completes, the example resolves the enclosed nodes with getNodesInSelectionView(...) and uses that list as the new editing-node set.
  • Clicking empty canvas space clears editing nodes, clears the active editing line, and removes checked state.
  • The floating helper window can be dragged, can open a settings panel, can switch wheel and drag behavior at runtime, and can export the graph as an image.
  • The quick-action trays are placeholders only. They demonstrate placement and targeting, not actual mutation commands.

Key Code Fragments

This fragment shows that the example builds its graph data inline, fits the viewport, and seeds two edited nodes so the overlay appears on load.

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'Border color' },
            // ...
        ],
        lines: [
            { id: 'l1', from: 'a', to: 'b' },
            // ...
        ]
    };
    await graphInstance.setJsonData(myJsonData);
    graphInstance.zoomToFit();

    const nodeD = graphInstance.getNodeById('d');
    const nodeA = graphInstance.getNodeById('a');
    if (nodeD && nodeA) {
        graphInstance.setEditingNodes([nodeD, nodeA]);
    }
};

This fragment shows that plain clicks, modifier clicks, selection-box completion, and canvas reset all update the same editing-node state.

const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
    if ($event.shiftKey || $event.ctrlKey || ($event.metaKey && !$event.altKey)) {
        graphInstance.toggleEditingNode(nodeObject);
    } else {
        graphInstance.setEditingNodes([nodeObject]);
    }
    graphInstance.setEditingLine(null);
};

const onCanvasSelectionEnd = (selectionView: RGSelectionView) => {
    const willSelectedNodes: RGNode[] = graphInstance.getNodesInSelectionView(selectionView) || [];
    graphInstance.setEditingNodes(willSelectedNodes);
};

This fragment shows that the graph disables the built-in toolbar and mounts a custom overlay in the view slot.

const graphOptions: RGOptions = {
    showToolBar: false
};

<RelationGraph
    options={graphOptions}
    onCanvasSelectionEnd={onCanvasSelectionEnd}
    onCanvasClick={onCanvasClick}
    onNodeClick={onNodeClick}
>
    <RGSlotOnView>
        <RGEditingNodeController>

This fragment shows one of the custom trays. The same pattern is repeated for the other sides and corners.

{/* top */}
<div className="pointer-events-auto absolute left-0 top-0 transform translate-y-[-40px]">
    <div className="w-fit flex gap-1 flex-nowrap whitespace-nowrap bg-white border border-gray-300 p-1 rounded shadow">
        <div className="bg-blue-500 text-white text-xs hover:bg-blue-700 px-1 py-0.5 rounded">top1</div>
        <div className="bg-blue-500 text-white text-xs hover:bg-blue-700 px-1 py-0.5 rounded">top2</div>
    </div>
</div>
{/* ... similar blocks follow for bottom, left, right, and all four corners ... */}

This fragment shows that the shared helper window can export the graph and change runtime canvas behavior through relation-graph APIs.

const downloadImage = async () => {
    const canvasDom = await graphInstance.prepareForImageGeneration();
    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
        graphBackgroundColor = '#ffffff';
    }
    const imageBlob = await domToImageByModernScreenshot(canvasDom, {
        backgroundColor: graphBackgroundColor
    });
    await graphInstance.restoreAfterImageGeneration();
};

This fragment shows the stylesheet that turns the canvas into an editor-like dotted surface that scales with the graph viewport.

.relation-graph {
    --rg-canvas-scale: 1;
    --rg-canvas-offset-x: 0px;
    --rg-canvas-offset-y: 0px;
    background-position: var(--rg-canvas-offset-x) var(--rg-canvas-offset-y);
    background-size: calc(var(--rg-canvas-scale) * 15px) calc(var(--rg-canvas-scale) * 15px);
    background-image: radial-gradient(circle, rgb(197, 197, 197) calc(var(--rg-canvas-scale) * 1px), transparent 0);
}

What Makes This Example Distinct

This example stands out among nearby editing demos because it uses RGEditingNodeController mainly as a placement reference rather than as a mutation tool. Compared with create-line-from-node and line-vertex-on-node, the overlay is not a line-authoring workflow. It is a view-layer pattern for docking contextual controls around the edited node or nodes.

Compared with batch-operations-on-nodes, the emphasis is not on wiring commands such as recolor, resize, or delete. The distinguishing lesson here is spatial coverage: the sample shows trays at the top, bottom, left, right, and all four corners, and it keeps that placement pattern compatible with seeded selection, modifier-based multi-select, and marquee retargeting.

Compared with gee-node-resize and gee-node-alignment-guides, the reusable value is not built-in resize handles or drag assistance. It is the combination of a dotted editor canvas, a floating utility window, showToolBar: false, and custom HTML overlays that follow the current editing-node set.

Where Else This Pattern Applies

This pattern transfers well to workflow and process editors where selected steps need nearby actions such as branch, approve, reject, assign, or inspect.

It also fits service topology tools, dependency maps, and knowledge-graph curation screens where users already know the commands they need, but need those commands to appear close to the currently targeted node instead of in a distant global toolbar.

In internal admin tools, the same approach can support organization charts, moderation queues, or asset relationship views where one selection model should drive both single-node and multi-node contextual controls.