JavaScript is required

Focus Viewport by Node ID

This example builds a large node-only relation-graph canvas and drives viewport navigation from a fixed list of node IDs in a floating panel. It generates 160 nodes in memory, initializes the graph through instance APIs, wraps `focusNodeById(...)` in temporary canvas animation, and reuses shared controls for canvas settings and image export.

Focus the Viewport by Node ID on a Large Node-Only Canvas

What This Example Builds

This example builds a full-height relation-graph viewer that displays 160 disconnected rectangular nodes on a gray canvas and lets the user jump the viewport to a known target by choosing its node ID from a floating control window. The graph does not present business relationships or edge styling. It acts as a locator-style sandbox for moving around a large node field.

Users can pick one of ten preset node IDs, watch the canvas animate toward that target, drag or minimize the utility window, open a settings overlay, change wheel and drag behavior, and export the graph as an image. The most important point is the minimal external-control pattern: an outside UI drives focusNodeById(...) without rebuilding the graph.

How the Data Is Organized

The graph data is generated locally in MyGraph.tsx. initializeGraph() creates a JsonNode[] with 160 nodes named N0 through N159, assigns each node id, text, x, and y, and arranges those coordinates on a 22-column grid that starts at (-500, -500). The nodes are then wrapped in an RGJsonData object with lines: [], so the rendered graph is intentionally edge-free.

There is a small preprocessing step before the graph is loaded: the example computes positions in memory instead of reading a prepared JSON file or calling a backend. After that, it loads only the node array into the graph instance through clearGraph() and addNodes(). In real applications, the same structure could represent searchable map markers, large-canvas assets, seat or shelf locations, floor-plan objects, or any other items that users need to jump to by known identifier.

How relation-graph Is Used

index.tsx wraps the page with RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() as the main integration point. The graph options are narrow and explicit: debug: false, defaultNodeShape: RGNodeShape.rect, and layout.layoutName = 'force'. The example does not use setJsonData(...); instead, it imperatively clears the graph, adds generated nodes, centers the canvas, and fits the viewport after mount.

The primary relation-graph behavior is viewport navigation. A useEffect watches nodeId, temporarily enables canvas animation, calls focusNodeById(nodeId), and disables animation again after 350 ms. There are no node, line, canvas, or viewport slots in this example, and there is no editing workflow. The local SCSS only overrides the relation-graph background color to #aaa, so most of the visible behavior comes from the graph instance APIs and the shared control window.

The floating utility shell is implemented by the shared DraggableWindow component. Inside that shared component, CanvasSettingsPanel uses RGHooks.useGraphStore() to read the live wheelEventAction and dragEventAction, then updates them with graphInstance.setOptions(...). The same panel also supports export through prepareForImageGeneration(), getOptions(), domToImageByModernScreenshot(...), and restoreAfterImageGeneration().

Key Interactions

  • Clicking one of the preset pills updates nodeId and triggers an animated viewport jump to that node.
  • The focus flow comes from external UI rather than from clicking nodes inside the graph.
  • The Random Position button only calls graphInstance.refresh() in the reviewed source, so it should not be described as generating new coordinates here.
  • The floating window can be dragged by its title bar and collapsed with the minimize control.
  • The settings button opens an overlay inside the same window, and clicking the translucent backdrop closes it.
  • The settings overlay switches wheel behavior among scroll, zoom, and none, switches drag behavior among selection, move, and none, and exposes the Download Image action.

Key Code Fragments

This fragment shows that the graph is configured as a simple force-layout viewer with rectangular nodes and no extra presentation options:

const graphOptions: RGOptions = {
    debug: false,
    defaultNodeShape: RGNodeShape.rect,
    layout: {
        layoutName: 'force'
    }
};

This fragment proves that the example generates its dataset locally instead of reading an external JSON file:

const newNodes: JsonNode[] = [];
const startPoint = { x: -500, y: -500 };
for (let i = 0; i < 160; i++) {
    newNodes.push({
        id: 'N' + i,
        text: 'N' + i,
        x: startPoint.x + (i % 22) * 200,
        y: startPoint.y + Math.floor(i / 22) * 200
    });
}

This fragment shows the mount-time instance API sequence that loads a node-only graph and fits it into view:

const myJsonData: RGJsonData = {
    nodes: newNodes,
    lines: [] // no edges in this example
};
graphInstance.clearGraph();
graphInstance.addNodes(myJsonData.nodes);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

This fragment shows the core navigation pattern: enable animation, focus the chosen node ID, then turn animation back off:

const tabChange = async () => {
    graphInstance.enableCanvasAnimation();
    graphInstance.focusNodeById(nodeId);
    setTimeout(() => {
        graphInstance.disableCanvasAnimation();
    }, 350);
};

This fragment shows that the focus targets are a fixed preset list rather than a live search result derived from graph content:

<SimpleUISelect
    data={[
        { value: 'N1', text: 'N1' },
        { value: 'N20', text: 'N20' },
        // ... other preset ids ...
        { value: 'N159', text: 'N159' }
    ]}
    currentValue={nodeId}
    onChange={(newValue: string) => {
        setNodeId(newValue);
    }}
/>

This fragment shows how the shared settings panel updates relation-graph interaction modes 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 fragment shows the export flow that prepares the graph canvas for capture, renders it to an image blob, and restores graph state 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();

What Makes This Example Distinct

The comparison data places this example closest to search-and-focus, but the emphasis is different. search-and-focus works with a labeled people relationship graph and derives focus choices from live graph data, while this example generates 160 disconnected nodes and offers a hardcoded list of ten IDs. That makes focus-node-by-id the more stripped-down reference when the requirement is simply “jump the viewport to a known node ID from external UI.”

Compared with switch-layout, center-layout-options, and tree-distance, this example does not use the floating window as a layout tuning surface. It keeps one force-layout canvas and turns the control panel into a locator. Compared with line-shape-and-label, it removes edges entirely, so developers can isolate node-id-based viewport focusing without line geometry, label placement, or relationship semantics competing for attention.

The strongest distinctive combination is the one called out in the comparison artifacts: a gray edge-free rectangular node field, pill-style preset selectors, a floating white utility window, mount-time instance API bootstrapping, and a short animated viewport jump. That combination makes the page read as a navigation sandbox rather than a relationship viewer or layout demonstration.

Where Else This Pattern Applies

This pattern transfers well to graph experiences where users already know the identifier of the thing they want to inspect but still need help reaching it on a large canvas. Typical migration targets include topology locator tools, warehouse or seat maps, floor-plan inspection views, search-result jump panels, and operations dashboards that need overview-to-detail navigation.

It also applies when the selector should live outside the graph instead of inside node interactions. The hardcoded pill list in this demo could be replaced with a search box, a result table, a command palette, or a backend-driven ID list while keeping the same focus sequence and viewport-control APIs.