JavaScript is required

Batch Edit Selected Nodes

A centered relation graph is turned into a lightweight node maintenance workspace where the current editing-node set drives multi-select, resize handles, batch recolor actions, and bulk node removal. A shared floating overlay also exposes canvas interaction settings and image export without changing the example's main selection-driven editing pattern.

Batch Editing Selected Nodes with a Contextual Toolbar

What This Example Builds

This example builds a lightweight node maintenance workspace on top of a centered relation graph. The user sees a small graph on a dotted full-height canvas, a floating helper window, two nodes preselected on load, resize handles on the active selection, and a compact toolbar docked above the selected nodes.

The main interaction model is selection-driven editing. Users can click one node to make it the active target, use Shift, Ctrl, or Meta-click to build a multi-selection, use a canvas selection box when the drag mode is set to selection, recolor all selected nodes at once, resize them directly on the canvas, remove them in one action, and clear the selection by clicking empty canvas space. The key point is that one editing-node state powers every visible editing affordance.

How the Data Is Organized

The graph data is declared inline inside initializeGraph() as one RGJsonData object with rootId: 'a', 11 nodes, and 11 lines. Each line has its own explicit id, and the dataset is loaded in one step through setJsonData(...) rather than being assembled incrementally.

There is almost no preprocessing before relation-graph consumes the dataset. After setJsonData(...), the code calls zoomToFit(), looks up nodes d and a by id, and seeds them as the initial editing-node set. In a real application, the same structure could represent assets, tasks, people, catalog items, or workflow entities where operators need to select multiple records and apply the same maintenance action to all of them.

How relation-graph Is Used

RGProvider wraps the example so both the graph component and the shared overlay utilities can use relation-graph hooks. Inside MyGraph, RGHooks.useGraphInstance() drives the full workflow: it loads the inline dataset with setJsonData(...), fits the first view with zoomToFit(), resolves startup nodes with getNodeById(...), manages selection through setEditingNodes(...) and toggleEditingNode(...), converts a selection rectangle into node objects with getNodesInSelectionView(...), clears line editing with setEditingLine(null), batch-updates node color with updateNode(...), and removes the current selection with removeNodes(...).

The graph options stay intentionally small. layout.layoutName is set to center, so the demo focuses on editing behavior rather than layout configuration. The RelationGraph component is then wired with onNodeClick, onCanvasSelectionEnd, and onCanvasClick, which makes editing-node state the common target for click selection, selection-box handoff, and empty-canvas clearing.

The visible editing UI is composed through relation-graph subcomponents instead of custom absolute positioning tied to raw DOM coordinates. RGSlotOnView hosts two overlay layers that should stay stable relative to the viewport rather than scale with the graph canvas. One is the shared DraggableWindow, which provides usage notes plus a settings panel for wheelEventAction, dragEventAction, and image export. The other is RGEditingNodeController, which anchors node-specific controls to the current editing-node set; inside it, RGEditingResize adds resize handles and a custom toolbar exposes three batch recolor swatches plus a bulk remove action.

+Styling is minimal but purposeful. The local SCSS overrides the .relation-graph background with a scale-aware dotted pattern based on --rg-canvas-scale and canvas offset variables, which makes the canvas read like an editing surface instead of a plain viewer.

Key Interactions

Clicking a node updates the editing selection. A plain click replaces the selection with that node, while Shift, Ctrl, or Meta without Alt toggles the clicked node into or out of the current editing set. In both cases the code also clears any active line-editing state, which keeps the example strictly focused on node operations.

When the canvas is in selection mode, onCanvasSelectionEnd turns the completed selection rectangle into a node list through getNodesInSelectionView(...) and replaces the editing-node set with those enclosed nodes. Clicking empty canvas space clears both the node selection and any active editing line.

Once nodes are in the editing set, the above-node toolbar becomes the action entry point. The color chips apply the same updateNode(..., { color }) patch to every selected node, the Remove Nodes button deletes the entire current selection, and RGEditingResize exposes resize handles on the same selected nodes. The floating helper window is also interactive: it can be dragged, minimized, switched into a settings panel, and used to export the current graph as an image.

Key Code Fragments

This fragment shows that initialization loads one inline dataset, fits it into view, and immediately seeds two nodes as the first editing selection.

await graphInstance.setJsonData(myJsonData);
graphInstance.zoomToFit();
// Set default editing nodes after initialization
const nodeD = graphInstance.getNodeById('d');
const nodeA = graphInstance.getNodeById('a');
if (nodeD && nodeA) {
    graphInstance.setEditingNodes([nodeD, nodeA]);
}

This fragment shows that plain click and modifier click are mapped to two different selection behaviors on the same editing-node state.

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

This fragment proves that box selection is handed off to relation-graph’s selection API rather than implemented with custom hit testing.

const onCanvasSelectionEnd = (selectionView: RGSelectionView, $event: RGUserEvent) => {
    // 3.x API directly gets nodes within selection range
    const willSelectedNodes = graphInstance.getNodesInSelectionView(selectionView) || [];
    // Batch set editing nodes, no need to manually iterate and modify selected property
    graphInstance.setEditingNodes(willSelectedNodes);
};

This fragment shows that the batch recolor action is imperative and operates on the whole current editing-node set.

const changeNodesColor = (newColor: string) => {
    // Get currently editing nodes from instance
    const editingNodes = graphInstance.getEditingNodes();
    for (const node of editingNodes) {
        // 3.x strictly forbids direct node.color = xxx, must use updateNode API
        graphInstance.updateNode(node, { color: newColor });
    }
};

This fragment shows how the contextual toolbar and resize handles are attached to the selected nodes through relation-graph editing components.

<RGEditingNodeController>
    {/* Node size adjustment handle */}
    <RGEditingResize />
    {/* Custom widget docked above nodes */}
    <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">
            <div
                className="rg-gee-icon"
                style={{ backgroundColor: 'rgba(205,92,92,0.77)' }}
                onClick={() => changeNodesColor('rgba(205,92,92,0.77)')}
            />
        </div>
    </div>
</RGEditingNodeController>

What Makes This Example Distinct

The comparison data shows that this example is not distinctive simply because it uses RGEditingNodeController, RGEditingResize, modifier-based selection, or a floating helper panel. Nearby examples such as custom-node-quick-actions, gee-node-resize, line-vertex-on-node, and change-line-path also reuse parts of that editing scaffold. What stands out here is the specific workflow: relation-graph’s editing-node state is turned into a concrete batch node maintenance tool with multi-select, selection-box handoff, bulk recolor, bulk removal, and direct resize handles all targeting the same selected node set.

Compared with custom-node-quick-actions, this example trades elaborate overlay placement for real mutation commands. Compared with gee-node-resize, it expands resize from a mostly single-node control pattern into a broader multi-node workflow where resize is only one of several actions. Compared with line-vertex-on-node and change-line-path, its reusable lesson is node maintenance rather than connection authoring or line-route editing. That makes it a stronger starting point when the requirement is selection-driven node operations without building a full graph authoring tool.

Where Else This Pattern Applies

This pattern transfers well to internal maintenance tools where operators need to select multiple graph items and apply the same change to all of them, such as asset inventories, organization structures, topology cleanup tools, workflow catalogs, or knowledge-map moderation screens. The same editing-node workflow can drive bulk tagging, status coloring, resizing, locking, archiving, or deletion.

It is also a good fit for lightweight editors where teams want node-level maintenance but not full edge authoring. In those cases, RGEditingNodeController can keep the action surface contextual, while the selection APIs keep batch operations tied to what the user has actively chosen on the canvas.