JavaScript is required

Create or Select Nodes by Drag Selection

This example shows how one selection gesture can either create a circle or rectangle node or select existing nodes, depending on the armed mode. It combines `onCanvasSelectionEnd` with keyboard and palette arming, live selection HUD feedback, a dashed preview overlay, and a shared floating settings panel.

Create or Select Nodes by Drag Selection

What This Example Builds

This example builds a full-height relation-graph workspace seeded with 14 standalone lettered nodes and overlays it with two authoring controls: a draggable helper window and a compact top-center shape palette. The user can inspect the current selection rectangle, switch canvas behavior, and export an image without leaving the canvas.

The main outcome is a dual-purpose drag gesture. When no creation mode is armed, dragging a selection box picks existing nodes inside the region. When circle or rectangle mode is armed, the same gesture becomes a one-shot node-creation action, with a dashed preview overlay during the drag and toast messages when the gesture completes.

How the Data Is Organized

The initial graph data is assembled inline in initializeGraph() as a local RGJsonData object. It contains a flat nodes array with ids a through n and an empty lines array, so the example starts from a neutral canvas rather than a domain-specific graph.

There is no fetch step and no preprocessing before setJsonData(...) other than constructing that object in memory. After the data is loaded, the example centers and fits the viewport with moveToCenter() and zoomToFit().

During interaction, the selection rectangle becomes temporary input data. In creation mode, the code converts the selection view back into canvas coordinates and uses the selection width and height to size the new node. In normal mode, the same selection view is passed to getNodesInSelectionView(...), and each returned node is updated to selected: true.

In real products, this same data shape could stand in for sticky notes on a whiteboard, devices on a topology draft, rooms on a floor-plan editor, or generic assets on an annotation canvas.

How relation-graph Is Used

The demo is wrapped in RGProvider, and MyGraph relies on RGHooks.useGraphInstance() for initial data loading, selection handling, node insertion, and runtime option updates. It also uses RGHooks.useSelection() so the current selection rectangle can drive both the floating readout and the dashed preview layer. Inside the shared settings panel, RGHooks.useGraphStore() reads the active drag and wheel modes.

The graph options are minimal but intentional: showToolBar keeps the built-in toolbar visible, checkedItemBackgroundColor gives checked items a translucent green highlight, defaultLineWidth stays at 1, and defaultJunctionPoint is set to RGJunctionPoint.border. The example does not define a custom layout; it loads simple disconnected data and normalizes the viewport after mount.

RelationGraph is the event hub. onCanvasClick clears checked and selected state, onCanvasSelectionEnd branches between node creation and node picking, and onKeyboardUp arms temporary creation modes for Q and W. RGSlotOnNode replaces the default node body with a centered text container. MyCreatingShapeLayer adds an SVG overlay with pointerEvents: 'none' so preview shapes do not block canvas interaction.

The surrounding editor shell is built from local helper components rather than extra graph slots. DraggableWindow provides the floating instruction panel and can open a shared settings view that changes wheelEventAction, changes dragEventAction, and exports the current graph through prepareForImageGeneration(), domToImageByModernScreenshot(...), and restoreAfterImageGeneration().

Key Interactions

  • Clicking the circle or rectangle button in the top-center palette arms a temporary creation mode and switches the canvas drag action to selection.
  • onKeyboardUp on RelationGraph also arms creation mode for KeyQ and KeyW, giving the same flow a shortcut path when the graph receives keyboard events.
  • Dragging a selection box while creation mode is active shows a dashed preview overlay and creates a new circle or rectangle node when the drag ends.
  • Dragging the same selection box with no creation mode armed resolves the enclosed nodes, shows a count toast, and marks those nodes selected.
  • Clicking empty canvas space clears checked and selected state.
  • Opening the settings panel inside the draggable helper window lets the user change wheel behavior, change canvas drag behavior, and download the current graph image.

Node-click and line-click handlers exist, but in this example they only log the clicked objects and do not change the visible behavior.

Key Code Fragments

The component keeps the graph instance, live selection rectangle, and current creation mode in hook and React state.

const graphInstance = RGHooks.useGraphInstance();
const selectionView = RGHooks.useSelection();
const [selectionForCreateNode, setSelectionForCreateNode] = React.useState<'' | 'circle' | 'rect'>('');

const graphOptions: RGOptions = {
    debug: false,
    showToolBar: true,
    checkedItemBackgroundColor: 'rgba(0, 128, 0, 0.2)',
    defaultLineWidth: 1,
    defaultJunctionPoint: RGJunctionPoint.border
};

This example intentionally starts with a small inline dataset and immediately normalizes the viewport after loading it.

const myJsonData: RGJsonData = {
    nodes: [
        {id: 'a', text: 'A'},
        {id: 'b', text: 'B'},
        {id: 'c', text: 'C'},
        // ...
    ],
    lines: []
};

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

onCanvasSelectionEnd turns the dragged region into a new node when a creation mode is armed.

if (selectionForCreateNode === 'circle') {
    const xyOnCanvas = graphInstance.getCanvasXyByViewXy(userSelectionView);
    graphInstance.addNode({
        id: graphInstance.generateNewNodeId(),
        nodeShape: RGNodeShape.circle,
        width: userSelectionView.width,
        height: userSelectionView.height,
        x: xyOnCanvas.x,
        y: xyOnCanvas.y
    });
}

The non-creation branch uses the same selection rectangle as a picking query and then resets the canvas back to normal dragging.

graphInstance.clearChecked();
graphInstance.clearSelected();
const nodesInSelection = graphInstance.getNodesInSelectionView(userSelectionView);
SimpleGlobalMessage.success(`Select [${nodesInSelection.length}] Nodes`);
nodesInSelection.forEach(node => {
    graphInstance.updateNode(node, {selected: true})
});

graphInstance.updateOptions({
    dragEventAction: 'move'
});
setSelectionForCreateNode('');

A small helper keeps creation mode transient by switching the canvas into selection mode only when the user explicitly arms a shape.

const onKeyboardUp = (e: KeyboardEvent) => {
    if (e.code === 'KeyQ') {
        setSelectionForCreateNodeMode('circle');
    } else if (e.code === 'KeyW') {
        setSelectionForCreateNodeMode('rect');
    }
};

const setSelectionForCreateNodeMode = (nodeShape: 'circle' | 'rect') => {
    graphInstance.updateOptions({ dragEventAction: 'selection' });
    setSelectionForCreateNode(nodeShape);
};

The preview overlay normalizes negative drag directions for rendering and stays transparent to pointer events.

const rectX = width > 0 ? x : x + width;
const rectY = height > 0 ? y : y + height;
const absWidth = Math.abs(width);
const absHeight = Math.abs(height);

<svg
    style={{
        position: 'absolute',
        width: '100%',
        height: '100%',
        pointerEvents: 'none',
    }}
>

The shared floating settings panel reuses relation-graph APIs to expose runtime canvas controls and image export.

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();

What Makes This Example Distinct

Compared with canvas-selection, this example emphasizes transient authoring rather than persistent mode switching. The comparison data shows that canvas-selection-pro adds onKeyboardUp shortcuts and a dedicated top-center palette, then clears the active shape and restores dragEventAction to move after each completed gesture. That makes creation feel like a one-shot command instead of a sticky canvas state.

Compared with selections, the drag box is not only a picking tool. The example converts selection geometry back into canvas coordinates and uses it to size and place new nodes, so the same gesture can either select existing content or author new content depending on the armed mode.

Compared with custom-node-quick-actions and create-line-from-node, the selection rectangle itself is the editing instrument. Those examples use selection to decide which existing nodes expose contextual controls, while this one uses the dragged region directly as geometry for creation.

The rarity data also supports a specific combination that is not common across the example set: RGHooks.useSelection(), a dashed shape preview overlay, keyboard and palette mode arming, selection-end node creation, selection-end node picking, and a shared settings or export panel in the same lightweight workspace.

Where Else This Pattern Applies

This pattern transfers well to lightweight diagram editors where users need to switch quickly between selection and bounded-shape creation without staying in a permanent tool mode. It fits whiteboards, workflow sketchers, topology drafts, and floor-plan tools where the dragged region should define both placement and size.

It is also useful in annotation-heavy products. A review tool, map markup tool, or design feedback surface could reuse the same flow to create callout regions, placeholders, grouped containers, or domain-specific bounded objects from one drag gesture.