我不负责拓扑组件是对的,就应该给大佬去维护,大佬参考github上issue中的讨论与代码思路,实现了线条上的流动效果,牛的牛的,我赶紧学习一波,下面代码实现都是初步思路,没有进行实现优化,如防抖优化、定时器优化等,根据项目需求自行优化即可。
vis-network中没有提供流动的配置,这个功能需要自己去实现。
思路是需要自己绘制一个canvas图层,和地图功能类似,在自定义canvas上实现流动功能。但是简单的实现流动功能还是不行的,拓扑图是支持平移、缩放等操作的,所以自定义canvas也是适配这些操作,在拓扑图平移、缩放等操作时,流动效果要一直在线条的正确位置才行。
主要思路如下:
在拓扑图渲染完成后,开始绘制自定义图层。关键代码如下:
import * as lineAnimation from './lineAnimation';
const network = new vis.Network(dom, { nodes, edges }, options);
lineAnimation.setAnimationCanvas(network); // 绘制自定义canvas
function setAnimationCanvas(container) {
let trafficCanvas = container.body.container.getElementsByClassName(
'networkTrafficCanvas',
)[0];
if (trafficCanvas === undefined) {
const { frame } = container.canvas;
trafficCanvas = document.createElement('canvas');
trafficCanvas.className = 'networkTrafficCanvas';
trafficCanvas.style.position = 'absolute';
trafficCanvas.style.top = 0;
trafficCanvas.style.left = 0;
trafficCanvas.style.zIndex = 1;
trafficCanvas.style.pointerEvents = 'none';
trafficCanvas.style.width = frame.style.width;
trafficCanvas.style.height = frame.style.height;
// trafficCanvas.width = frame.canvas.clientWidth;
// trafficCanvas.height = frame.canvas.clientHeight;
trafficCanvas.width = frame.canvas.width;
trafficCanvas.height = frame.canvas.height;
frame.appendChild(trafficCanvas);
}
}
lineAnimation.getAllEdges(network);
function getAllEdges(container) {
// 清除定时器、清空画布
removeCanvas(container);
// 绘制流动节点
lineAnimate(container);
}
其中绘制节点前的关键处理是找到所有的线条与要配置好自定义canvas,要与vis-network中自带canvas配置保持一致。
const canvas = container.body.container.getElementsByClassName(
'networkTrafficCanvas',
)[0];
const context = canvas.getContext('2d');
clearTimeout(timeout);
const scale = container.getScale();
const { edges } = container.body;
const points = Object.values(edges);
const { translation } = container.body.view;
// 设置缩放和位置
context.setTransform(1, 0, 0, 1, 0, 0); // 6个参数分别为水平缩放、水平倾斜、垂直倾斜、垂直缩放、水平移动、垂直移动
context.translate(translation.x, translation.y);
context.scale(scale, scale);
然后是要通过遍历绘制流动节点,单个节点的绘制如下。这里不展示调用drawLine函数的传参与线条数组遍历处理是因为我们的处理有局限,只能用于直线线条,好像还不支持曲线线条,这里的算法可以自己进行扩展创造。
export function drawLine(container, x, y, arg) {
const ctx = container.body.container
.getElementsByClassName('networkTrafficCanvas')[0]
.getContext('2d');
ctx.beginPath();
const endX = arg.from.x + arg.distanceX * (arg.speed + 0.2);
const endY = arg.from.y + arg.distanceY * (arg.speed + 0.2);
const grd = ctx.createLinearGradient(x, y, endX, endY);
grd.addColorStop(0, 'rgba(255,0,255,0)');
grd.addColorStop(1, 'rgba(88, 206, 255,1)');
ctx.moveTo(x, y);
ctx.lineTo(endX, endY);
ctx.lineCap = 'round';
ctx.lineWidth = 3;
ctx.strokeStyle = grd;
ctx.stroke();
}
最后是添加定时器,不断的重绘,通过改变流动节点的绘制位置来实现流动效果。这里不用重复调用lineAnimate函数,可以自己抽取一下代码进行优化,只需重复调用流动节点的重绘与canvas画布清除操作就行了,这里示例就懒得改了。
timeout = setTimeout(() => {
lineAnimate(container);
}, 100);
window.sessionStorage.setItem('timeout', timeout);
对于清除画布,这里被骗了一次,使用如下方法发现只能清除部分画布内容。
context.clearRect(
0,
0,
canvas.width,
canvas.height,
);
后来发现由于之前的context.translate()
操作让canvas的原点改变了,于是又写了一种清除画布思路,不知道还会不会产生问题。因为canvas画布是可以进行缩放的,要处理scale不是1的情况。
// 清除画布
if (scale >= 1) {
context.clearRect(
-Math.abs(translation.x),
-Math.abs(translation.y),
canvas.width,
canvas.height,
);
} else {
context.clearRect(
-Math.abs(translation.x) / scale,
-Math.abs(translation.y) / scale,
canvas.width / scale,
canvas.height / scale,
);
}
不写了不写了,思路很清晰了,这里只参考思路即可,具体实现可优化!
function canvasScale(container) {
// 清除定时器、清空画布
removeCanvas(container);
// 重新绘制节点动画
lineAnimate(container);
}
这里思路差不多,只是加了一层canvas大小的重新计算,都是可加防抖操作进行处理的,这里懒得加了。
export function canvasTranslate(container) {
// 清除定时器、清空画布
removeCanvas(container);
// 重新计算canvas大小
const canvas = container.body.container.getElementsByClassName(
'networkTrafficCanvas',
)[0];
const { frame } = container.canvas;
canvas.width = frame.canvas.width;
canvas.height = frame.canvas.height;
// 重新绘制节点动画
lineAnimate(container);
}