Canvas Center Offset
This example shows how to recenter a relation-graph canvas from external React controls instead of graph data or layout changes. It mounts a labeled SVG coordinate grid in RGSlotOnCanvas, drives setCanvasCenter(...) from X and Y sliders, and reuses the shared floating settings panel for runtime canvas options and image export.
Recenter the Canvas with a Slot-Mounted Coordinate Grid
What This Example Builds
This example builds a canvas-positioning playground rather than a graph-data showcase. The page renders a full-height RelationGraph canvas, mounts a large labeled SVG coordinate grid inside the canvas layer, and places a floating utility window above it.
Users can drag that window, minimize it, open a shared settings panel, export an image, and most importantly move two sliders that shift the canvas center on the X and Y axes. The main point is that the example isolates canvas recentering itself, so the effect is easy to study without node data, line routing, or automatic layout changes getting in the way.
How the Data Is Organized
There is no graph dataset in this demo. MyGraph.tsx imports RGJsonData, but it never calls setJsonData, never provides nodes or lines to RelationGraph, and leaves initializeGraph() empty. The live state is just two numbers, canvasOffsetX and canvasOffsetY, which are used as the target canvas center.
The visual reference comes from CoordinateGrid.tsx. That component defines a fixed graph-space range from -1000 to 1000 on both axes, uses a 50 unit step, and memoizes text-label positions for each grid segment before rendering the SVG. In a production tool, the same structure could represent workshop coordinates, warehouse aisles, floor-plan guides, CAD rulers, seat maps, or any other measured overlay that needs to stay aligned with graph space.
How relation-graph Is Used
index.tsx wraps the demo in RGProvider, so relation-graph hooks can resolve the active graph context. Inside MyGraph.tsx, RelationGraph runs with layoutName: 'fixed', a horizontal toolbar positioned at the bottom-right, and predefined node and line defaults. Because no graph data is loaded, those node and line defaults are mostly latent configuration rather than the visible lesson of the example.
The key runtime integration is RGHooks.useGraphInstance(). A React effect watches canvasOffsetX and canvasOffsetY, then calls graphInstance.setCanvasCenter(...) whenever either value changes. That turns a pair of ordinary React controls into an external camera-control surface for relation-graph.
The other important integration point is RGSlotOnCanvas. Instead of drawing the grid outside the graph container with CSS, the example mounts the SVG directly into the canvas layer and offsets its wrapper to left: -1000px and top: -1000px. That placement lines the grid’s viewBox up with graph-space coordinates, so moving the canvas center produces a measurable visual result.
The floating helper window comes from a shared DraggableWindow component. In this example it hosts the X/Y sliders, and its built-in settings overlay uses RGHooks.useGraphStore() plus graphInstance.setOptions(...) to switch wheel and drag behavior at runtime. The same helper also uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() to export the current canvas through modern-screenshot.
Styling is intentionally light. my-relation-graph.scss contains selector scaffolding for toolbar, nodes, and lines, but it does not add concrete overrides, so the graph surface stays close to the library defaults while the coordinate grid and floating panel carry most of the visual meaning.
Key Interactions
The primary interaction is slider-driven recentering. Each slider updates local React state in steps of 10, and the effect hook immediately pushes the new values into setCanvasCenter(...). The sliders do not move nodes or alter a layout; they move the canvas center itself.
The floating window is also interactive. Users can drag it by the title bar, minimize it, and open the settings overlay. That overlay is not unique to this example, but here it matters because it lets users change wheel behavior between scroll, zoom, and none, and switch drag behavior between selection, move, and none while testing the empty workspace.
The last meaningful interaction is image export. The helper panel can prepare the current graph canvas for capture, render it to a blob, and download it, which makes the demo useful as a small debugging or documentation aid when checking coordinate alignment.
Key Code Fragments
This effect is the core proof that the demo controls canvas position through the graph instance rather than through layout data.
const [canvasOffsetX, setCanvasOffsetX] = useState(0);
const [canvasOffsetY, setCanvasOffsetY] = useState(0);
const graphInstance = RGHooks.useGraphInstance();
useEffect(() => {
graphInstance.setCanvasCenter(canvasOffsetX, canvasOffsetY);
}, [canvasOffsetX, canvasOffsetY]);
This JSX shows that the example-specific UI is only two sliders, while the graph surface itself receives a canvas-layer overlay.
<DraggableWindow>
<div className="py-1 text-sm">canvas center:</div>
<div className="c-option-name">canvas X: {canvasOffsetX}</div>
<SimpleUISlider min={-1000} max={1000} step={10} currentValue={canvasOffsetX} onChange={(newValue: number) => { setCanvasOffsetX(newValue); }} />
<div className="c-option-name">canvas Y: {canvasOffsetY}</div>
<SimpleUISlider min={-1000} max={1000} step={10} currentValue={canvasOffsetY} onChange={(newValue: number) => { setCanvasOffsetY(newValue); }} />
</DraggableWindow>
This memoized block proves that the coordinate labels are precomputed from a fixed graph-space range before the SVG is rendered.
const labels = useMemo(() => {
const xLabels = [];
const yLabels = [];
for (let x = minX; x <= maxX; x += step) {
for (let y = minY; y < maxY; y += step) {
xLabels.push({ x, y: y + step / 2, val: x });
}
}
// ... matching yLabels loop omitted
return { xLabels, yLabels };
}, [minX, maxX, minY, maxY, step]);
This canvas slot wiring is what keeps the grid aligned with graph space instead of with the page viewport.
<RelationGraph options={graphOptions}>
<RGSlotOnCanvas>
<div className="absolute left-[-1000px] top-[-1000px]">
<CoordinateGrid />
</div>
</RGSlotOnCanvas>
</RelationGraph>
This shared settings row shows how the helper panel changes relation-graph input behavior at runtime without reloading the scene.
<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 export helper uses relation-graph’s image-preparation APIs before handing the canvas DOM to modern-screenshot.
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
The comparison data makes this example stand out for one narrow reason: it turns setCanvasCenter(...) into the main lesson. External sliders drive X and Y recentering directly, so the demo isolates canvas movement itself instead of combining it with data loading or layout transitions.
It is also more measurement-oriented than nearby examples. Compared with canvas-caliper, this demo moves the canvas center across a fixed graph-space grid instead of annotating the currently visible viewport with rulers. Compared with area-set, the canvas slot is analytical rather than semantic: it exists to reveal coordinates and offsets, not to partition a populated scene. Compared with zoom and gee-thumbnail-diagram, the surrounding controls change graph-space position itself rather than scale or minimap configuration.
The most distinctive combination is an otherwise empty fixed-layout graph, a labeled RGSlotOnCanvas grid that spans negative and positive coordinates, and slider-driven setCanvasCenter(...). That makes this example a stronger starting point for debugging graph-space alignment than data-heavy demos that happen to include viewport controls.
Where Else This Pattern Applies
This pattern transfers well to tools that need precise external camera control. Typical follow-on uses include recenter buttons that jump to a selected work area, synchronized inspectors that move the canvas to a chosen coordinate, and guided tutorials that step through graph-space landmarks.
It also fits measurement-heavy overlays. A team could replace the demo grid with floor-plan axes, warehouse coordinates, engineering guides, game-map sectors, or large-image annotation guides while keeping the same RGSlotOnCanvas plus setCanvasCenter(...) structure.
Finally, it is a practical template for troubleshooting. When a project needs to verify whether slot content, graph-space coordinates, screenshots, and interaction modes stay aligned, an empty scene with a labeled overlay is often easier to reason about than a populated business graph.