Animated Route Progress Line Slot
This example replaces relation-graph's default line rendering with a custom route-progress slot driven by per-line metadata such as icon, speed, weight, and completion percent. It also reveals richer metrics when a line is checked, adds a coordinated circular node slot, and reuses shared viewer utilities for canvas settings and image export.
Custom Route Progress Line Slots with Checked-State Metrics
What This Example Builds
This example builds a left-to-right route tree that looks like a transport or flow-monitoring board rather than a default graph. Each line becomes a thick animated route with a moving dot, a vehicle emoji positioned along the computed path, and a label card that expands when the line is checked.
Users can inspect lines, click the moving marker or label without losing relation-graph’s normal line event flow, open a line-specific Detail action, drag or minimize the floating help window, switch canvas interaction modes, and export the graph as an image. The main point of the demo is that per-line metadata drives both the motion and the detail reveal, while relation-graph still handles layout, path geometry, and line-state management.
How the Data Is Organized
The graph data is declared inline as RGJsonData with rootId, nodes, and lines. Every line still uses relation-graph’s standard from and to structure, but each one also carries a data object with myIcon, myCapacity, myWeight, mySpeed, and myPercent. That payload is the real content of the example: it determines which vehicle marker is shown, how large it is, how fast the white dot moves, and how far the highlighted route has progressed.
There is one small preprocessing step before setJsonData: every line has showEndArrow = false. That keeps the route display closer to a monitored track than a directional connector with arrowheads. In a real application, the same shape could represent shipment routes, process transfer lines, pipeline status, service traffic, or machine flow, as long as each connection can expose a progress value and a few metrics.
How relation-graph Is Used
RGProvider wraps the example so hooks can resolve the active graph instance. RelationGraph is configured with a tree layout that grows from the left, levelGaps of [500, 400], RGJunctionPoint.lr, and RGLineShape.StandardCurve. Those defaults make the route geometry predictable enough for path-following animation.
The key extension point is RGSlotOnLine. MyGraph passes the runtime line slot props into MyLineContent, then adds a parent callback for the Detail action. Inside MyLineContent, RGHooks.useGraphInstance() calls generateLinePath to get the real SVG route, generateLineTextStyle to reuse relation-graph’s computed label placement, and onLineClick to forward custom SVG or HTML clicks back into relation-graph’s line event pipeline.
The slot keeps relation-graph primitives in the loop instead of reimplementing everything manually. RGLinePath renders the actual SVG path and receives the forwarded click handler. RGLineText renders the HTML label container when the line is in the div-text branch. A wrapper <g> writes CSS variables such as the path itself, total length, moved length, moved percent, and speed, and the stylesheet consumes those variables to animate the stroke, white dot, and emoji marker with stroke-dasharray and offset-path.
The example also pairs the custom line slot with RGSlotOnNode, which replaces the default node body with a circular warehouse icon node. Outside the graph body, the graph instance API loads and frames the dataset through setJsonData, moveToCenter, and zoomToFit. The shared DraggableWindow overlay uses RGHooks.useGraphStore() and setOptions(...) to switch wheel and drag behavior, then uses prepareForImageGeneration() and restoreAfterImageGeneration() with modern-screenshot to export the graph canvas.
Key Interactions
- Clicking the custom SVG route, the emoji marker, or the HTML label still forwards into relation-graph’s
onLineClickhandling. - The richer metric block is hidden by default and only becomes visible when the line enters the checked state.
- The
Detailbutton calls back into the parent component with the currentRGLineand opens a line-specific alert. - The floating overlay can be dragged, minimized, switched into a settings panel, and used to export an image.
- The settings panel changes wheel behavior and canvas drag behavior without reloading the graph.
Key Code Fragments
This block shows the left-to-right tree layout and the default line settings the custom renderer relies on.
const graphOptions: RGOptions = {
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineShape: RGLineShape.StandardCurve,
layout: {
layoutName: 'tree',
from: 'left',
levelGaps: [500, 400]
}
};
This block shows that each route line carries its own icon, capacity, weight, speed, and progress data before the default end arrows are disabled.
lines: [
{ id: 'l1', from: 'base', to: '1', text: 'Line X01', data: { myIcon: '🚢', myCapacity: 1.2, myWeight: 1, mySpeed: 1, myPercent: 0.2 } },
{ id: 'l2', from: 'base', to: '2', text: 'Line X02', data: { myIcon: '🚢', myCapacity: 2, myWeight: 1.8, mySpeed: 0.3, myPercent: 0.8 } },
// ...
]
myJsonData.lines.forEach((line) => {
line.showEndArrow = false;
});
await graphInstance.setJsonData(myJsonData);
This block shows the graph wiring both the custom line slot and the secondary custom node slot.
<RelationGraph
options={graphOptions}
onLineClick={onLineClick}
>
<RGSlotOnLine>
{(lineSlotProps: RGLineSlotProps) => (
<MyLineContent
{...lineSlotProps}
onMyLineDetailClick={onMyLineDetailClick}
/>
)}
</RGSlotOnLine>
<RGSlotOnNode>
This block shows the line slot deriving real path geometry and converting route metadata into CSS variables for motion and checked-state styling.
const linePathInfo = useMemo<RGLinePathInfo>(() => graphInstance.generateLinePath(lineConfig), [lineConfig]);
const textStyle = graphInstance.generateLineTextStyle(lineConfig, linePathInfo);
const pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathEl.setAttribute('d', linePathInfo.pathData);
const pathLength = pathEl.getTotalLength();
return (<g
style={{
'--my-line-path': `path('${linePathInfo.pathData}')`,
'--my-line-path-length': pathLength + 'px',
'--my-moved-length': (pathLength * lineConfig.line.data?.myPercent) + 'px',
'--my-moved-percent': lineConfig.line.data?.myPercent * 100 + '%',
'--my-speed': ((1 / lineConfig.line.data?.mySpeed) * 0.5) + 's'
}}>
This block shows the slot keeping RGLinePath but adding a moving dot and a size-varying vehicle marker that sits on the route path.
<RGLinePath
lineConfig={lineConfig}
linePathInfo={linePathInfo}
useTextOnPath={useSvgTextPath}
checked={checked}
graphInstanceId={graphInstanceId}
onLineClick={onLineClick}
>
<g>
<circle className="my-dot" r="3"></circle>
<g className="my-container">
<text onClick={onLineClick} fontSize={(lineConfig.line.data?.myWeight || 1) * 20 + 15} y={-5} transform="scale(-1, 1)">{lineConfig.line.data?.myIcon}</text>
</g>
</g>
</RGLinePath>
This block shows the HTML label branch rendering the route metrics and a parent-handled detail action.
<div className="my-text">
<div>Cap: {lineConfig.line.data?.myCapacity.toFixed(1)}</div>
<div>Weight: {lineConfig.line.data?.myWeight.toFixed(1)}</div>
<div>Speed: {lineConfig.line.data?.mySpeed.toFixed(1)}</div>
</div>
<button
className="cursor-pointer hover:underline bg-blue-600 text-white py-1 px-3 rounded mt-2"
onClick={() => { onMyLineDetailClick(lineConfig.line); }}
>
Detail
</button>
This block shows the CSS rule that keeps the metric card hidden until the line is checked.
.rg-line-peel.rg-line-checked {
.rg-line-label {
display: block;
background-color: rgb(255, 0, 150);
color: #fff;
}
.my-text {
display: block;
}
}
This block shows the shared viewer utility panel changing canvas behavior and exporting an image of the prepared graph DOM.
const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;
<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 }); }}
/>
What Makes This Example Distinct
The comparison data positions this example as the status-rich member of the nearby custom line slot demos. Its strongest distinguishing trait is that it turns per-line metadata into route-progress rendering by combining RGSlotOnLine, generateLinePath, generateLineTextStyle, RGLinePath, and RGLineText in one custom renderer. That combination is then pushed further by using the same metadata to control marker icon, marker size, draw distance, and animation speed.
Compared with adv-line-slot-1, this variant is more data-driven. It does not stop at a generic moving dot and a simple detail link; it adds myPercent, checked-only route metrics, and a coordinated node treatment. Compared with adv-line-slot, it shifts the focus away from endpoint annotation and toward monitored progress along the route itself. Compared with line, the distinction is even sharper: this demo replaces the line renderer and measures the generated path, instead of staying inside relation-graph’s built-in line option gallery.
The custom node slot matters here, but it is secondary. The comparison record is clear that the real lesson is checked-line inspection plus path-driven route animation, not a broad node-style showcase.
Where Else This Pattern Applies
This pattern transfers well to logistics tracking, warehouse transfer monitoring, pipeline status boards, infrastructure flow views, service traffic maps, and production routing dashboards. It is especially useful when each connection needs to expose progress, speed, or load directly on the line, while keeping the graph read-only and preserving relation-graph’s built-in layout and event handling.