JavaScript is required

Node Drag Collision and Drop Rules

This example shows a compact drag-rule workspace where node overlap is interpreted as an allowed drop, a rejected drop, or a protected collision. It combines fixed-layout graph data, drag lifecycle callbacks, inline node policy toggles, and release-time line creation to demonstrate collision-aware authoring without a full editor shell.

Collision-Aware Node Dragging and Drop Rules

What This Example Builds

This example builds a compact relationship workspace where dragging one node across another becomes a rule-driven authoring gesture. The screen starts as a fixed-position graph with mixed rectangular and circular nodes on a dotted canvas, plus a floating help window and a fixed legend that explains the drag states.

Users can drag nodes, watch target nodes switch between green, red, and purple feedback, change each target’s drop policy from inside the node card, and release on an allowed target to create a new line. The main point is not freeform layout editing, but showing how relation-graph can turn overlap detection into acceptance, rejection, or constrained movement during drag.

How the Data Is Organized

The graph is declared inline as one RGJsonData object with a rootId, a flat nodes array, and a flat lines array. Each node carries fixed x and y coordinates because the example uses the fixed layout, and some nodes also carry runtime policy flags inside node.data, such as allowDrop and disallowDroppingNodesInside.

There is no preprocessing before setJsonData(...). The code loads the dataset directly, then centers and fits the viewport. During interaction, the example extends node.data with temporary fields such as startX, startY, and saved colors, so the drag rules live on the nodes themselves instead of in a separate editor store.

In a real application, the same structure could represent roles and permissions, workflow steps, storage zones, or assets that may only be attached to approved parents. The local overlap utility keeps the hit test simple by using rectangle intersection even when the rendered graph mixes node shapes.

How relation-graph Is Used

index.tsx wraps the example in RGProvider, and MyGraph reads the active instance with RGHooks.useGraphInstance(). That instance handles initialization through setJsonData(...), moveToCenter(), and zoomToFit(), then drives the drag workflow with getNodes(), updateNodeData(), updateNode(), updateNodePosition(), and addLines().

RelationGraph is configured with layout.layoutName = "fixed", defaultJunctionPoint = RGJunctionPoint.border, white node fills, amber borders and lines, and a right-side toolbar position. The important runtime behavior comes from the three node-drag callbacks rather than from a custom layout engine: onNodeDragStart records the original position, onNodeDragging inspects overlap and may return a constrained coordinate, and onNodeDragEnd decides whether to create a relationship.

The example also uses two slots. RGSlotOnNode replaces the default node body with a card that contains two live SimpleUIBoolean controls, and RGSlotOnView places a fixed legend inside the canvas so the three drag outcomes are visible in the workspace itself.

A shared DraggableWindow adds secondary tooling. Its settings overlay reads RGHooks.useGraphStore(), updates wheelEventAction and dragEventAction with setOptions(...), and uses prepareForImageGeneration() plus restoreAfterImageGeneration() to export an image. The accompanying SCSS overrides the canvas background with a scale-aware dotted pattern and styles the legend panel.

Key Interactions

  • Dragging a node continuously checks overlap against every other node in the graph.
  • Overlapping a target with allowDrop turns that target green, while overlapping a target without allowDrop turns it red.
  • Overlapping a target with disallowDroppingNodesInside turns it purple and returns a limited position so the dragged node stays outside the protected node.
  • Drag start records the original coordinates in node.data, and drag end restores those coordinates before creating a new relationship line.
  • The Allow Drop and Disallow Dropping Nodes Inside checkboxes update the current node’s policy immediately, so the next drag uses the new rules without rebuilding the dataset.
  • The floating helper window can be dragged, minimized, switched into a settings panel, and used to export the current graph image.

Key Code Fragments

This fragment shows that the example seeds a fixed-position graph inline and immediately centers and fits it after loading.

const myJsonData: RGJsonData = {
    rootId: 'SYS_ROLE',
    nodes: [
        { id: 'SYS_USER', text: 'SYS_USER', nodeShape: RGNodeShape.rect, x: -32, y: -427, data: { allowDrop: true } },
        { id: 'SYS_DEPT', text: 'SYS_DEPT', nodeShape: RGNodeShape.circle, x: -244, y: -283, data: { allowDrop: true } },
        { id: 'SYS_ROLE', text: 'SYS_ROLE', nodeShape: RGNodeShape.rect, x: 0, y: 0, width: 300, height: 200, data: { disallowDroppingNodesInside: true } }
    ]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

This fragment shows the drag-time rule engine that recolors targets and computes a constrained position for protected overlaps.

const overlap = MyRelationGraphUtils.shapesOverlap(nodeBox, n, shapeA, shapeB);
if (overlap) {
    let newColor = n.data!.allowDrop ? 'rgba(212,252,189,0.82)' : 'rgba(252,189,189,0.82)';
    if (n.data.disallowDroppingNodesInside) {
        rejectOverlaped = true;
        limitedPosition = MyRelationGraphUtils.getNoOverlapLimitedPosition(node, newX, newY, n);
        newColor = 'rgba(214,165,246,0.82)';
    }
    graphInstance.updateNode(n, { color: newColor });
}

This fragment shows that an accepted drop restores the node position and then adds a new relation instead of nesting the node inside the target.

const updateNodeAsChildren = (node: RGNode, parentNode: RGNode) => {
    graphInstance.updateNodePosition(node, node.data!.startX, node.data!.startY);
    graphInstance.addLines([{
        id: `new_l_${Date.now()}`,
        from: parentNode.id,
        to: node.id,
        text: 'New Relationship',
        color: 'rgba(214,165,246,0.82)',
        lineWidth: 3
    }]);
};

This fragment shows how RGSlotOnNode turns every node into a live policy editor.

<SimpleUIBoolean
    label="Disallow Dropping Nodes Inside"
    currentValue={node.data.disallowDroppingNodesInside}
    onChange={(newValue) => {graphInstance.updateNodeData(node, {disallowDroppingNodesInside: newValue})}}
/>
<SimpleUIBoolean
    label="Allow Drop"
    currentValue={node.data.allowDrop}
    onChange={(newValue) => {graphInstance.updateNodeData(node, {allowDrop: newValue})}}
/>

This fragment shows the shared floating panel reading the live graph store and pushing runtime canvas-mode changes back into the graph instance.

const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;

<SettingRow
    label="Wheel Event:"
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

This fragment shows the stylesheet that turns the graph into a dotted editing surface.

.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 the nearby editor demos because the node-drag lifecycle itself is the teaching vehicle. Compared with create-line-from-node, relationship creation is overlap-triggered instead of selection-triggered: the user drags one node onto a target that currently permits the drop, and only then does the example add a line.

Compared with custom-node-quick-actions and batch-operations-on-nodes, the custom node UI is not an action launcher for selection or batch tools. Each node carries its own rule switches, so the graph content directly controls whether a collision should be accepted, rejected, or treated as protected overlap.

Compared with gee-node-resize and gee-node-alignment-guides, the extra drag feedback is semantic rather than geometric. The rare combination here is a fixed-layout scene, inline policy cards, a view-level legend, manual overlap utilities, and release-time line creation after the dragged node is snapped back to its original position. That makes it a strong starting point for rule-based drop behavior, not for a general editor shell.

Where Else This Pattern Applies

This pattern transfers well to workflow or approval designers where dragging one step onto another should propose a valid transition only when the target allows it.

It also fits access-control mapping, data-lineage curation, and dependency-authoring tools where objects may connect to some classes of node but must stay out of protected containers or zones.

A related use is operational planning interfaces such as storage layouts or equipment maps, where overlap can act as a quick assignment gesture while protected regions reject entry and only record the relationship.