Code前端首页关于Code前端联系我们

Vue3 里怎么用 G6 做可视化图表开发?从集成到落地全流程解答

terry 2小时前 阅读数 4 #SEO
文章标签 Vue3;G6

前端开发中,可视化需求越来越常见,Vue3 作为主流框架,G6 是阿里系强大的图可视化引擎(能做流程图、拓扑图、知识图谱等),把两者结合做项目开发时,不少同学会碰到“咋初始化环境?数据咋联动?交互咋封装?”这类问题,今天用问答形式,把 Vue3 + G6 从基础集成到项目落地的关键环节讲透~

Vue3 项目里咋把 G6 跑起来?

想在 Vue3 里用 G6,第一步得让 G6 能在 Vue 组件里正常渲染画布,核心是借助 Vue 的生命周期 + G6 的初始化逻辑,步骤如下:

  • 安装依赖:先通过包管理工具装 G6,命令行执行 npm install @antv/g6(或 yarn/pnpm 对应命令)。
  • 组件内引入 + 初始化画布:在 Vue 组件里,用 onMounted 钩子(DOM 挂载后执行)创建 G6 的 Graph 实例,还要用 ref 拿到 DOM 容器。

举个最简代码示例:

<template>
  <!-- 给 G6 提供渲染容器 -->
  <div ref="graphContainer" class="graph-box"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import G6 from '@antv/g6' // 引入 G6
const graphContainer = ref(null) // 用 ref 绑定 DOM 容器
onMounted(() => {
  // 创建 G6 图实例,配置容器、宽高、默认样式
  const graph = new G6.Graph({
    container: graphContainer.value, // 绑定 DOM
    width: 800,
    height: 600,
    defaultNode: { // 全局默认节点样式
      type: 'circle',
      size: 30,
      color: '#5B8FF9'
    }
  })
  // 模拟可视化数据(节点 + 边)
  const data = {
    nodes: [{ id: 'node1', x: 100, y: 100 }], // 节点需有唯一 id 和坐标
    edges: [] // 边数据暂时为空
  }
  graph.data(data) // 把数据传入 G6
  graph.render() // 渲染图表
})
</script>
<style scoped>
.graph-box {
  border: 1px solid #eee;
}
</style>

关键点:

  • onMounted 里操作 DOM:G6 需要挂载到已存在的 DOM 节点上,所以必须等 Vue 把 <div ref="graphContainer"> 渲染到页面后,再初始化 G6。
  • ref 是“桥梁”:用 ref 拿到 DOM 元素,传给 G6 的 container 配置,让 G6 知道该把图表画到哪。

Vue3 的响应式数据变了,G6 图表咋跟着更?

Vue3 的 reactive/ref 能让数据变“响应式”(数据变了,页面自动更),但 G6 是独立的可视化引擎,得手动告诉它“数据变了,重新渲染”,核心思路是watch 监听数据变化,触发 G6 的更新逻辑

举个“点击按钮新增节点,G6 自动刷新”的例子:

<template>
  <button @click="addNode">新增节点</button>
  <div ref="graphContainer" class="graph-box"></div>
</template>
<script setup>
import { onMounted, ref, reactive, watch } from 'vue'
import G6 from '@antv/g6'
const graphContainer = ref(null)
let graph = null // 存 G6 Graph 实例,方便后续操作
// 用 reactive 包裹可视化数据,让 Vue 能监听变化
const graphData = reactive({
  nodes: [{ id: 'node1', x: 100, y: 100 }],
  edges: []
})
onMounted(() => {
  graph = new G6.Graph({ /* 配置和之前一样 */ })
  graph.data(graphData) // 把响应式数据传给 G6
  graph.render()
})
// 监听 graphData 变化,触发 G6 重新渲染
watch(graphData, (newData) => {
  graph.data(newData) // 更新 G6 内部数据
  graph.render() // 重新渲染图表
}, { deep: true }) // 深度监听对象内部变化
// 点击按钮时,往响应式数据里加节点
const addNode = () => {
  graphData.nodes.push({ 
    id: `node${Date.now()}`, // 用时间戳保证 id 唯一
    x: Math.random() * 600, // 随机坐标
    y: Math.random() * 400 
  })
}
</script>

进阶优化:
如果数据量很大(比如上千节点),全量 render 会很卡,这时候可以局部更新,比如只用 graph.updateItem(item, newModel) 修改单个节点,避免全图重绘。

G6 的交互事件,咋和 Vue3 组件通信?

G6 能监听“节点点击、边 hover、画布缩放”等事件,这些事件要和 Vue 组件通信(比如点击节点后,Vue 弹个弹窗、更新右侧面板),核心是在 G6 事件回调里,调用 Vue 的 emit 或修改响应式数据

场景 1:点击节点后,Vue 组件显示弹窗

直接在 G6 事件里修改响应式数据:

<template>
  <div ref="graphContainer"></div>
  <!-- 点击节点后,显示这个弹窗组件 -->
  <NodePopup :node="selectedNode" v-if="selectedNode" />
</template>
<script setup>
import { onMounted, ref, reactive } from 'vue'
import G6 from '@antv/g6'
import NodePopup from './NodePopup.vue'
const graphContainer = ref(null)
const selectedNode = ref(null) // 响应式数据,控制弹窗显示
let graph = null
onMounted(() => {
  graph = new G6.Graph({ /* 配置 */ })
  // 监听节点点击事件
  graph.on('node:click', (e) => {
    const nodeModel = e.item.getModel() // 获取节点数据
    selectedNode.value = nodeModel // 修改响应式数据,触发弹窗显示
  })
})
</script>

场景 2:子组件的 G6 事件,通知父组件

emit 把事件抛给父组件:

<!-- 子组件 GraphWrapper.vue -->
<template>
  <div ref="container"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import G6 from '@antv/g6'
const emit = defineEmits(['nodeClick']) // 声明要触发的事件
onMounted(() => {
  const graph = new G6.Graph({ /* 配置 */ })
  graph.on('node:click', (e) => {
    const nodeModel = e.item.getModel()
    emit('nodeClick', nodeModel) // 触发事件,把节点数据传给父组件
  })
})
</script>
<!-- 父组件使用 -->
<template>
  <GraphWrapper @nodeClick="handleNodeClick" />
</template>
<script setup>
const handleNodeClick = (node) => {
  console.log('父组件收到点击事件,节点数据:', node)
  // 这里可以弹弹窗、更新数据等
}
</script>

思路总结:G6 的事件回调是“入口”,在这个入口里,要么直接改 Vue 的响应式数据,要么用 emit 通知父组件——本质是让 Vue 感知到“交互发生了,需要更新 UI”。

数据量大时,Vue3 + G6 咋优化性能?

如果做“万级节点的拓扑图、超大流程图”,直接渲染会巨卡,得从 G6 自身优化 + Vue 响应式优化 两方面下手:

G6 侧优化

  • 开启 WebGL 渲染:G6 默认用 Canvas 渲染,数据量大时换成 WebGL(利用 GPU 加速),只需在初始化 Graph 时加配置:
    new G6.Graph({
      renderer: 'webgl', // 关键配置
      // 其他配置...
    })
  • 节点复用 / 缓存:如果很多节点样式一致,用 cacheNode 配置复用节点,减少重复绘制。
  • 分批加载数据:比如先渲染“可见区域的节点”,滚动时再加载其他节点(类似分页)。

Vue 侧优化

  • 减少响应式开销:如果某些数据只是给 G6 用,不需要 Vue 响应式,可以用 shallowReactive(浅响应式,只监听对象第一层变化)或普通对象,避免 Vue 给每个数据加 Proxy 代理。
  • 控制 watch 深度watch 监听的对象很复杂,把 deep: true 改成手动对比关键字段(比如只监听 nodes.length),减少不必要的触发。

举个“分批加载节点”的例子:

<script setup>
import { onMounted, ref, reactive } from 'vue'
import G6 from '@antv/g6'
const graphData = reactive({
  nodes: [],
  edges: []
})
let graph = null
onMounted(() => {
  graph = new G6.Graph({ 
    renderer: 'webgl', // 开 WebGL 加速
    /* 其他配置 */ 
  })
  // 先加载前 20 个节点
  loadBatch(0, 20)
})
// 分批加载函数
const loadBatch = (start, end) => {
  // 模拟从接口拿数据,这里循环生成节点
  for (let i = start; i < end; i++) {
    graphData.nodes.push({ 
      id: `node${i}`, 
      x: Math.random() * 800, 
      y: Math.random() * 600 
    })
  }
  graph.data(graphData)
  graph.render()
  // 滚动到底部时,加载下一批(简化逻辑,实际要判断滚动位置)
  window.addEventListener('scroll', () => {
    if (/* 滚动到底部 */) {
      loadBatch(end, end + 20) // 每次加 20 个
    }
  })
}
</script>

能搞个实际案例,比如流程图编辑器吗?

很多项目需要“拖拽节点画流程、编辑节点属性”这类功能,结合 Vue3 + G6,可拆成左侧节点库(拖拽)、中间画布(G6 渲染)、右侧属性面板(编辑)三部分:

步骤 1:左侧节点库(支持拖拽)

用 Vue 组件 + HTML5 Drag API,让用户能把“开始节点、结束节点、任务节点”拖到画布:

<!-- NodeLib.vue -->
<template>
  <div class="node-lib">
    <div 
      v-for="type in nodeTypes" 
      :key="type" 
      class="node-item" 
      v-draggable="type" <!-- 自定义拖拽指令 -->
    >
      {{ type }}
    </div>
  </div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
  nodeTypes: { type: Array, default: () => ['start', 'end', 'task'] }
})
// 自定义拖拽指令:让节点能被拖拽,传递节点类型
const vDraggable = {
  mounted(el, binding) {
    el.draggable = true
    el.addEventListener('dragstart', (e) => {
      e.dataTransfer.setData('text/plain', binding.value) // 存节点类型
    })
  }
}
</script>

步骤 2:中间画布(处理拖拽 + 渲染流程)

监听 drop 事件,把拖拽的节点加到 G6 画布;同时用 reactive 管理数据,让右侧面板能联动:

<!-- GraphCanvas.vue -->
<template>
  <div 
    ref="container" 
    class="canvas" 
    @drop="handleDrop" 
    @dragover.prevent <!-- 允许拖拽元素进入 -->
  ></div>
</template>
<script setup>
import { onMounted, ref, reactive } from 'vue'
import G6 from '@antv/g6'
import { useGraphData } from './useGraphData' // 抽离数据逻辑(复用性更好)
const container = ref(null)
const { graphData, graph } = useGraphData() // 假设这个组合式函数管理数据和 G6 实例
onMounted(() => {
  graph.value = new G6.Graph({
    container: container.value,
    width: '100%',
    height: 600,
    defaultNode: { /* 节点默认样式 */ }
  })
  graph.value.data(graphData)
  graph.value.render()
})
// 处理拖拽放下事件:把节点加到画布
const handleDrop = (e) => {
  e.preventDefault()
  const nodeType = e.dataTransfer.getData('text/plain') // 拿到拖拽的节点类型
  // 生成新节点,坐标是鼠标放下的位置
  graphData.nodes.push({
    id: `node_${Date.now()}`,
    type: nodeType,
    x: e.offsetX,
    y: e.offsetY
  })
  graph.value.data(graphData)
  graph.value.render()
}
</script>

步骤 3:右侧属性面板(编辑节点属性)

绑定响应式数据,修改节点标签、类型等属性:

<!-- NodeProps.vue -->
<template>
  <div class="props-panel" v-if="selectedNode">
    <input v-model="selectedNode.label" placeholder="节点标签" />
    <select v-model="selectedNode.type">
      <option value="start">开始</option>
      <option value="end">结束</option>
      <option value="task">任务</option>
    </select>
  </div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
  selectedNode: { type: Object, default: null }
})
// 因为 props 是响应式的(来自父组件的 reactive 数据),直接 v-model 就能触发更新
</script>

步骤 4:父组件整合三部分

把左侧库、画布、右侧面板拼起来,用响应式数据管理“选中节点”:

<template>
  <div class="editor-layout">
    <NodeLib :node-types="['start', 'end', 'task']" />
    <GraphCanvas @node-select="(node) => selectedNode = node" />
    <NodeProps :selected-node="selectedNode" />
  </div>
</template>
<script setup>
import { ref } from 'vue'
import NodeLib from './NodeLib.vue'
import GraphCanvas from './GraphCanvas.vue'
import NodeProps from './NodeProps.vue'
const selectedNode = ref(null) // 管理当前选中的节点
</script>
<style scoped>
.editor-layout { display: flex; }
.node-lib { width: 200px; border-right: 1px solid #eee; }
.canvas { flex: 1; border-right: 1px solid #eee; }
.props-panel { width: 200px; padding: 10px; }
</style>

Vue3 + G6 结合的核心逻辑

Vue3 负责数据驱动、组件化拆分、响应式更新;G6 负责图形渲染、复杂交互、可视化算法(比如力导布局、分层布局),两者结合时,要抓住几个关键:

  • 利用 onMounted 做 G6 初始化,保证 DOM 就绪;
  • watchemit 打通 Vue 响应式和 G6 渲染的联动;
  • 性能优化要“双管齐下”:G6 开 WebGL、分批加载;Vue 减少响应式代理开销;
  • 实际项目里,把复杂需求拆成“节点库、画布、属性面板”等组件,用 Vue 的组件化优势提可维护性。

掌握这些逻辑后,不管是做“运维拓扑图、知识图谱,还是流程图编辑器”,都能更高效落地~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门