JavaScript is required

Partial Node Drag Handles

This example loads a fixed-layout graph, disables whole-node dragging, and restores movement only from slotted subregions marked with `rg-node-drag-handler`. It mixes header-strip and move-button handle variants through node metadata, while a shared floating helper panel exposes wheel mode, canvas drag mode, and image export.

Restricting Fixed-Layout Node Movement to Explicit Drag Handles

What This Example Builds

This example builds a full-height relation-graph workspace where node movement is intentionally restricted. The user sees a small fixed-position graph on a dotted canvas, amber handle regions inside selected nodes, a right-side toolbar, and a floating helper window above the graph.

Users can reposition only certain nodes, and only from the specific handle region rendered inside each node. Some nodes expose a full header strip as the handle, others expose a small move badge in the top-left corner, and nodes without either marker stay non-draggable. That selective movement pattern is the main point of the example.

The page also includes shared utility behavior around the graph. The helper window can be dragged or minimized, its settings overlay can switch canvas wheel and drag modes, and the current graph can be exported as an image.

How the Data Is Organized

The graph data is declared inline inside initializeGraph() as one RGJsonData object. It contains a rootId, a nodes array, and a lines array. Every node is authored with explicit x and y coordinates because the example uses a fixed layout rather than an automatic layout engine.

The important preprocessing idea is lightweight metadata, not structural transformation. The code stores hasHeader and hasMoveButton flags inside node.data, and the node slot reads those flags later to decide which drag affordance to render. One node also overrides width and height, so the sample graph mixes node sizes as well as shapes.

There is no separate normalization pass before setJsonData(...). The code builds the JSON object locally, sends it directly into the graph instance, and then fits the viewport. In a real product, the same structure could represent workflow cards, access-control entities, topology items, or architecture blocks where some node types should be movable only from approved regions.

How relation-graph Is Used

index.tsx wraps the demo in RGProvider, which allows both MyGraph and the shared floating helper components to access the same active graph instance. Inside MyGraph, RGHooks.useGraphInstance() is used to load the inline dataset with setJsonData(...), then zoomToFit() is called so the hand-placed scene is visible immediately after mount.

The graph options define the interaction model. layout.layoutName is set to fixed, disableDragNode is set to true, and the default node and line colors are set to the same amber-and-white theme that appears in the handle regions. defaultJunctionPoint is set to RGJunctionPoint.border, and the built-in toolbar is moved to the right side of the canvas.

The main extension point is RGSlotOnNode. Instead of using default node text rendering, the slot creates custom HTML for every node and selectively applies the rg-node-drag-handler class to the header strip or move icon region. That is the mechanism that restores dragging only on approved subregions after whole-node dragging has been disabled globally.

No view-level or canvas-level slot is used in this example. The canvas styling comes from my-relation-graph.scss, which uses --rg-canvas-scale and offset variables to draw a dotted workbench background behind the graph.

The floating DraggableWindow and CanvasSettingsPanel are shared helper components rather than logic unique to this example. They use RGHooks.useGraphStore() to reflect current interaction settings, graphInstance.setOptions(...) to change wheel and canvas drag modes at runtime, and prepareForImageGeneration() plus restoreAfterImageGeneration() to support image export. That makes the example a hybrid tool: users can reposition existing nodes during the session, but there is no CRUD editing or coordinate persistence.

The wrapper onMouseMove handler also uses graphInstance.isLine(...) and getLinkByLine(...) to detect hovered links, but it only writes to the console. It is implementation detail, not the main user-facing lesson.

Key Interactions

The most important interaction is handle-scoped node dragging. Users cannot drag a node from its whole body. They must start from the amber header strip or the amber move badge, depending on that node’s metadata.

The floating helper window can be dragged by its title bar and minimized when the graph needs more space. Its settings button opens an overlay panel on top of the same window.

Inside that settings panel, users can switch the mouse wheel behavior between scroll, zoom, and none. They can also switch canvas dragging between selection, move, and none without rebuilding the graph.

The same panel includes a download action that captures the current graph canvas as an image. This is shared scaffolding, but it still affects the actual runtime experience of the demo.

Key Code Fragments

This options block proves that the example uses a fixed layout and disables whole-node dragging before adding back local drag handles.

const graphOptions: RGOptions = {
    debug: true,
    defaultJunctionPoint: RGJunctionPoint.border,
    defaultNodeColor: '#ffffff',
    defaultNodeBorderWidth: 1,
    defaultNodeBorderColor: '#f39930',
    defaultLineColor: '#f39930',
    defaultLineWidth: 2,
    toolBarPositionH: 'right',
    disableDragNode: true,
    layout: {
        layoutName: 'fixed'
    }
};

This data fragment shows that per-node metadata controls which drag affordance each node receives.

const myJsonData: RGJsonData = {
    rootId: 'SYS_ROLE',
    nodes: [
        { id: 'SYS_USER', text: 'SYS_USER', nodeShape: RGNodeShape.rect, x: -32, y: -427, data: { hasHeader: true } },
        { id: 'SYS_DEPT', text: 'SYS_DEPT', nodeShape: RGNodeShape.circle, x: -244, y: -283, data: { hasMoveButton: true } },
        { id: 'SYS_ROLE', text: 'SYS_ROLE', nodeShape: RGNodeShape.rect, x: 0, y: 0, width: 300, height: 200, data: { hasHeader: true } },
        { id: 'SYS_USER_ROLE', text: 'SYS_USER_ROLE', nodeShape: RGNodeShape.rect, x: 405, y: -174, data: { hasMoveButton: true } },
        { id: 'SYS_RESOURCE', text: 'SYS_RESOURCE', nodeShape: RGNodeShape.rect, x: -246, y: -80, data: { hasHeader: true } },
        { id: 'SYS_ROLE_RESOURCE', text: 'SYS_ROLE_RESOURCE', nodeShape: RGNodeShape.rect, x: 338, y: -100 }
    ],
    // ...
};

This initialization function shows that the graph is loaded directly from inline JSON and then fitted into the viewport.

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        // ...
    };
    await graphInstance.setJsonData(myJsonData);
    graphInstance.zoomToFit();
};

This slot fragment is the core implementation: the rg-node-drag-handler class is attached only to the allowed drag region inside each custom node.

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="h-full">
            {node.data.hasHeader && <div className="rg-node-drag-handler px-3" style={{ backgroundColor: '#f39930', color: 'white'}}>
                {node.text}
            </div>}
            {node.data.hasMoveButton && <div className="rg-node-drag-handler cursor-move absolute top-0 left-0 h-5 w-5 rounded bg-gray-200 flex place-items-center justify-center" style={{ backgroundColor: '#f39930', color: 'white'}}>
                <MoveIcon />
            </div>}
        </div>
    )}
</RGSlotOnNode>

This shared settings fragment shows that wheel behavior and canvas drag behavior are changed on the live graph instance at runtime.

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

This SCSS fragment shows that the editor-like background is not an image asset; it is generated from relation-graph canvas variables.

.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

The comparison data shows that this example is not distinctive just because it uses RGSlotOnNode, a fixed layout, or the shared floating helper window. Nearby examples such as area-set, css-theme, canvas-bg2, and node-style3 also cover parts of that territory.

What stands out is the interaction contract. This demo turns off default node dragging globally and then restores movement only on slot-rendered subregions marked with rg-node-drag-handler. That makes partial node dragging the main lesson, not a secondary embellishment.

It also pushes the idea further than a single handle style. The drag affordance is selected per node through node.data, so the same graph can mix a full header handle, a compact move-button handle, and nodes that do not expose a drag handle at all.

Compared with area-set, the important difference is not canvas partitioning but movement scope inside the node body. Compared with css-theme and canvas-bg2, the reusable value is not theme switching or wrapper-level styling. Compared with node-style3, the slot is used to control behavior more than appearance. That combination makes this example a better starting point when a graph needs selective repositioning without turning the whole node surface into a drag target.

Where Else This Pattern Applies

This pattern transfers well to workflow or operations boards where cards contain buttons, labels, or embedded controls that should remain clickable while a small approved region still allows repositioning. The same idea works for topology maps, architecture diagrams, and access-control views where some nodes should be adjustable but their main content should not accidentally start a drag.

It is also useful for review tools that need temporary manual arrangement without full editing features. A team can keep fixed layout data as the baseline, expose drag handles only on chosen node types, and avoid the usability problems that come from making every pixel inside a node draggable.