Search Node and Focus Viewport
This example builds a people-relationship viewer with a floating search panel that derives selectable names from the loaded graph and focuses the viewport on the chosen node. It loads static RGJsonData asynchronously, renders avatar nodes and labeled straight multi-links, and combines `focusNodeById(...)` with shared canvas settings and image export controls.
Search a Relationship Graph and Focus the Selected Node
What This Example Builds
This example builds a full-height people-relationship viewer with a floating search panel above the graph canvas. The graph shows avatar-based person nodes, labeled straight relationships, and a shared utility window that can be dragged, minimized, or switched into a settings view.
Users can type part of a person name, pick a filtered result, and watch the viewport jump to that node with a short animation. The main point is not graph editing or path analysis. It is the locator pattern: derive a search UI from the loaded graph, then use that UI to navigate the canvas quickly.
How the Data Is Organized
The graph data comes from a local static RGJsonData payload in mock-data-api.ts. It includes a rootId, a nodes array, and a lines array. Each node record carries id, text, color, borderColor, and a data object that contains person attributes such as gender, isGoodMan, and an icon URL used by the custom avatar slot. Each line record uses from, to, text, color, fontColor, and data.type, so the graph can render named relationship labels directly on the edge.
There is very little preprocessing before the graph is loaded. fetchJsonData() just wraps the static payload in a short async delay, and the more important transformation happens after setJsonData(...): the example calls graphInstance.getNodes() and converts the rendered nodes into { text, value } options for the search panel. In a real product, the same shape could represent employees, investigators, customers, suppliers, devices, or any other named entities in a relationship map.
How relation-graph Is Used
index.tsx provides the graph context through RGProvider, and MyGraph.tsx takes control with RGHooks.useGraphInstance(). The graph options make the scene read like a relationship board rather than a generic node canvas: straight lines, circular nodes, line labels on the path, multiLineDistance = 20 for repeated edges, a force layout with lower repulsion, and a right-bottom vertical toolbar. The instance API then drives the full lifecycle: loading(), setJsonData(...), clearLoading(), moveToCenter(), zoomToFit(), getNodes(), focusNodeById(...), clearChecked(), and the temporary animation helpers around the focus jump.
The example customizes node rendering with RGSlotOnNode, replacing the default node body with a circular portrait and a label below it. It does not use editing APIs or viewport slots in the reviewed source, so this remains a viewer-oriented example. Outside the graph itself, QueryFormPanel supplies the search UI, while the shared DraggableWindow component contributes the floating shell, the settings overlay, and the image-export action. Inside that shared window, CanvasSettingsPanel uses RGHooks.useGraphStore() plus graphInstance.setOptions(...) to switch wheel and drag behavior at runtime.
The stylesheet in my-relation-graph.scss is also part of the implementation, not decoration only. It changes node-name coloring, turns line labels into white badges, adds a stronger checked-node highlight, recolors checked line labels, and keeps the custom avatar node circular.
Key Interactions
- Typing in the search field filters the node list case-insensitively by visible label text.
- Choosing one search result closes the dropdown, clears the keyword state, and triggers animated
focusNodeById(...)navigation to the selected node. - Typing again after a selection clears the previous chosen value so the input can become a fresh search box.
- Clicking empty canvas space calls
clearChecked(), which resets checked graph elements. - The floating panel can be dragged by its title bar and minimized when the settings overlay is not open.
- The settings button opens an overlay inside the same window, clicking the translucent backdrop closes it, and the panel can switch wheel and drag modes at runtime.
- The same shared overlay can export the current graph as an image.
Key Code Fragments
This fragment shows the mount-time graph initialization sequence and the graph-derived search-option list:
graphInstance.loading();
await graphInstance.setJsonData(myJsonData);
graphInstance.clearLoading();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
setNodeItems(graphInstance.getNodes().map(n => ({ text: n.text || '', value: n.id })));
This fragment shows that the example turns one selected search result into a short animated viewport jump:
const focusOnNodeWithAnimation = async (nodeId: string) => {
graphInstance.enableCanvasAnimation();
focusOnNode(nodeId);
await graphInstance.sleep(300); // Wait for animation to complete then turn off animation
graphInstance.disableCanvasAnimation();
};
This fragment proves that the dropdown search is case-insensitive and works on node labels rather than hardcoded numeric positions:
const filtered = data.filter((item) =>
item.text.toLowerCase().includes(keyword.toLowerCase())
);
This fragment shows how typing after a previous selection clears that selection and reopens the dropdown for a new search:
onChange={(e) => {
if (value) {
onListItemClick(null);
}
setKeyword(e.target.value);
setOpen(true);
}}
This fragment shows the custom avatar node slot that uses node.data.icon and places the name under the portrait:
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => (
<div className="w-12 h-12 flex place-items-center justify-center">
<div className="my-node-avatar" style={{ backgroundImage: `url(${node.data?.icon})` }} />
<div className="my-node-name absolute transform translate-y-[35px]">{node.text}</div>
</div>
)}
</RGSlotOnNode>
This fragment shows that the shared settings overlay changes relation-graph interaction behavior 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 workflow used by the shared floating window:
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 artifacts place this demo near scene-relationship, focus-node-by-id, and find-min-path, but the emphasis here is narrower. Compared with scene-relationship, this example uses the floating panel as a node locator rather than as a graph-wide metadata filter. Compared with find-min-path, it asks for only one destination and skips route computation, dimming, and path emphasis. Compared with focus-node-by-id, it replaces a fixed id picker with a search box derived from graphInstance.getNodes(), so the focus behavior works with live labels from an already loaded relationship graph.
That makes the strongest combination very specific: async relationship-map loading, live node-to-form option derivation, a draggable white search panel, avatar node slots, straight labeled repeated links, and a search-triggered animated focus jump. The distinctive part is not the shared settings overlay or image export, because those come from common helper code used by other demos. The distinctive part is the product-facing locator flow on top of a denser people-relationship scene.
Where Else This Pattern Applies
This pattern transfers well to relationship viewers where operators need to find one known person or entity quickly without manually panning across the full canvas. Good migration targets include organization charts, investigation boards, customer-account maps, partner ecosystems, infrastructure dependency views, and knowledge-graph browsers.
It also extends cleanly to richer retrieval workflows. The same graph-driven option derivation could feed an autocomplete, a command palette, a backend-assisted search service, or a permission-filtered picker while keeping the same focus animation and shared canvas controls.