JavaScript is required

模拟数据的流转路径

动画数据流路径 - 最短路径计算与顺序数据流动画

动画数据流路径可视化

功能概述

本示例演示了高级路径可视化,带有节点之间的动画数据流。当选择两个节点时,系统使用 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();
    }}
/>

创意使用场景

网络流量监控: 可视化通过网络基础设施的数据包流。选择源服务器和目标服务器以查看数据包采用的实际路由,动画显示拥塞点。

供应链跟踪: 显示通过分销中心的产品移动。点击工厂和零售商以查看分销路径,带有货物进度动画跟踪。

流程工作流分析: 显示带有动画任务进度的业务流程流。选择开始和结束步骤以可视化完整工作流路径及当前状态指示器。

金融交易追踪: 追踪通过银行网络的资金流。选择发送方和接收方账户以可视化通过中介的交易路径。

疾病传播建模: 通过接触网络动画感染传播。选择零号病人和当前病例以可视化带有时间信息的传播链。