JavaScript is required

Canvas Selection and Node Creation

This example shows how one relation-graph canvas selection gesture can either select enclosed nodes or create a new circle or rectangle node. It also exposes the live selection bounds in a floating helper window and lets users switch canvas drag behavior at runtime.

Canvas Selection for Picking and Shape Creation

What This Example Builds

This example builds a full-screen relation-graph workspace seeded with 14 disconnected lettered nodes. A draggable helper window stays above the canvas, explains how to start selection, shows the live selection rectangle metrics, and exposes toggles that change what the drag gesture does.

The core behavior is that one box-selection gesture has two possible outcomes. In normal mode it selects every node inside the dragged region. In creation mode it turns the same region into a new circle or rectangle node sized from the dragged box. That makes the example a compact reference for selection-driven tooling rather than a domain-specific chart.

How the Data Is Organized

The initial dataset is an inline RGJsonData object created inside initializeGraph. It contains a flat nodes array with ids a through n, and an empty lines array, so the starting point is intentionally simple and unconnected.

There is no external fetch and no preprocessing before setJsonData(...) beyond building that local object. After the data is loaded, the example centers and fits the graph in the viewport. During interaction, the selection rectangle becomes transient runtime input: it is either passed to getNodesInSelectionView(...) for multi-select, or converted to canvas coordinates and used to size a new node with addNode(...).

In a production app, the same data shape could represent cards on a planning board, devices on a topology canvas, seats on a floor plan, or assets on an annotation workspace where region selection and region-based creation matter more than line relationships.

How relation-graph Is Used

The demo is wrapped in RGProvider, then MyGraph uses RGHooks.useGraphInstance() to load data, center the view, clear state, resolve nodes inside a selection, and insert new nodes. It also uses RGHooks.useSelection() so the current selection rectangle can drive both the floating status card and the temporary shape preview.

The graph options are small but deliberate: showToolBar enables the built-in toolbar, checkedItemBackgroundColor gives selected items a translucent green background, defaultLineWidth stays minimal, and dragEventAction is bound to React state so the canvas can switch between selection and move at runtime. The local code does not declare a custom layout; it loads the sample data and normalizes the viewport with moveToCenter() and zoomToFit().

RelationGraph receives the key event handlers, especially onCanvasClick and onCanvasSelectionEnd. RGSlotOnNode overrides node content with a padded text renderer. The floating DraggableWindow component adds the instructional panel and opens a shared settings panel that reads current options through RGHooks.useGraphStore(), updates wheel and drag behavior with setOptions(...), and exports the graph image through prepareForImageGeneration() plus a DOM-to-image helper. The SCSS file only defines placeholder selectors, so most visible customization comes from JSX, Tailwind-style utility classes, and the SVG overlay in MyCreatingShapeLayer.

Key Interactions

  • Hold Shift to start canvas selection, or explicitly switch the canvas drag action into selection mode from the helper panel.
  • Drag a selection box in normal mode to find nodes inside the region and mark them selected.
  • Enable circle or rectangle creation, then drag the same selection box to insert a new node whose size comes from the dragged bounds.
  • Click empty canvas space to clear checked and selected graph state.
  • Open the floating settings panel to change wheel behavior, change drag behavior, or download the current graph as an image.

Node-click and line-click handlers are present, but in this example they only log objects to the console and do not change the graph state.

Key Code Fragments

The graph instance, live selection state, and runtime drag mode are wired together at the top of MyGraph.

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

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

The initial graph data is local, lightweight, and loaded directly into the relation-graph instance before the view is centered and fitted.

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

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

onCanvasSelectionEnd turns the selection rectangle 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 relation-graph’s built-in selection query to resolve existing nodes and mark them selected.

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

The temporary overlay normalizes negative drag directions so the preview still renders correctly when the user drags leftward or upward.

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

{shape === 'rect' ? (
    <rect
        x={rectX}
        y={rectY}
        width={absWidth}
        height={absHeight}
        strokeDasharray="4 2"
    />
) : (
    <circle
        cx={rectX + absWidth / 2}
        cy={rectY + absHeight / 2}
        r={Math.min(absWidth, absHeight) / 2}
    />
)}

What Makes This Example Distinct

Compared with selections, this example does not treat marquee selection as the final outcome. The dragged region can still select existing nodes, but it can also become new graph content. That shifts the example from pure picking into hybrid selection-and-authoring.

Compared with canvas-selection-pro, the emphasis here is explicit teaching rather than shortcut-first speed. The helper window keeps move-or-selection mode and circle-or-rectangle creation visible at all times, so the workflow is easier to study and adapt.

The comparison data also shows a distinct feature combination: manual canvas mode switching, click-to-clear workspace behavior, live selection instrumentation, a dashed creation preview, and selection-driven node insertion in one compact screen. Other nearby examples cover parts of that pattern, but this one is a particularly focused reference when you want relation-graph’s native selection geometry to be the central interaction primitive.

Where Else This Pattern Applies

This pattern transfers well to lightweight whiteboard editors where a dragged region can either select existing objects or create a new bounded shape. It also fits planning boards, floor-plan tools, topology sketchers, and annotation canvases where the same gesture should support both region-based picking and region-based insertion.

Another extension path is to treat the selection rectangle as a generic input primitive. Instead of creating only circle and rectangle nodes, the same handoff could create grouped cards, bounded containers, placeholder devices, or domain-specific regions once the user chooses a creation mode.