vue3项目使用@antv/g6实现可视化流程功能 (2024)

文章目录

  • 项目需求
  • 一、需要解决的问题
  • 二、初步使用
    • 1.动态数据-组件封装(解决拖拽会留下痕迹的问题,引用图片,在节点右上角渲染图标,实现,事现旋转动画,达到loading效果)
    • 2.文本太长,超出部分显示(...),如下函数返回新的文本和文本宽度
    • 3.根据某些字段的值给线增加动画,并在线上渲染文本
    • 4.自定义按钮,实现局部区域点击
    • 5.开启自带的操作栏
    • 5.鼠标悬浮展示数据

项目需求

antv/G6 - 4.8.24 版本地址
实现一个流程图,根据不同阶段、不同功能、不同状态来显示图形
1、线,需要根据状态展示不同的颜色和动画效果
2、节点部分区域需要点击功能
3、文本太长需要显示…(三个点)
4、不同状态的节点需要使用不同icon(svg图片)
5、根据需求,采用G6缩进树的布局方式,缩进树地址
6、鼠标悬浮需要展示详情数据
7、需要操作栏快速缩放还原比例

一、需要解决的问题

1、4xx版本,节点拖拽会留下痕迹,由于我画布是白色的底,所以使用官方提供的解决方案,就是在节点最底层画一个白色的矩形(图形后画的会覆盖先画)

二、初步使用

1.动态数据-组件封装(解决拖拽会留下痕迹的问题,引用图片,在节点右上角渲染图标,实现,事现旋转动画,达到loading效果)

由于旋转会绕着节点的中心点,所以需要将节点的中心点移到右上角图形的中心
假设:右上角图形中心点距离顶部和在右边的距离是12,则中心点设置为(-w + 12,-12)
vue3代码如下(示例):

<template> <div id="mountNode" ref="mountNodeRef" ></div></template><script setup lang="ts">import { ref,reactive } from 'vue'import G6 from '@antv/g6'import runImg from '@/assets/run.svg'const treeGraph = reactive<any>({ graph: {},})interface DataType{id:stringchildren:DataType[]}const drawerImg= (cfg: any, group: any, w: number, h: number) => { // 图片 let img switch (cfg.status) { case StatusType.ING: img = runImg break case StatusType.ABNORMAL: img = abnormalImg break case StatusType.END: img = successImg break default: img = waitImg } const image = group.addShape('image', { attrs: { x: -8, y: -8, width: 16, height: 16, img, // import 引入的图片 }, name: 'image-shape', }) // 旋转动画 if (cfg.status === StatusType.ING) { image.animate( (ratio: any) => { // 每一帧的操作,入参 ratio:这一帧的比例值(Number)。返回值:这一帧需要变化的参数集(Object)。 // 旋转通过矩阵来实现 // 当前矩阵(矩阵文档中有描述) const matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1] // 目标矩阵 const toMatrix = G6.Util.transform(matrix, [['r', ratio * Math.PI * 2]]) // 返回这一帧需要的参数集,本例中只有目标矩阵 return { matrix: toMatrix, } }, { repeat: true, // 动画重复 duration: 3000, easing: 'easeLinear', } ) }}// 注册自定义节点G6.registerNode('card-node', { draw: function drawShape(cfg: any, group) { // 获取初始化时defaultNode设置的宽高 const w = cfg.size[0] const h = cfg.size[1] // 中心点坐标(默认是节点左上角),这里设置成图形中心(影响图像旋转等功能) // const centerX = -w / 2 // const centerY = -h / 2 // 中心点坐标(默认是节点左上角),这里设置成节点右上角距离顶部和右边12的位置 const centerX = -w + 12 const centerY = -12 const r = 10 // 边的倒角 radius const color = '#004CFE' // 文本颜色 const baseColor = '#001043' // 文本颜色 const backgroundColor = 'rgba(0,76,254,0.2)' // 填充颜色 // 主图,容器矩形,画白色容器矩形,防止拖拽产生的痕迹 const shape = group.addShape('rect', { attrs: { x: centerX, y: centerY, width: w, height: h, shadowColor: 'rgba(0,0,0,0.16)', shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 4, radius: r, // 4个角都设置圆角 fill: '#fff', }, name: 'main-box', // 必须,用来操作图行,需要唯一 // draggable: true, // 只用为true,图形才可以拖拽,同时需要配置modes中开启拖拽功能,如果上层重叠有图形,重叠的图形也需要开启该属性}) // 之后添加的图形会默认覆盖在之前添加的图形上面// 新增图形,矩形头部group.addShape('rect', { attrs: { x: centerX, y: centerY, width: w, height: 28, fill: baseColor , radius: [r, r, 0, 0], // 左上和右上设置圆角,左下和右下不变 }, name: 'header-box', // draggable: true,}) // 矩形头部文本group.addShape('text', { attrs: { x: centerX + 8, y: centerY + 14, lineHeight: 20, text: cfg.text, // 节点数据text字段 fill: color, textBaseline: 'middle', // 文本垂直居中 }, name: 'title', // draggable: true,}) // 右上角图标 drawerImg(cfg, group, w, h) // 有子数据的矩形添加收起/展开的按钮 cfg.children && group.addShape('marker', { attrs: { x: 12, y: h / 2 - 12, r: 6, cursor: 'pointer', symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,// G6 自带的标记 stroke: '#666', lineWidth: 1, fill: '#fff', }, name: 'collapse-icon', }) return shape }, setState(name, value, item: any) { // 开启缩进树的节点按钮,响应节点点击事件,展开、收起子节点树 if (name === 'collapsed') { const marker = item.get('group').find((ele: any) => ele.get('name') === 'collapse-icon') const icon = value ? G6.Marker.expand : G6.Marker.collapse marker.attr('symbol', icon) } },})// 初始化图形实例const initGraph = () => { const width = mountNodeRef.value.scrollWidth const height = mountNodeRef.value.scrollHeight const graph = new G6.TreeGraph({ container: 'mountNode', // String | HTMLElement,必须,容器 id 或容器本身 width, // Number,必须,图的宽度 height, // Number,必须,图的高度 plugins: [tooltip, toolbar], // 添加tooltip // 画布配置 modes: { default: ['drag-canvas', 'zoom-canvas'], // 允许拖拽画布、放缩画布(没有添加节点拖拽) }, defaultNode: { type: 'card-node',// 自定义node节点 size: [132, 98], }, defaultEdge: { type: 'cubic-horizontal', style: { endArrow: true, }, }, // 基本布局配置 layout: { type: 'indented', // 布局模式(缩进树布局) direction: 'LR', // 布局方向 dropCap: false, indent: 260, // 图形水平间距 getHeight: () => { return 100 // 图形垂直间距 }, }, }) toRaw(treeGraph).graph = graph}onMounted(() => { if (mountNodeRef.value) { // 初始化图形,渲染需要在异步数据更新之后 initGraph() }})// 模拟数据// const data = {// id: 'A',// text:'我是文本超级长的文本给个省略号',// status:'ING',// children: [// {// id: 'A1',// text:'我是文本',// status:'ING',// children: [{ id: 'A11', text:'我是文本', }, { id: 'A12', text:'我是文本', }],// },// {// id: 'A2',// text:'我是文本',// children: [// {// id: 'A21',// text:'我是文本',// children: [{ id: 'A211', text:'我是文本', }, { id: 'A212', text:'我是文本', }],// },// {// id: 'A22',// text:'我是文本',// },// ],// },// ],// };// 监听数据变化渲染图形watch( () => props.data, (value) => { toRaw(treeGraph).graph.data(value) toRaw(treeGraph).graph.render() // 渲染图 toRaw(treeGraph).graph.fitView() // 布局// 监听节点点击 toRaw(treeGraph).graph.on('node:click', (e: any) => { /** * 控制展开收起的小图标事件 * collapse-icon 是创建图形的name,将点击响应确定在一定的范围 */ if (e.target.get('name') === 'collapse-icon') { e.item.getModel().collapsed = !e.item.getModel().collapsed toRaw(treeGraph).graph.setItemState(e.item, 'collapsed', e.item.getModel().collapsed) toRaw(treeGraph).graph.layout() } }) // 可视窗口变化,更新视图 if (typeof window !== 'undefined') { window.onresize = () => { if (!toRaw(treeGraph).graph || toRaw(treeGraph).graph.get('destroyed')) return if (!mountNodeRef.value || !mountNodeRef.value.clientWidth || !mountNodeRef.value.clientHeight) return toRaw(treeGraph).graph.changeSize(mountNodeRef.value.clientWidth, mountNodeRef.value.clientHeight) toRaw(treeGraph).graph.fitView() } } })</script>

2.文本太长,超出部分显示(…),如下函数返回新的文本和文本宽度

// 计算文本宽度,和超出显示三个点const truncateText = (text: string, maxWidth: number, fontSize = 12, fontFace = 'Microsoft YaHei') => { // 创建一个临时canvas来测量文本宽度 const tempCanvas = document.createElement('canvas') const tempCtx = tempCanvas.getContext('2d')! tempCtx.font = fontSize + 'px ' + fontFace // 计算文本宽度 let textWidth = tempCtx.measureText(text).width // 如果文本宽度超出最大宽度,则截断并添加省略号 if (textWidth > maxWidth) { // 尝试去除一个字符,然后重新测量,直到文本宽度小于或等于最大宽度 while (textWidth > maxWidth) { text = text.slice(0, -1) // 移除最后一个字符并添加省略号 textWidth = tempCtx.measureText(text).width } return { width: textWidth, text: text + '...', // 移除最后一个字符并添加省略号 } } else { return { width: textWidth, text, } }}// 用例,修改上文 - 矩形头部文本G6.registerNode('card-node', {draw: function drawShape(cfg: any, group) { // ...其他配置 // 矩形头部文本 const { text } = truncateText(cfg.text, 100) group.addShape('text', { attrs: { x: centerX + 8, y: centerY + 14, lineHeight: 20, // text: cfg.text, // 节点数据text字段 text: text, fill: color, textBaseline: 'middle', // 文本垂直居中 }, name: 'title', // draggable: true,})}})

3.根据某些字段的值给线增加动画,并在线上渲染文本

需要修改defaultEdge配置,代码如下(示例):

const lineDash = [4, 2, 1, 2]G6.registerEdge( 'line-dash', { afterDraw(cfg: any, group: any) { // 获取图形组中的第一个图形,在这里就是边的路径图形 const shape = group.get('children')[0] // 由于没有直接的线数据,需要根据线上的源节点或者目标节点的id来获取,节点的数据 // 这里获取目标节点的模型数据 const targetModel = toRaw(treeGraph).graph.findById(cfg.target).getModel() if (targetModel.status && targetModel.status === 'ING') {// 增加动画 let index = 0 // Define the animation shape.animate( () => { index++ if (index > 9) { index = 0 } const res = { lineDash, lineDashOffset: -index, } return res }, { repeat: true, // whether executes the animation repeatly duration: 3000, // the duration for executing once } ) } }, }, 'cubic-horizontal' // extend the built-in edge 'cubic-horizontal')const initGraph = () => {const graph = new G6.TreeGraph({// ...其他配置defaultEdge: { type: 'line-dash',// 自定义线段 style: { lineWidth: 2, stroke: '#bae7ff', endArrow: true, }, // 线上文本的样式配置 labelCfg: { autoRotate: true, style: { fill: '#1890ff', fontSize: 14, background: { fill: '#ffffff', padding: [2, 2, 2, 2], radius: 2, }, }, }, },})}

线上配置文本需要在graph.render() 之前,修改上文中的watch

watch( () => props.data, (value) => { // 设置各个边样式及其他配置,以及在各个状态下节点的 KeyShape 的样式。 toRaw(treeGraph).graph.edge(function (edge: any) { const targetItem = toRaw(treeGraph) .graph.findById(edge.target as string) .getModel() const config: any = {} // 存在流量 if (targetItem.status) { if (targetItem.status === 'ERROR') { config.style = { stroke: 'red', } } config.label = targetItem.status } return config }) // ...其他配置 toRaw(treeGraph).graph.render() // 渲染图 })

4.自定义按钮,实现局部区域点击

1、按钮由一个矩形节点和文本节点组成,上文G6.registerNode增加配置
2、节点点击,锁定局部区域,graph的node:click事件

G6.registerNode('card-node', { draw: function drawShape(cfg: any, group) { // ...其他配置 // 按钮矩形区域 group.addShape('rect', { attrs: { x: -52, y: h - 38, width: 64, height: 26, fill: 'rgba(35,131,228,0.1)', radius: [4, 0, r, 0], cursor: 'pointer', }, name: 'btn', draggable: true, }) group.addShape('text', { attrs: { x: -20, y: h - 25, text: '查看详情', fill: '#2383E4', fontSize: 12, fontFamily: textFontFace, textAlign: 'center', // 文本水平居中 textBaseline: 'middle', // 文本垂直居中 cursor: 'pointer', }, name: 'btn-text', draggable: true, }) }})toRaw(treeGraph).graph.on('node:click', (e: any) => { // 点击了查看详情 if (e.target.get('name') === 'btn-text' || e.target.get('name') === 'btn') { const model = e.item.getModel() // 获取数据 console.log(model) }})

5.开启自带的操作栏

const toolbar = new G6.ToolBar()const graph = new G6.TreeGraph({ plugins: [..., toolbar], // 添加tooltip})

5.鼠标悬浮展示数据

const graph = new G6.TreeGraph({ plugins: [..., tooltip], // 添加tooltip})const tooltip = new G6.Tooltip({ offsetX: 10, offsetY: 10, // 允许出现 tooltip 的 item 类型 itemTypes: ['node'], shouldBegin: (e: any) => { const model = e.item.getModel() const type = e.item.getType() // if (type === 'node' && model.id !== 'custom') { // return true // } return false }, // 自定义 tooltip 内容 getContent: (e: any) => { const model = e.item.getModel() let outDiv = document.createElement('div') outDiv.style.width = 'fit-content' outDiv.innerHTML = ` <h4 style="font-size:16px;font-weight:bold;margin-bottom:6px">节点详情</h4> <ul style="font-size:14px;"> <li>type: ${model.nodeType}</li> <li>code: ${model.code}</li> <li>name: ${model.name}</li> </ul>` return outDiv },})
vue3项目使用@antv/g6实现可视化流程功能 (2024)

References

Top Articles
Latest Posts
Article information

Author: Catherine Tremblay

Last Updated:

Views: 5766

Rating: 4.7 / 5 (47 voted)

Reviews: 86% of readers found this page helpful

Author information

Name: Catherine Tremblay

Birthday: 1999-09-23

Address: Suite 461 73643 Sherril Loaf, Dickinsonland, AZ 47941-2379

Phone: +2678139151039

Job: International Administration Supervisor

Hobby: Dowsing, Snowboarding, Rowing, Beekeeping, Calligraphy, Shooting, Air sports

Introduction: My name is Catherine Tremblay, I am a precious, perfect, tasty, enthusiastic, inexpensive, vast, kind person who loves writing and wants to share my knowledge and understanding with you.