JavaScript is required

Animated Shortest Path Highlighting

This example shows how to compute a shortest path from the live relation-graph instance and highlight only that route with animated built-in line styles. It combines two-click endpoint selection, autoplay path playback, runtime layout switching, custom SVG markers, and dimmed context rendering in one read-only viewer.

Animated Shortest-Path Highlighting with Selectable Line Effects

What This Example Builds

This example builds a read-only relation viewer that continuously emphasizes the shortest route between two nodes in a compact graph. The canvas uses icon-based circular nodes, curved links with custom arrow markers, and a dark neon visual theme. Users can click two nodes to compute a route manually, let the demo replay random routes automatically, switch between tree and center layouts, and change the visual skin used on the active route.

The main point is not generic line theming. The selected effect class is reserved for the current shortest path, while unrelated nodes and lines are dimmed to push attention toward the route under inspection.

How the Data Is Organized

The graph data is declared inline as one RGJsonData object with a rootId, a flat nodes array, and a flat lines array. The sample mixes outgoing and incoming lines around the same hub so the highlighted route can cross different parts of the topology.

Before setJsonData runs, the code rewrites every line to use custom SVG marker ids, enables the start arrow, and disables the default end arrow. The pathfinding data is not kept as a separate static structure. Instead, the example rebuilds a helper graph from graphInstance.getNodes() and graphInstance.getLinks(), then reconstructs a shortest route by walking those connections in either direction. In a production system, the same shape could represent service dependencies, process handoffs, warehouse routes, infrastructure traces, or escalation paths.

How relation-graph Is Used

RGProvider supplies the graph instance, and RGHooks.useGraphInstance() drives nearly all runtime behavior. The graph starts with layout.layoutName = 'tree', from = 'left', treeNodeGapH = 100, treeNodeGapV = 20, defaultJunctionPoint = RGJunctionPoint.ltrb, defaultNodeShape = RGNodeShape.circle, and defaultLineShape = RGLineShape.StandardCurve.

The example keeps relation-graph’s built-in renderers and customizes them through slots, defs, and CSS. RGSlotOnNode replaces the default node body with an icon-and-text node. MySvgDefs injects the custom arrow markers and the glitch filter used by the electric route style. The SCSS file targets .rg-node-peel, .rg-line-peel, .rg-line-bg, and .rg-line so the example can restyle built-in nodes and lines without providing a custom line slot.

At runtime, setJsonData loads the prepared graph, moveToCenter() and zoomToFit() normalize the viewport, updateOptions() relaunches the same dataset in another layout, and getNodes(), getLinks(), getLines(), updateNode(), and updateLine() compute and render the active path. The shared floating window also uses prepareForImageGeneration(), getOptions(), setOptions(), and restoreAfterImageGeneration() to expose canvas settings and image export around the graph.

Key Interactions

  • One node click stores a starting endpoint. A different second click computes the shortest path and highlights matching nodes and lines.
  • Clicking the same node twice is ignored. Clicking a line clears any pending first-node selection.
  • An 800ms timer feeds random nodes into the same node-click flow, so autoplay and manual path selection share one route-highlighting mechanism.
  • The floating panel switches the graph between tree and center layouts, reloads the dataset, and lets users swap the route effect between Electric Current and Data Flow.
  • The shared window can also be dragged, minimized, opened as a canvas-settings overlay, and used to export the current graph as an image.

Key Code Fragments

This initialization pass proves that line markers are assigned during data preparation, before the graph is loaded.

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);

This timer keeps replaying the same route-selection flow instead of creating a separate autoplay renderer.

const restartRandomPathTask = () => {
    clearInterval(playTimerRef.current);
    playTimerRef.current = setInterval(() => {
        const nodes = graphInstance.getNodes();
        const randomNode = nodes[Math.floor(Math.random() * nodes.length)];
        onNodeClick(randomNode);
    }, 800);
};

This click handler turns two endpoint selections into one shortest-path query and clears the pending start node afterward.

const onNodeClick = (node: RGNode, $event?: RGUserEvent) => {
    if (checkedNodeIdRef.current) {
        if (checkedNodeIdRef.current === node.id) {
            return;
        }
        calcShortestPath(checkedNodeIdRef.current, node.id, graphInstance, itemsOnPathClassName);
        checkedNodeIdRef.current = '';
    } else {
        checkedNodeIdRef.current = node.id;
    }
};

This helper shows that the pathfinding layer is rebuilt from the live relation-graph instance rather than from a second hard-coded adjacency list.

loadDataFromRelationGraph(graphInstance: RelationGraphInstance) {
    this.nodes = graphInstance.getNodes().map(n => {
        return { id: n.id, childs: [], indexed: false, parentNode: null };
    });
    this.edges = graphInstance.getLinks().map(link => {
        return { from: link.fromNode.id, to: link.toNode.id };
    });
}

This mutation pass applies the chosen effect class only to route lines and fades the rest of the graph context.

graphInstance.getLines().forEach((line: RGLine) => {
    if (lineIdsOnPath.includes(line.id)) {
        graphInstance.updateLine(line, { className: flagClassName });
    } else {
        graphInstance.updateLine(line, { opacity: 0.2 });
    }
});

This SCSS preset shows that the animated route skins are ordinary classes on relation-graph’s built-in line layers.

.rg-line-peel.my-line-class-12 {
    .rg-line-bg {
        stroke: #334455;
        stroke-width: 6px;
        stroke-dasharray: 9, 9, 9;
        animation: traffic-slow 4s linear infinite;
    }

    .rg-line {
        stroke: #00d2ff;
        stroke-dasharray: 20, 80;
        animation: traffic-fast 1.5s linear infinite;
    }
}

What Makes This Example Distinct

The comparison data positions this example between line-style demos and path-analysis demos. Compared with custom-line-animation and custom-line-style, it does not re-theme every rendered line. Instead, the style selector changes how the computed route is displayed, while non-route items are dimmed. Compared with find-min-path, it uses a lighter interaction model: two node clicks and a timer-driven replay loop reuse the same shortest-path routine rather than relying on a form-based query flow.

That combination is what makes the example stand out. The rare part is not any single ingredient by itself, but the mix of live shortest-path computation, path-only animated skins, custom SVG arrow markers, runtime layout switching, and autoplay-driven route playback in one viewer. It is therefore a stronger starting point for route-inspection interfaces than for generic graph-wide line-style galleries.

Where Else This Pattern Applies

This pattern transfers well to infrastructure and operations diagrams where teams need to inspect one route at a time without editing the graph. Examples include service-dependency tracing, logistics handoff maps, alert-escalation chains, data lineage viewers, and network path walkthroughs.

It also fits presentation-oriented tools that need both analysis and atmosphere. A team can keep the same underlying topology, switch layouts for readability, and apply different route skins for demos, investigations, or exported snapshots without replacing relation-graph’s built-in line renderer.