Canvas Drag and Wheel Modes
A full-height relation-graph workspace demo focused on changing canvas drag and wheel behavior at runtime. It loads a small inline graph, exposes live `wheelEventAction` and `dragEventAction` controls in a draggable floating panel, clears checked state from empty-canvas clicks, and includes an image export flow.
Live Canvas Drag and Wheel Modes in a Floating Graph Workspace
What This Example Builds
This example builds a full-height relation-graph workspace for testing how the canvas reacts to drag and wheel input. The graph itself is intentionally generic: a centered sample network rendered with cloud icons, a floating white utility window, and no domain-specific overlays competing for attention.
Users can switch the live wheel mode between scroll, zoom, and none, switch the canvas drag mode between selection, move, and none, click empty canvas space to clear checked state, drag the helper window by its title bar, and export the current graph view as an image. The main point is not the sample data but the runtime control surface around relation-graph interaction behavior.
How the Data Is Organized
The data is declared inline inside initializeGraph() as one RGJsonData object. It sets rootId: '2', defines 24 hard-coded nodes, and defines 23 hard-coded lines. The structure is loaded directly with setJsonData(...), then centered and fitted in the viewport.
There is no preprocessing pipeline before setJsonData(...). The example does not transform external data, derive display state, or rebuild the dataset when interaction modes change. In a real application, the same shape could stand in for a lightweight topology sample, a QA fixture for gesture rules, or an onboarding sandbox that lets users try workspace controls on stable graph content.
How relation-graph Is Used
index.tsx wraps the page in RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() to load data and clear checked state. The graph options are small but explicit: debug: false, defaultJunctionPoint: RGJunctionPoint.border, and layout.layoutName = 'center'. After mount, the example loads the inline graph with setJsonData(...), then calls moveToCenter() and zoomToFit().
RelationGraph binds onCanvasClick and hosts an RGSlotOnNode renderer that replaces node content with a centered Cloud icon. The example does not use line, canvas, or viewport slots, and it does not implement editing or authoring behavior. The runtime interaction controls live in the shared CanvasSettingsPanel, which uses RGHooks.useGraphStore() to read the current dragEventAction and wheelEventAction, then calls graphInstance.setOptions(...) to update those values on the mounted graph. The same panel also uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() to support image export.
The floating utility shell is a shared React component rather than a relation-graph feature, but it is important to the overall pattern. DraggableWindow keeps its own screen position in React state, starts dragging from the title bar, and updates the window position through document-level mousemove and mouseup listeners. The local stylesheet is minimal and leaves most relation-graph selectors untouched, so the visible customization comes mainly from the shared floating window and the custom node slot.
Key Interactions
- Empty-canvas clicks call
clearChecked(), so the canvas acts as a reset gesture for any checked graph state. - The floating settings panel reflects the graph’s current wheel and drag modes through
useGraphStore(), so the control state stays aligned with the live relation-graph instance. - Clicking a wheel-mode button immediately changes how the canvas handles wheel input without reloading data or remounting the graph.
- Clicking a drag-mode button immediately changes whether canvas dragging selects, moves, or does nothing.
- The utility window itself can be dragged by its title bar, which makes the control surface movable without covering a fixed part of the graph.
- The download action prepares the graph canvas for capture, renders it to an image blob, downloads the file, and then restores the graph state.
Key Code Fragments
This fragment shows that the example uses a center layout with a border junction point and keeps the graph options focused on workspace behavior:
const graphOptions: RGOptions = {
debug: false,
defaultJunctionPoint: RGJunctionPoint.border,
layout: {
layoutName: 'center'
}
};
This fragment shows that the sample graph is assembled inline and loaded once during initialization:
const myJsonData: RGJsonData = {
rootId: '2',
nodes: [
{ id: '1', text: 'Node-1' },
{ id: '2', text: 'Node-2' },
{ id: '3', text: 'Node-3' },
// ...
],
lines: [
{ id: 'l1', from: '7', to: '71' },
// ...
]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This fragment shows that empty-canvas clicks are wired to checked-state clearing rather than to logging or selection geometry handling:
const onCanvasClick = () => {
graphInstance.clearChecked();
};
This fragment shows that node rendering is overridden with a custom slot so the graph stays visually neutral and icon-driven:
<RelationGraph options={graphOptions} onCanvasClick={onCanvasClick}>
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => (
<div className="flex items-center justify-center h-full p-2">
<Cloud size={30} color="#666666" />
</div>
)}
</RGSlotOnNode>
</RelationGraph>
This fragment shows the runtime mode-switching pattern: the panel reads current values from graph store and pushes updates through setOptions(...):
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 matching drag-mode control, which changes canvas behavior without touching the dataset:
<SettingRow
label="Canvas Drag Event:"
options={[
{ label: 'Selection', value: 'selection' },
{ label: 'Move', value: 'move' },
{ label: 'none', value: 'none' },
]}
value={dragMode}
onChange={(newValue: string) => { graphInstance.setOptions({ dragEventAction: newValue }); }}
/>
This fragment shows the export flow that prepares relation-graph for capture, renders the canvas DOM into an image, and restores the graph afterward:
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();
This fragment shows how the floating helper window becomes draggable by capturing title-bar mouse down and then updating position from document-level events:
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
if (!draggable || !windowRef.current) return;
if (e.button !== 0) return;
const currentLeft = windowRef.current.offsetLeft;
const currentTop = windowRef.current.offsetTop;
setPosition({ x: currentLeft, y: currentTop });
dragInfoRef.current = {
isDragging: true,
relX: e.clientX - currentLeft,
relY: e.clientY - currentTop
};
};
What Makes This Example Distinct
The comparison data places this example near canvas-event, selections, canvas-selection, gee-thumbnail-diagram, and switch-layout, but its emphasis is narrower than all of them. Against canvas-event, it does not teach drag lifecycle callbacks or visual drag instrumentation. Against selections and canvas-selection, it does not consume selection geometry, mark selected nodes, or create new graph content. Against gee-thumbnail-diagram and switch-layout, it does not focus on minimap navigation or layout changes.
What makes it stand out is the combination of a draggable overlay panel, live reflection of dragEventAction and wheelEventAction from graph store, immediate setOptions(...) updates on the mounted graph, click-to-clear canvas behavior, image export, and a deliberately neutral center-layout sample graph. Within the floating helper-window demos, this one keeps the graph visuals unusually simple so the input-mode controls remain the primary lesson.
Where Else This Pattern Applies
This pattern transfers well to graph workspaces where teams need to expose canvas behavior as a user-facing setting rather than a hard-coded implementation detail. Examples include topology inspection tools, investigation dashboards, training sandboxes for pan and zoom rules, and QA pages that verify how gesture policies feel on a live graph.
It also fits products that already have a stable graph dataset but need an auxiliary control surface around it. The same approach can be extended into accessibility mode toggles, operator-specific interaction presets, screenshot tooling, or support consoles where users need to switch between browse, move, and selection behaviors without rebuilding the graph.