JavaScript is required

Simulated data flow path

This example builds a prepared technical network graph where users choose two nodes, compute the shortest connection path, and replay a simulated data flow step by step along that route. It combines live-graph shortest-path lookup, custom line-slot animation, runtime layout switching, and shared canvas and export controls.

Shortest Path Data Flow Playback on a Relation Graph

What This Example Builds

This example builds a read-only technical network viewer that turns a shortest-path query into a visible data-flow replay. The canvas starts with a prepared graph, dims nodes and lines that are not on the chosen route, and then animates the selected route one segment at a time so the flow direction is easy to follow.

Visually, the example uses a dark telemetry-style background, glowing line treatments, and rounded icon-based node chips instead of default node labels. Users can trigger a route by selecting two different nodes, switch between tree and center layouts, change the playback speed, drag or minimize the floating control window, open shared canvas settings, and export the current graph as an image. On first render, the demo also auto-plays a default route from H-7-21 to 2.

How the Data Is Organized

The graph data is assembled inline inside initializeGraph() as a static RGJsonData object with rootId, a flat nodes array, and a flat lines array. Each line carries from, to, and text, and some lines point toward the root while others point away from it, which gives the playback logic a real need to handle mixed visual direction.

Before setJsonData(...) runs, the example preprocesses every line by attaching custom marker IDs, turning on start arrows, and disabling end arrows. The path search is not performed against a separate data copy. Instead, calcShortestPath(...) rebuilds a helper graph from the current relation-graph nodes and links, finds the node path, maps that node path back to line IDs, and dims everything outside the route.

In a real application, the same structure could represent service dependencies, ETL stages, device hops, queue transfers, approval chains, or any pipeline where users need to inspect how something moves between two known endpoints.

How relation-graph Is Used

The demo is wrapped in RGProvider, and RGHooks.useGraphInstance() is the central runtime API. The initial RGOptions set RGJunctionPoint.ltrb, circular nodes, RGLineShape.StandardCurve, and a left-to-right tree layout. After loading data, the code uses setJsonData(...), moveToCenter(), zoomToFit(), and setCanvasOffset(...) to build and position the graph. The layout selector later updates layout.layoutName and reruns initialization so the same dataset can be replayed in either tree or center mode.

The most important customization is RGSlotOnLine. MyLineContent replaces the default line surface, asks the graph instance to generate path geometry, computes the SVG path length, writes CSS variables such as --rg-line-path and --my-step-time, and conditionally renders a small moving rectangle on active segments. The same slot also forwards clicks back into relation-graph so custom rendering does not break the normal line-click event path.

RGSlotOnNode is used more lightly. It renders each node as a rounded pill with a LandPlotIcon and text, while SCSS overrides supply the dark background, cyan borders, checked-state label reveal, played-path stroke animation, and reverse-direction playback styling. MySvgDefs contributes the custom start and end marker definitions used by the loaded lines.

The floating DraggableWindow is shared demo scaffolding, but it still matters here because it hosts the layout selector, speed selector, canvas behavior settings, and screenshot export action. Inside that shared panel, RGHooks.useGraphStore() exposes the live wheel and drag modes, and RGHooks.useGraphInstance() powers prepareForImageGeneration() and restoreAfterImageGeneration() for image export. This remains a viewer, not an editor: users can inspect, replay, relayout, and export the graph, but they do not create or reconnect nodes and lines.

Key Interactions

The primary interaction is two-step node selection. The first node click stores a source node ID, and the second distinct node click computes the shortest route and restarts playback. Clicking the same node twice does nothing. This is the actual implemented behavior even though the helper text in the floating panel is looser than the code.

Playback itself is timer-driven. Each step marks the current line as active, marks previously visited lines as played, highlights the current receiving node, and then schedules the next step after the selected delay. When the chosen route traverses a line against that line’s stored from -> to direction, the demo sets reverseLine so the draw animation and moving marker run backward along that segment.

There are also several supporting interactions that materially affect the experience. Clicking a line clears any pending first-node selection. Changing the playback speed restarts the current route so the new timing applies immediately. Switching layouts rebuilds the graph with the same dataset and then replays the same kind of flow interaction on the new arrangement. The floating window can be dragged, minimized, opened into a settings overlay, and used to change wheel or drag behavior or download an image of the graph.

Key Code Fragments

This fragment shows that the example starts from ordinary relation-graph options and then builds a local dataset instead of fetching it from elsewhere.

const graphOptions: RGOptions = {
    defaultJunctionPoint: RGJunctionPoint.ltrb,
    defaultNodeShape: RGNodeShape.circle,
    defaultLineShape: RGLineShape.StandardCurve,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 100,
        treeNodeGapV: 20
    }
};

This fragment proves that line markers are preprocessed before load and that the initial render immediately starts a default playback.

myJsonData.lines.forEach(line => {
    line.endMarkerId = 'my-arrow-001';
    line.startMarkerId = 'my-arrow-001-start';
    line.showStartArrow = true;
});
myJsonData.lines.forEach((line) => {
    line.showEndArrow = false;
});
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
graphInstance.setCanvasOffset(canvasOffset.x, canvasOffset.y + 100);
showDataFlow('H-7-21', '2');

This fragment shows that route playback is triggered by two distinct node clicks and recomputed from the live graph instance.

const onNodeClick = (node: RGNode) => {
    if (checkedNodeIdRef.current) {
        if (checkedNodeIdRef.current === node.id) {
            return;
        }
        showDataFlow(checkedNodeIdRef.current, node.id);
        checkedNodeIdRef.current = '';
    } else {
        checkedNodeIdRef.current = node.id;
    }
};
const showDataFlow = (fromNodeId: string, toNodeId: string) => {
    const { lineIdsOnPath, nodeIdsOnPath } = calcShortestPath(fromNodeId, toNodeId, graphInstance, '');
    allLineIdsOnPathRef.current = lineIdsOnPath;
    allNodeIdsOnPathRef.current = nodeIdsOnPath;
    restartTask();
};

This fragment shows the shortest-path helper rebuilding its working graph from the current relation-graph state instead of relying on a separate index.

const graphDb = new ShortestPathGraph();
graphDb.loadDataFromRelationGraph(graphInstance);
const nodeIdsOnPath = graphDb.findPath(fromNodeId, toNodeId);
if (!nodeIdsOnPath) {
    throw new Error('Cannot find the association between the start node and the end node!');
}
const lineIdsOnPath = getAllLineIdsOnPath(nodeIdsOnPath, graphInstance);
drawMinPath(nodeIdsOnPath, lineIdsOnPath, graphInstance, itemsOnPathClassName);

This fragment shows the custom line slot translating generated path geometry into CSS-driven motion rendering.

const linePathInfo = useMemo<RGLinePathInfo>(() => graphInstance.generateLinePath(lineConfig), [lineConfig]);
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathEl.setAttribute('d', linePathInfo.pathData);
const pathLength = pathEl.getTotalLength();
return (<g
    className={`${lineConfig.line.data?.played ? (lineConfig.line.data?.reverseLine ? 'my-draw-path-animation reverse-line' : 'my-draw-path-animation') : ''}`}
    style={{
        '--rg-line-path': `path('${linePathInfo.pathData}')`,
        '--my-line-path-length': pathLength + 'px',
        '--my-step-time': `${lineConfig.line.data?.myStepTime || 1000}ms`
    }}>

What Makes This Example Distinct

According to the comparison data, the closest functional neighbor is line-style-pro, but the emphasis is different. line-style-pro demonstrates reusable path-effect themes on built-in line surfaces, while this example pushes more deeply into slot-level motion rendering: it computes a route, replaces the active route with a custom line slot, and animates each segment with a moving marker and stroke-draw timing.

The comparison file also separates this example from layout-focused viewers such as use-dagre-layout-2 and layout-tree. Those examples treat relayout as the main teaching point. Here, runtime layout switching is secondary. It exists so the same shortest-path playback interaction can be replayed under a different arrangement, not to teach layout tuning by itself.

Relative to slot-heavy technical viewers such as table-relationship and line-centric demos such as line-shape-and-label, this example uses line customization as route-specific runtime behavior rather than fixed endpoint wiring or graph-wide line geometry controls. The strongest rare combination is the one identified in the prepared comparison data: mount-time autoplay, shortest-path derivation from live graph state, dimmed non-path context, custom arrow markers, dark telemetry styling, speed control, and direction-aware segment replay that can restart after layout or speed changes.

Where Else This Pattern Applies

This pattern transfers cleanly to dependency tracing, data lineage walkthroughs, and service-hop inspection. If a team needs to explain how an event travels across systems, or why one destination is reached through a particular chain of intermediate objects, the combination of shortest-path lookup, route dimming, and timed playback is easier to read than a static highlighted path alone.

It also fits operator-facing tools and teaching views. Examples include replaying workflow approvals, showing incident blast paths across infrastructure, explaining queue-to-consumer delivery chains, or demonstrating recommended routes through a process graph. In each case, the reusable idea is not the placeholder H-* dataset, but the way relation-graph is used to rebuild a route from live graph state and then turn that route into a paced visual trace.