• Vue项目中使用AntV G6绘制自适应图谱


    Vue3.x中使用AntV G6绘制力导图实现自适应

    一、需求

    • 需求1:Vue3.x项目下使用AntV G6绘制力导图谱

    • 需求2:图谱节点为两个IP地址,节点间存在多条连线情况

    • 需求3:鼠标悬浮到节点上方时,高亮当前节点并出现tooltip气泡提示,展示相关信息

    • 需求4:点击图谱连线后需要高亮关联节点和当前连线

    • 需求5:图谱大小需要保证自适应(这里可以是根据窗口自适应,也可以是可调整大小的弹窗DOM内的自适应,后续会分开实现)

    二、实现

    1.安装AntV G6

    • 初始Vue3.x的项目可以看之前的博客:通过vite搭建Vue3.x项目,初始化项目后需要安装AntV G6的依赖,执行如下命令:
    npm install --save @antv/g6
    
    • 1

    2.初始化

    • 安装G6后在需要使用的页面进行导入,在创建好容器后利用G6进行实例化创建一个画布,后续的图谱绘制都将在画布内进行,目前为止需求1部分已经完成了,代码如下:
    <template>
      <div
        class="home"
        id="container"
      >
      div>
    template>
    
    <script setup>
    import G6 from '@antv/g6';
    import { onMounted, ref } from '@vue/runtime-core';
    // 图谱实例化对象
    const graph = ref(null);
    /* 挂载钩子 */
    onMounted(() => {
      initGraph();
    });
    /* 初始化图谱 */
    const initGraph = () => {
      graph.value = new G6.Graph({
        container: 'container', // String | HTMLElement,必须,容器 id 或容器本身
        width: document.getElementById('container').clientWidth, // Number,必须,图的宽度
        height: document.getElementById('container').clientHeight, // Number,必须,图的高度
      });
    }
    script>
    
    <style lang="less" scoped>
    .home {
      width: 100%;
      height: 100%;
      position: relative;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 为了方便后续步骤的进行,这里先造一些节点和连线的数据,大致仿照 G6官网示例中的数据,根据需求2在模拟数据的边集中添加两个节点存在多条连线的情况(实际场景下根据实际数据做处理即可),如下:
    <script setup>
    ...
    // 模拟数据
    const data = {
      // 点集
      nodes: [
        {
          id: '10.0.0.1', // String,该节点存在则必须,节点的唯一标识
          label: '节点1',
        },
        {
          id: '192.168.1.1',
          label: '节点2',
        },
        {
          id: '192.168.1.2',
          label: '节点3',
        },
        {
          id: '10.0.0.2',
          label: '节点4',
        },
      ],
      // 边集
      edges: [
        {
          source: '10.0.0.1', // String,必须,起始点 id
          target: '192.168.1.1', // String,必须,目标点 id
          label: '节点1->节点2',
        },
        {
          source: '10.0.0.1',
          target: '192.168.1.1',
          label: '节点1->节点2',
        },
        {
          source: '192.168.1.1',
          target: '10.0.0.1',
          label: '节点2->节点1',
        },
        {
          source: '192.168.1.1',
          target: '192.168.1.2',
          label: '节点2->节点3',
        },
        {
          source: '192.168.1.2',
          target: '192.168.1.2',
          label: '节点3->节点3',
        },
        {
          source: '192.168.1.2',
          target: '10.0.0.2',
          label: '节点3->节点4',
        },
        {
          source: '10.0.0.2',
          target: '192.168.1.2',
          label: '节点4->节点3',
        },
        {
          source: '10.0.0.2',
          target: '10.0.0.1',
          label: '节点4->节点1',
        },
      ],
    };
    ...
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    3.绘制图谱

    • 创建好模拟数据后继续完善画布的配置,这时可以参考官网的Api对画布、节点样式、连线样式、气泡提示tooltip进行一些配置

    • 这里需要注意的是,拖动画布或缩放画布时有概率出现**残影(拖影)**的问题,需要单独处理

    • 利用G6的工具函数将连线类型设置为贝塞尔曲线就能保证两个节点之间多条连线不会出现重叠的问题,这样就实现了需求2的部分

    • 需求3则利用G6实例化一个Tooltip后,再利用plugins字段配置到到画布内,当鼠标移入节点时会出现一个tooltip显示节点的IP地址(id),但是移出节点后tooltip并不会消失,需要在移出节点的事件中单独处理一次

    • 需求4可以给节点添加一个点击事件,根据点击的节点信息找到关联的两个节点的IP地址(id)并进行高亮处理

    • 代码如下:

    <script setup>
    ...
    /* 初始化图谱 */
    const initGraph = () => {
      // 节点添加提示框
      const tooltip = new G6.Tooltip({
        offsetX: 10, // tooltip 的 x 方向偏移值,需要考虑父级容器的 padding
        offsetY: 20, // tooltip 的 y 方向偏移值,需要考虑父级容器的 padding
        getContent(e) {
          const outDiv = document.createElement('div');
          outDiv.style.width = '180px';
          outDiv.innerHTML = `
              
    • 节点IP:${e.item.getModel().id}
    `
    ; return outDiv; }, itemTypes: ['node'] // 给node添加tooltip }); graph.value = new G6.Graph({ container: 'container', // String | HTMLElement,必须,容器 id 或容器本身 width: document.getElementById('container').clientWidth, // Number,必须,图的宽度 height: document.getElementById('container').clientHeight, // Number,必须,图的高度 fitCenter: true, // 是否平移图使其中心对齐到画布中心 plugins: [tooltip], // 添加tooltip // 画布配置 modes: { default: ['drag-canvas', 'zoom-canvas', 'drag-node'], // 允许拖拽画布、放缩画布、拖拽节点 }, // 基本配置 layout: { type: 'force', // 指定为力导向布局,此处可以使用force2,具体参考官方api preventOverlap: true, // 防止节点重叠 linkDistance: 180, // 指定边距离为150 nodeSpacing: 85, // 防止重叠时节点边缘间距的最小值 alpha: 1, // 当前的迭代收敛阈值 alphaDecay: 0.05, // 迭代阈值的衰减率,收敛阈值衰减为0时图谱不会迭代,节点之间不存在作用力。范围 [0, 1],迭代阈值为0时图谱可以一直保持迭代状态,即:图谱会一直保持动态力导状态 }, // 默认节点配置 defaultNode: { type: 'rect', // 节点类型 size: [120, 46], style: { fill: '#F6FCFE', // 填充色 stroke: '#2EA1FF', // 节点描边颜色 shadowColor: 'rgba(78,89,105,0.3)', // 阴影颜色 lineWidth: 1, // 描边宽度 radius: 3, // 圆角 shadowBlur: 2, // 阴影大小 }, // 文本配置 labelCfg: { position: 'center', style: { fontSize: 14, fill: '#0282FF', fontWeight: 400, }, }, }, // 默认边配置 defaultEdge: { type: 'loop', // 设置边默认为“自环”类型 style: { stroke: '#2EA1FF', lineWidth: 1, // 边添加箭头 endArrow: { path: G6.Arrow.triangle(4, 4, 2), d: 2, fill: '#2EA1FF', } }, labelCfg: { autoRotate: true, refY: 12, style: { fontSize: 12, fill: '#1D2129', } }, }, // 边状态样式 edgeStateStyles: { selected: { stroke: '#0282FF', shadowBlur: 0, 'text-shape': { fill: "#0282FF", fontWeight: 600, } } }, // 节点状态样式 nodeStateStyles: { // 选中后样式 selected: { fill: '#0282FF', // 填充色 stroke: '#0282FF', // 节点描边颜色 lineWidth: 1, // 描边宽度 shadowColor: 'rgba(0,102,210,0.5)', 'text-shape': { fill: "#ffffff" } }, // 悬浮后样式 active: { fill: '#CDEEFF', // 填充色 stroke: '#2EA1FF', // 节点描边颜色 lineWidth: 1, // 描边宽度 shadowColor: 'rgba(78,89,105,0.3)', 'text-shape': { fill: "#0282FF", fontWeight: 500, } } } }); // 添加节点间多条连线,贝塞尔曲线 G6.Util.processParallelEdges(data.edges, 65); // 解决拖动产生残影问题 graph.value.get('canvas').set('localRefresh', false); // 边添加点击事件 graph.value.on('edge:click', (evt) => { const { item } = evt; graph.value.getEdges().forEach((edge) => { graph.value.clearItemStates(edge); }); graph.value.getNodes().forEach((node) => { graph.value.clearItemStates(node); }); graph.value.setItemState(item, 'selected', true); // 高亮关联节点 graph.value.setItemState(item['_cfg'].source, 'selected', true); graph.value.setItemState(item['_cfg'].target, 'selected', true); }); // 节点悬浮高亮 graph.value.on('node:mouseover', (e) => { graph.value.setItemState(e.item, 'active', true); }); // 节点鼠标移出后使tooltip消失并取消节点悬浮高亮 graph.value.on('node:mouseout', (e) => { document.getElementsByClassName('g6-component-tooltip')[0].style.display = 'none'; graph.value.setItemState(e.item, 'active', false); }); graph.value.data(data); graph.value.render(); }
    script> <style lang="less" scoped> .home { width: 100%; height: 100%; position: relative; } style> <style lang="less"> // g6的tooltip气泡提示样式需要写进不含scoped的style中,否则不生效 .g6-component-tooltip { background-color: rgba(0, 0, 0, 0.7); padding: 7px 17px; box-shadow: none; } .g6-component-tooltip ul li { font-size: 12px; font-weight: 400; color: #ffffff; line-height: 19px; } style>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172

    4.解决自适应

    • 需求5要分成两种情况来说:

    • 第一种情况是窗口大小改变时影响到画布,可以直接通过监听窗口windowresize来对画布进行自适应的操作,代码如下:

    <script setup>
    ...
      	graph.value.render();
        // 监听window的resize事件
      	window.addEventListener('resize', () => {
        // 利用changeSize()事件使画布自适应
        graph.value.changeSize(document.getElementById('container').clientWidth, document.getElementById('container').clientHeight);
        // 将图谱移动到画布正中间
        graph.value.fitCenter();
    ...
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 第二种情况就稍微复杂一些,笔者实际中的业务是在一个可以拖动改变大小的弹窗中实现画布自适应;同理这里可以是收起导航菜单影响到画布的DOM亦或是改变布局等方式影响了画布的DOM大小发生改变,总之就是画布所在的DOM元素大小发生改变时的自适应,因为不是window的大小改变,所以不能使用第一种监听windowresize方式来处理,笔者这里使用的是一个jselement-resize-detector用来监听画布的DOM大小是否改变,步骤如下:

    • 安装element-resize-detector库,详情:https://www.npmjs.com/package/element-resize-detector

    npm install --save element-resize-detector
    
    • 1
    • 使用element-resize-detector监听目标DOM大小是否发生改变
    <script setup>
    // 引入
    import elementResizeDetectorMaker from 'element-resize-detector';
    ...
    	graph.value.render();
     	 // 使用
      	let erd = elementResizeDetectorMaker();
      	// 监听目标DOM的大小是否改变,以container为例
      	erd.listenTo(document.getElementById("container"), (element) => {
            let width = element.clientWidth;
            let height = element.clientHeight;
            // 利用changeSize()事件使画布自适应
            graph.value.changeSize(width, height);
            // 将图谱移动到画布正中间
            graph.value.fitCenter();
      	});
    ...
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    5.资源释放

    • 最后在界面卸载时需要释放一次资源,将画布资源进行释放,代码如下:
    <script setup>
    ...
    /* 卸载钩子 */
    onUnmounted(() => {
      if (graph.value) {
        // 清空数据(可省略)
        graph.value.clear();
        // 销毁画布
        graph.value.destroy();
        // 将画布实例对象置空
        graph.value = null;
      }
    });
    ...
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    6.效果展示及demo地址

  • 相关阅读:
    面向6G的无蜂窝大规模MIMO无线传输技术
    中国设备工程杂志中国设备工程杂志社中国设备工程编辑部2022年第18期目录
    [Vue3] 滚动条自动滚动到底部
    [NPUCTF2020]Mersenne twister
    模块化方案:CommonJs & ESModule
    麻了,别再为难软件测试员了
    nginx使用详解
    2023年四川省网络与信息安全技能大赛初赛 个人赛 Writeup
    学习笔记:机器学习之支持向量机(七、求核函数)
    h5和微信小程序实现拍照功能(其中h5暂时无法调用闪光灯)
  • 原文地址:https://blog.csdn.net/baoyin0822/article/details/128181019