前言:antvG6是一个简单、易用、完备的图可视化引擎,官网上有很多图表示例,大家可以选用不同的布局来进行开发。官网链接:G6 Demos - AntV

本篇主要讲解的是antvG6的节点和连线的动画、以及不同节点展示不同图标如何实现 

一、效果展示

二、实现步骤

1、先安装依赖

npm i --save @antv/g6@^4.8.17

2、html代码:

  <div class="app-container">
    <div id="container"> </div>
  </div>

3、引入G6

  import G6 from '@antv/g6';

 4、定义数据信息(节点和连线)

let topuData = ref({
    nodes: [
      {
        id: 'node1', //节点id,唯一值
        shape: 'hexagon', //图标类型
        label: 'tomcat_tomcat沙箱', //节点要显示的名称
      },
      {
        id: 'node2',
        shape: 'cloud',
        label: 'gitlab_gitlab沙箱',
      },
      {
        id: 'node3',
        shape: 'hexagon',
        label: 'gitlab_redis沙箱',
      },
      {
        id: 'node4',
        shape: 'hexagon',
        label: 'gitlab_postgresql沙箱',
      },
      {
        id: 'node5',
        shape: 'topu_other',
        label: 'mariadb沙箱',
      },
      {
        id: 'node6',
        shape: 'hexagon',
        label: 'mariadb沙箱',
      },
    ],
    edges: [
      {
        type: 'line', //连线类型,默认传就行
        source: 'node1', //连线起点
        target: 'node2', //连线终点
      },
      {
        type: 'line',
        source: 'node2',
        target: 'node4',
      },
      {
        type: 'line',
        source: 'node1',
        target: 'node3',
      },
      {
        type: 'line',
        source: 'node5',
        target: 'node6',
      },
    ],
  });

5、图表初始化函数:

let graph;
let container;
const createGraph = () => {
    container = document.getElementById('container');
    graph = new G6.Graph({
      container: 'container',
      width: container.offsetWidth, // 画布宽
      height: 700, // 画布高
      pixelRatio: 2,
      fitView: true,
      modes: {
        default: ['drag-canvas', 'drag-node'],
      },
      layout: {
        type: 'dagre',
        rankdir: 'LR',
        nodesep: 50,
        ranksep: 100,
        width: container.offsetWidth - 20, // 画布宽
        height: 500, // 画布高
      },
      defaultNode: {
        size: [50], // 设置每个节点的大小
        labelCfg: {
          style: {
            fontSize: 12,
          },
        },
      },
      defaultEdge: {
        size: 1,
        type: 'line',
        color: '#e2e2e2',
        style: {
          endArrow: {
            path: 'M 0,0 L 8,4 L 8,-4 Z',
            fill: '#e2e2e2',
          },
        },
      },
    });
    // 设置初始缩放和位移
    graph.zoom(1); // 设置缩放比例
    graph.moveTo(0, 0); // 移动视口,使 (0,0) 在左上角
    graph.data(topuData.value);

    graph.render();

    if (typeof window !== 'undefined')
      window.onresize = () => {
        if (!graph || graph.get('destroyed')) return;
        if (!container || !container.scrollWidth || !container.scrollHeight) return;
        graph.changeSize(container.scrollWidth, container.scrollHeight);
      };
  };

6、实现需求:不同节点展示不同图标

将以下代码写在初始化函数中,在graph.render()之前

graph.node(function (node) {
      if (node.shape == 'hexagon') {
        return {
          type: 'image',
          img: new URL('./testWeb/dashboard/topu_serve.png', import.meta.url).href,
          size: [30, 30],
          labelCfg: {
            style: {
              fontSize: 10,
            },
          },
        };
      }
      if (node.shape == 'cloud') {
        return {
          type: 'image',
          img: new URL('./testWeb/dashboard/topu_attacker.png', import.meta.url).href,
          size: [30, 30],
          labelCfg: {
            style: {
              fontSize: 10,
            },
          },
        };
      }
      return {
        type: 'image',
        img: new URL('./testWeb/dashboard/topu_other.png', import.meta.url).href,
        size: [30, 30],
        labelCfg: {
          style: {
            fontSize: 10,
          },
        },
      };
    });

用节点数据里面的shape来决定用哪个图标

7、注册连线动画

 也是写在初始化函数里面

    G6.registerEdge(
      'attack',
      {
        afterDraw(cfg, group) {
          const shape = group.get('children')[0]; // get the first shape in the group, it is the edge's path here=
          const startPoint = shape.getPoint(0); // the start position of the edge's path
          const circle = group.addShape('circle', {
            attrs: {
              x: startPoint.x,
              y: startPoint.y,
              fill: '#1890ff',
              r: 3,
            },
            name: 'circle-shape',
          });
          circle.animate(
            (ratio) => {
              // the operations in each frame. Ratio ranges from 0 to 1 indicating the prograss of the animation. Returns the modified configurations
              // get the position on the edge according to the ratio
              const tmpPoint = shape.getPoint(ratio);
              // returns the modified configurations here, x and y here
              return {
                x: tmpPoint.x,
                y: tmpPoint.y,
              };
            },
            {
              repeat: false, // Whether executes the animation repeatly
              duration: 4000, // the duration for executing once
            }
          );
        },
      },

      'line' // extend the built-in edge 'cubic'
    );

这里可以当做我们 注册了一个叫attack的连线动画

8、将节点和连线动画连起来

selectNodes是我们想要产生动画的节点,如果节点之前没有连线就意味着是另一条新的连线

  let selectNodes = ref(['node1', 'node2', 'node4', 'node5', 'node6']);
  const allNodesEvt = ref([]);
  const allEdgesEvt = ref([]);
  let selectEvt;
  let timeoutId;
//图片闪烁函数
  const flashFn = (item) => {
    const model = item.getModel();
    // 切换图片
    const originalImg = model.img;
    const flashingImg = new URL('./testWeb/dashboard/gj.png', import.meta.url).href; // 闪烁图片

    // 开始闪烁效果
    let isFlashing = true;
    let flashCount = 0; // 计数闪烁次数
    const maxFlashes = 6; // 最大闪烁次数(3次切换)

    const flashInterval = setInterval(() => {
      if (flashCount >= maxFlashes) {
        clearInterval(flashInterval);
        item.update({ img: flashingImg }); // 恢复原始图片
        return;
      }
      item.update({ img: isFlashing ? flashingImg : originalImg });
      isFlashing = !isFlashing;
      flashCount++;
    }, 500); // 每500毫秒切换一次
  };
  const edgeFlashFn = (a, b) => {
    // 遍历 allEdgesEvt,筛选符合条件的边
    for (let i = 0; i < allEdgesEvt.value.length; i++) {
      if (
        allEdgesEvt.value[i].getModel().source == a &&
        allEdgesEvt.value[i].getModel().target == b
      ) {
        allEdgesEvt.value[i].update({
          type: 'attack',
        });
      }
    }
  };
  onMounted(() => {
    container = document.getElementById('container');
    createGraph();
    nextTick(() => {
      allNodesEvt.value = graph.getNodes();
      allEdgesEvt.value = graph.getEdges();
      selectEvt = selectNodes.value
        .map((id) => {
          return allNodesEvt.value.find((node) => node._cfg.id == id);
        })
        .filter(Boolean);
      selectEvt.forEach((item, index) => {
        timeoutId = setTimeout(() => {
          flashFn(item);          
          edgeFlashFn(selectNodes.value[index], selectNodes.value[index + 1]);
        }, 4000 * index); // 延迟 4秒
      });
    });
  });

根本实现思路: 将selectNodes拿去和allNodesEvt里面去比对,如果id匹配上,就闪烁,然后延时4秒再闪烁下一个节点,因为给连线动画的duration就设计了4秒。具体讲解可以看注释。连线动画就切换type就可以,可以看到本来所有的edge的type都是line,在闪烁后就将该起点和终点间的连线的type设成attack。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部