模拟数据的流转路径
动画数据流路径 - 最短路径计算与顺序数据流动画
动画数据流路径可视化
功能概述
本示例演示了高级路径可视化,带有节点之间的动画数据流。当选择两个节点时,系统使用 Dijkstra 算法计算它们之间的最短路径,然后沿该路径动画显示数据包传输。它具有带有动画粒子的自定义连线渲染、双向流指示器和交互式路径选择。用户还可以在树形和中心布局之间动态切换。
核心特性实现
最短路径计算
通过 calcShortestPath 工具使用 Dijkstra 算法:
const showDataFlow = (fromNodeId: string, toNodeId: string) => {
const { lineIdsOnPath, nodeIdsOnPath } = calcShortestPath(fromNodeId, toNodeId, graphInstance, '');
console.log('lineIdsOnPath:', lineIdsOnPath);
allLineIdsOnPathRef.current = lineIdsOnPath;
allNodeIdsOnPathRef.current = nodeIdsOnPath;
restartTask();
};
关键要点:
- 路径计算:返回构成最短路线的连线 ID 和节点 ID 数组
- 引用存储:使用引用在动画周期之间维护路径状态
- 任务重启:计算新路径时触发动画
动画数据流实现
playDataFlowAnimationTask 函数编排顺序连线动画:
const playDataFlowAnimationTask = async () => {
clearInterval(playTimerRef.current);
const stepTime = pathAnimationSpeed;
if (allLineIdsOnPathRef.current.length === 0) {
return;
}
let currentLineId = allLineIdsOnPathRef.current[currentLineIndexOfPathRef.current];
let currentNodeId = allNodeIdsOnPathRef.current[currentLineIndexOfPathRef.current];
if (currentLineIndexOfPathRef.current === 0) {
// 在动画开始时重置所有连线和节点
for (const line of graphInstance.getLines()) {
graphInstance.updateLineData(line, {
played: false,
reverseLine: false
});
}
graphInstance.getNodes().forEach(node => {
graphInstance.updateNode(node, {
className: ''
});
});
await graphInstance.sleep(200);
}
playDataFlowAnimationOnLine(currentLineId, currentNodeId, stepTime);
currentLineIndexOfPathRef.current++;
playTimerRef.current = setTimeout(() => {
playDataFlowAnimationTask();
}, stepTime);
};
关键要点:
- 顺序动画:一次沿路径动画一条连线
- 状态重置:在周期开始时清除以前的动画状态
- 可配置计时:使用
pathAnimationSpeed状态控制持续时间 - 递归调度:使用 setTimeout 进行下一步动画
逐连线动画控制
激活单个连线上的带方向流动画:
const playDataFlowAnimationOnLine = (currentLineId: string, currentNodeId: string, stepTime: number) => {
// 停止其他连线上的动画
graphInstance.getLines().forEach(line => {
if (currentLineId !== line.id && line.data?.playMyDataFlowAnimation) {
graphInstance.updateLineData(line, {
playMyDataFlowAnimation: false
});
}
});
const currentLine = graphInstance.getLineById(currentLineId);
graphInstance.updateLineData(currentLineId, {
playMyDataFlowAnimation: true,
played: true,
reverseLine: currentLine?.from !== currentNodeId,
myStepTime: stepTime
});
graphInstance.updateNode(currentNodeId, {
className: 'my-draw-path-animation'
});
};
关键要点:
- 单活动连线:一次只有一条连线显示动画
- 双向支持:
reverseLine指示相对于连线定义的流方向 - 节点高亮:当数据通过时添加 CSS 类到节点
- 自定义计时:通过 data 属性将动画持续时间传递给连线组件
双击节点选择
实现顺序节点选择以确定路径端点:
const onNodeClick = (node: RGNode, $event?: RGUserEvent) => {
console.log('onNodeClick:', node.id, graphInstance.getCheckedNode()?.id);
if (checkedNodeIdRef.current) {
if (checkedNodeIdRef.current === node.id) {
return;
}
// 第二次点击:计算并显示路径
showDataFlow(checkedNodeIdRef.current, node.id);
checkedNodeIdRef.current = '';
} else {
// 第一次点击:存储为起始节点
checkedNodeIdRef.current = node.id;
}
};
关键要点:
- 状态跟踪:使用引用跟踪第一个选定的节点
- 同节点检查:忽略对已选定节点的点击
- 自动清除:路径计算后重置
带有动画的自定义连线组件
MyLineContent 组件渲染动画数据流:
// MyLineContent.tsx(假设的实现)
const MyLineContent: React.FC<RGLineSlotProps & { onMyLineDetailClick: (line: RGLine) => void }> = ({
line,
onMyLineDetailClick
}) => {
const isAnimating = line.data?.playMyDataFlowAnimation;
const isReverse = line.data?.reverseLine;
const stepTime = line.data?.myStepTime || 2000;
return (
<g className={`my-line-content ${isAnimating ? 'animating' : ''}`}>
<line
x1={line.x} y1={line.y}
x2={line.x2} y2={line.y2}
stroke={line.data?.played ? '#4ade80' : '#94a3b8'}
strokeWidth={2}
/>
{isAnimating && (
<circle r={4} fill="#22c55e">
<animateMotion
dur={`${stepTime}ms`}
path={`M${line.x},${line.y} L${line.x2},${line.y2}`}
rotate="auto"
/>
</circle>
)}
{line.text && (
<text onClick={() => onMyLineDetailClick(line)}>
{line.text}
</text>
)}
</g>
);
};
自定义 SVG 标记
定义连线端点的箭头标记:
// MySvgDefs.tsx(假设的实现)
const MySvgDefs = () => (
<defs>
<marker
id="my-arrow-001"
markerWidth="10"
markerHeight="10"
refX="9"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M0,0 L0,6 L9,3 z" fill="#94a3b8" />
</marker>
<marker
id="my-arrow-001-start"
markerWidth="10"
markerHeight="10"
refX="0"
refY="3"
orient="auto"
markerUnits="strokeWidth"
>
<path d="M9,0 L9,6 L0,3 z" fill="#94a3b8" />
</marker>
</defs>
);
连线上的箭头配置
设置双向流指示的标记:
myJsonData.lines.forEach(line => {
line.endMarkerId = 'my-arrow-001';
line.startMarkerId = 'my-arrow-001-start';
line.showStartArrow = true;
line.showEndArrow = false;
});
带间距配置的树形布局
const graphOptions: RGOptions = {
defaultJunctionPoint: RGJunctionPoint.ltrb,
defaultNodeShape: RGNodeShape.circle,
defaultLineShape: RGLineShape.StandardCurve,
layout: {
layoutName: 'tree',
from: 'left',
treeNodeGapH: 100,
treeNodeGapV: 20
}
};
速度控制 UI
允许用户调整动画计时:
<SimpleUISelect
currentValue={pathAnimationSpeed}
data={[
{ value: 500, text: '0.5s' },
{ value: 1000, text: '1s' },
{ value: 2000, text: '2s' },
{ value: 3000, text: '3s' },
{ value: 5000, text: '5s' }
]}
onChange={(newValue: number) => {
setPathAnimationSpeed(newValue);
}}
/>
布局切换
动态布局更改支持:
<SimpleUISelect
currentValue={''}
data={[
{ value: 'center', text: 'Center Layout' },
{ value: 'tree', text: 'Tree Layout' }
]}
onChange={(newValue: string) => {
graphInstance.updateOptions({
layout: {
...graphOptions.layout,
layoutName: newValue
}
});
initializeGraph();
}}
/>
创意使用场景
网络流量监控: 可视化通过网络基础设施的数据包流。选择源服务器和目标服务器以查看数据包采用的实际路由,动画显示拥塞点。
供应链跟踪: 显示通过分销中心的产品移动。点击工厂和零售商以查看分销路径,带有货物进度动画跟踪。
流程工作流分析: 显示带有动画任务进度的业务流程流。选择开始和结束步骤以可视化完整工作流路径及当前状态指示器。
金融交易追踪: 追踪通过银行网络的资金流。选择发送方和接收方账户以可视化通过中介的交易路径。
疾病传播建模: 通过接触网络动画感染传播。选择零号病人和当前病例以可视化带有时间信息的传播链。