Runtime Graph Controls
This example shows a force-layout people relationship graph controlled from a floating utility window. It demonstrates how external UI can call relation-graph instance APIs to focus nodes, animate movement, mutate node state, add followers at runtime, switch canvas behavior, and export the graph as an image.
Runtime Graph Controls on a Force-Layout People Map
What This Example Builds
This example builds a full-screen people relationship graph with a draggable utility window layered above it. The graph uses avatar portraits for nodes, straight labeled links for relationships, and a force layout so the network keeps a dynamic, organic arrangement instead of a fixed tree or grid.
The user-facing experience is centered on runtime control. The floating window can jump the viewport to specific people, run an animated locate action, reduce one node’s opacity, move one node toward another node, add follower nodes after the graph has already loaded, switch canvas drag and wheel behavior, and export the graph as an image.
The most important point is that these behaviors are not implemented as separate demos or as on-canvas editor overlays. One external control surface drives several graphInstance APIs against the same live graph.
How the Data Is Organized
The example loads a local RGJsonData object from mock-data-api.ts. The data has a rootId, a nodes array, and a lines array. Each node carries core graph fields such as id, text, color, and borderColor, plus a data payload that includes icon, sexType, and isGoodMan. Each line carries from, to, text, color fields, and a typed data object for the relationship label.
There is almost no preprocessing before render. fetchJsonData() simulates an asynchronous request with a short timeout and then returns the local JSON object directly. initializeGraph() passes that payload straight into setJsonData(...), so the example focuses on runtime control after load rather than on data normalization before load.
The dataset is also prepared to show relation-graph presentation details. The sample includes repeated links such as N13 -> N8, which makes multiLineDistance: 20 visible, and each node stores an avatar URL that the node slot can render as a portrait.
At runtime, newly added follower nodes use a smaller structure: generated ids, default text, seeded x and y coordinates near the parent node, and a simple line back to that parent. In a production graph, the same pattern can represent added employees, suggested contacts, downstream services, investigation leads, or newly discovered entities.
How relation-graph Is Used
The graph is mounted under RGProvider, which makes relation-graph hooks available both in MyGraph and in the shared CanvasSettingsPanel.
The RelationGraph instance is configured with a force layout, maxLayoutTimes: 100, circular nodes, straight lines, labels rendered on the line path, multiLineDistance: 20, and default node border and fill styling. That mix is important because the example is trying to show movement, repeated edges, and live graph growth on top of an already styled people map.
RGHooks.useGraphInstance() is the central integration point. It is used for initial loading, viewport centering and fitting, node lookup, animated focus, runtime mutation, relayout, option switching, and image export preparation. RGHooks.useCheckedItem() contributes the current checked node id so the random-follower button can stay disabled until the graph has a meaningful target. In the shared window component, RGHooks.useGraphStore() exposes current drag and wheel settings so the settings panel reflects live canvas state.
The example uses slots selectively. RGSlotOnNode replaces the default node body with an avatar image plus a text label below it. RGSlotOnView is mounted but intentionally empty in the current version, which means the demo keeps its UI outside the canvas rather than tracking overlays inside the view.
Styling is split between graph options and SCSS overrides. The SCSS changes the node label color, recolors expand buttons, adds a strong halo and filled label chip for checked nodes, and switches checked line labels to a filled state. Together with the slot-based avatar rendering, those overrides give the graph a domain-specific look without replacing the underlying graph engine.
Key Interactions
- The floating window can be dragged, minimized, and switched into a settings mode from its header.
- One locate button focuses node
N5immediately, while another temporarily enables canvas animation, focusesN3, waits 300 ms, and then turns animation back off. - A dedicated action focuses
N6and toggles its opacity between1and0.3, which demonstrates direct node mutation without rebuilding the dataset. - Two movement actions frame
N8with a target node by callingzoomToFitWithAnimation(...), then moveN8toward that target with a custom interpolation loop. - One follower action always adds two children to
N1; another stays disabled until the graph has a checked node and then adds a random number of children to that checked node. - Clicking the canvas clears the checked graph item and resets the local hidden menu and info-card flags.
- The settings panel switches
wheelEventActionanddragEventActionat runtime and can export the current graph as an image.
Key Code Fragments
This configuration establishes the force layout, circular avatars, straight labeled links, and multi-link spacing that shape the example’s baseline behavior.
const graphOptions: RGOptions = {
debug: false,
defaultLineShape: RGLineShape.StandardStraight,
defaultNodeShape: RGNodeShape.circle,
defaultLineTextOnPath: true,
multiLineDistance: 20,
layout: {
layoutName: 'force',
maxLayoutTimes: 100
},
defaultNodeBorderWidth: 2,
defaultNodeColor: '#e85f84'
};
This load sequence shows that the demo does not remap data before rendering; it loads JSON, binds it to the instance, fits the viewport, and then programmatically activates one node.
const initializeGraph = async () => {
const myJsonData: RGJsonData = await fetchJsonData();
graphInstance.loading();
await graphInstance.setJsonData(myJsonData);
graphInstance.clearLoading();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
setTimeout(() => {
onNodeClick(graphInstance.getNodeById('N3'));
}, 1000);
};
This helper shows how runtime graph growth is added incrementally: create ids, seed starting coordinates near the parent, add nodes and lines, then restart auto layout.
const addChildNodeTo = (parentNodeId: string, newChildrenCount: number) => {
const newNodes: JsonNode[] = [];
const newLines: JsonLine[] = [];
const parentNode = graphInstance.getNodeById(parentNodeId);
for (let i = 0; i < newChildrenCount; i++) {
const newNodeId = graphInstance.generateNewNodeId();
newNodes.push({
id: newNodeId,
text: 'New Node',
x: parentNode.x + 200,
y: parentNode.y + (Math.random() * 200 - 100)
});
newLines.push({ id: 'line-to-' + newNodeId, from: parentNodeId, to: newNodeId });
}
The movement routine combines built-in viewport animation with a custom per-frame node update, which is one of the clearest runtime-control patterns in this example.
await graphInstance.zoomToFitWithAnimation([node8, targetNode]);
const finalXy = {
x: targetNode.x - 50,
y: targetNode.y
}
await animateMove(node8, finalXy, (x, y) => {
graphInstance.updateNode(node8, {
x, y
});
}, 800);
The node slot replaces default node content with portrait rendering based on the dataset’s data.icon field.
<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>
The shared settings panel shows that canvas behavior and export are also runtime instance operations, not compile-time options.
const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
backgroundColor: graphBackgroundColor
});
if (imageBlob) {
downloadBlob(imageBlob, 'my-image-name');
}
await graphInstance.restoreAfterImageGeneration();
What Makes This Example Distinct
Compared with nearby examples such as search-and-focus and find-min-path, this example is not mainly a query or retrieval workflow. Its main value is that one floating workbench drives multiple graph-instance commands that actively change the live graph.
Compared with scene-relationship-op, the control model is different. That example emphasizes node-centered overlays and contextual authoring inside the canvas, while this one keeps almost all visible controls in an external utility window and treats the graph as a runtime operations surface.
Compared with line-shape-and-label or adv-effect2, the focus is less about graph-wide presentation changes or reversible highlighting. This example goes further into targeted node mutation, incremental graph growth, animated focus, and manual position updates.
The rare combination is what makes it useful as a reference: avatar-based people data, a floating control window, animated viewport focus, direct node-state mutation, runtime node and line insertion with relayout, and shared canvas settings plus image export in one screen. The moveN8To(...) flow is especially distinctive because it mixes zoomToFitWithAnimation(...) with a custom animateMove(...) loop that repeatedly calls updateNode(...).
Where Else This Pattern Applies
This pattern transfers well to investigation workbenches where analysts need to jump between entities, reveal newly discovered neighbors, and export the current state without leaving the graph.
It also fits operational topology tools where support staff need external controls for camera movement, emphasis, and incremental graph expansion while keeping the canvas itself visually clean.
Another extension is guided product demos or training tools. A floating panel can expose selected runtime actions one by one, making relation-graph instance APIs easier to explain than a fully open-ended editor.
The same structure can support admin consoles that combine live canvas settings, snapshot export, and targeted graph mutations around a preloaded network. In those scenarios, the people dataset here can be replaced with services, devices, organizations, cases, or any entity graph that benefits from controlled runtime actions.