vis-network升级功能一(线条流动效果)

钱和平
2023-12-01

前言

我不负责拓扑组件是对的,就应该给大佬去维护,大佬参考github上issue中的讨论与代码思路,实现了线条上的流动效果,牛的牛的,我赶紧学习一波,下面代码实现都是初步思路,没有进行实现优化,如防抖优化、定时器优化等,根据项目需求自行优化即可。

vis-network中没有提供流动的配置,这个功能需要自己去实现。

实现思路

思路是需要自己绘制一个canvas图层,和地图功能类似,在自定义canvas上实现流动功能。但是简单的实现流动功能还是不行的,拓扑图是支持平移、缩放等操作的,所以自定义canvas也是适配这些操作,在拓扑图平移、缩放等操作时,流动效果要一直在线条的正确位置才行。

主要思路如下:

  1. 自定义canvas图层,插入vis-network的canvas图层中,当作子canvas,子canvas的大小与位置要与父canvas完全一致,两者是重叠关系。
  2. 为拓扑图中的每一个线条增加流动效果,并且是无限循环的。
  3. 当拓扑图触发 draging,zoom,resize等事件时,流动效果的位置也要与线条位置保持一致。

自定义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);
}

拓扑图触发resize

这里思路差不多,只是加了一层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);
}
 类似资料: