文章目录
项目需求
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:string
children: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实现可视化流程功能
发表评论 取消回复