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><divid="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 imgswitch (cfg.status) {case StatusType.ING:img = runImgbreakcase StatusType.ABNORMAL:img = abnormalImgbreakcase StatusType.END:img = successImgbreakdefault: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 + 12const centerY = -12const r = 10 // 边的倒角 radiusconst 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.collapsemarker.attr('symbol', icon)}},})// 初始化图形实例const initGraph = () => {const width = mountNodeRef.value.scrollWidthconst height = mountNodeRef.value.scrollHeightconst 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().collapsedtoRaw(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')) returnif (!mountNodeRef.value || !mountNodeRef.value.clientWidth || !mountNodeRef.value.clientHeight) returntoRaw(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 animationshape.animate(() => {index++if (index > 9) {index = 0}const res = {lineDash,lineDashOffset: -index,}return res},{repeat: true, // whether executes the animation repeatlyduration: 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},})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34855.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

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

References

Top Articles
Latest Posts
Article information

Author: Van Hayes

Last Updated:

Views: 5764

Rating: 4.6 / 5 (46 voted)

Reviews: 85% of readers found this page helpful

Author information

Name: Van Hayes

Birthday: 1994-06-07

Address: 2004 Kling Rapid, New Destiny, MT 64658-2367

Phone: +512425013758

Job: National Farming Director

Hobby: Reading, Polo, Genealogy, amateur radio, Scouting, Stand-up comedy, Cryptography

Introduction: My name is Van Hayes, I am a thankful, friendly, smiling, calm, powerful, fine, enthusiastic person who loves writing and wants to share my knowledge and understanding with you.