AntvG6 树图需要, 悬浮节点node
增加弹窗提示节点信息,需要点击节点node
请求下游节点。原本打算用jsmind
实现,但是jsmind不知道怎么加事件监听。
踩的坑:
- 节点id一定要为字符串
string
类型,我用接口返回的id为数字number
类型显示不出来 fitView()
方法,会适应视图,因为我只有几个节点,导致节点渲染特别大,所以注释了,然后就自己定义节点的坐标
渲染出来的效果
知识点:
- 根据接口返回树图
- 自定义节点样式坐标位置 (圆角)
- 组件监听鼠标事件 进入和点击 (mouseenter和click) 增加tooltip
- 组件销毁提升性能
使用antvG6 渲染树图
不想看这块代码的 可以直接看官方demo 怎么渲染树图
1.1 加载antvG6 树图一些配置公共样式 node节点、文本, 没有用fitView()
方法 改了x,y
坐标
// treeG6Config.js
import G6 from '@antv/g6'
const CustomNode = {
draw: function drawShape(cfg, group) {
const r = 12
const color = '#5B8FF9'
const w = cfg.size[0]
const h = cfg.size[1]
const shape = group.addShape('rect', {
attrs: {
x: w / 2 + 310,
y: h / 2,
width: w,
height: h,
stroke: color,
radius: r,
fill: '#fff',
},
name: 'main-box',
draggable: true,
})
group.addShape('text', {
attrs: {
textBaseline: 'middle',
x: w / 2 + 320,
y: h + 10,
lineHeight: 20,
cursor: 'pointer',
// 标签显示表名
text: cfg.tableName,
fontSize: 14,
fill: '#333',
},
name: 'title',
})
if (cfg.children) {
group.addShape('marker', {
attrs: {
x: w + w / 2 + 310,
y: h,
r: 6,
cursor: 'pointer',
symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
lineWidth: 1,
fill: '#fff',
},
name: 'collapse-icon',
})
}
return shape
},
setState(name, value, item) {
if (name === 'collapsed') {
const marker = item
.get('group')
.find(ele => ele.get('name') === 'collapse-icon')
const icon = value ? G6.Marker.expand : G6.Marker.collapse
marker.attr('symbol', icon)
}
},
}
// 标签文本过长换行
export function addNewlines(str, charsPerLine) {
let result = ''
for (let i = 0; i < str.length; i += charsPerLine) {
result += str.substr(i, charsPerLine) + '\n'
}
return result
}
// 递归转换后台返回的数据 把tableId转为id字段
export const convertData = data => {
if (data?.length) {
data.forEach(d => {
d.id = String(d.tableId)
d.tableName = addNewlines(d.tableName, 12)
if (d.children && d.children.length) {
convertData(d.children)
}
})
}
}
export default CustomNode
1.2 onMounted
时候渲染dom
import {
onMounted,
reactive,
watch,
ref,
watchEffect,
nextTick,
onBeforeUnmount,
} from 'vue'
import G6 from '@antv/g6'
import treeG6Config, { addNewlines, convertData } from '@/utils/treeG6Config'
// 加载公共配置 设置节点 文本
G6.registerNode('card-node', treeG6Config)
const chartDataObj = ref<any>(null) // 是一个对象啊
// 渲染dom
const initialRender = () => {
const container = document.getElementById('container')
const width = container.scrollWidth || 1024
const height = container.scrollHeight || 300
graphInstance = new G6.TreeGraph({
container: 'container',
width,
height,
plugins: [tooltip],
modes: {
default: ['drag-canvas'],
},
defaultNode: {
type: 'card-node',
size: [100, 40],
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
endArrow: true,
},
},
layout: {
type: 'indented',
direction: 'LR',
dropCap: false,
indent: 200,
getHeight: () => {
return 60
},
},
})
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graphInstance || graphInstance.get('destroyed')) return
if (!container || !container.scrollWidth || !container.scrollHeight)
return
graphInstance.changeSize(container.scrollWidth, container.scrollHeight)
}
}
onMounted(() => {
// 只是渲染dom 没有数据
initialRender()
})
1.3请求接口 渲染树图
const props = defineProps({
row: {
type: Object,
default: () => {},
},
})
// 渲染树图
const renderTree = () => {
if (chartDataObj.value) {
graphInstance.data(chartDataObj.value)
graphInstance.render()
// graphInstance.fitView();
}
}
watchEffect(() => {
if (props.row?.id) {
// 请求接口
getBuildBlood({
tableId: props.row.id,
type: branchDirectionType.value,
}).then(res => {
// 树图需要id字段 必须是string 类型
rowCopy.id = String(rowCopy.id)
rowCopy.dataBaseName = rowCopy.databases
let data = {
...rowCopy,
type: 0,
children: [],
tableName: addNewlines(rowCopy.tableName, 8),
}
// 因为接口返回的数据有[null]有这种情况
if (res.data?.length && res.data[0] !== null) {
convertData(res.data)
data.children = res.data
} else {
data.children = []
}
chartDataObj.value = data
nextTick(() => {
renderTree()
})
})
}
})
增加节点tooltip
Tooltip
插件主要用于在节点和边上展示一些辅助信息.
2.1 定义tooltip
插件
const tooltip = new G6.Tooltip({
offsetX: 10,
offsetY: 20,
trigger: 'mouseenter',
getContent(e) {
const outDiv = document.createElement('div')
outDiv.style.width = '270px'
outDiv.innerHTML = `
<ul>
<li>
<span style="width: 120px; display: inline-block;">表名:</span> ${
e.item.getModel().tableName
}</li>
<li><span style="width: 120px; display: inline-block;">数据库名:</span> ${
e.item.getModel().dataBaseName || '-'
}</li>
<li><span style="width: 120px; display: inline-block;">创建人:</span> ${
e.item.getModel().creator || '-'
}</li>
<li><span style="width: 120px; display: inline-block;">创建时间信息:</span> ${
e.item.getModel().createTime || '-'
}</li>
</ul>`
return outDiv
},
itemTypes: ['node'],
})
2.2 加载tooltip插件
graphInstance = new G6.TreeGraph({
container: 'container',
// 加载插件
plugins: [tooltip],
...
})
插件配置看官网
增加事件监听
通过graphInstance.on('node:click', e => {})
增加事件监听,e.item._cfg
可以获取节点的数据信息
graphInstance.on('node:click', e => {
if (e.target.get('name') === 'collapse-icon') {
e.item.getModel().collapsed = !e.item.getModel().collapsed
graphInstance.setItemState(
e.item,
'collapsed',
e.item.getModel().collapsed
)
graphInstance.layout()
}
const item = e.item // 被操作的节点 item
const target = e.target // 被操作的具体图形
// 点击节点 看有没有下级节点 有就新增 没有就提示
if (!item?._cfg?.model.children || !item?._cfg?.model.children?.length) {
console.log('yellow----', item)
let tableId = Number(item?._cfg?.id)
getBuildBlood({
tableId: tableId,
type: branchDirectionType.value,
}).then(res => {
// let chartDataCopy = JSON.parse(JSON.stringify(chartDataObj.value))
if (res.data?.length && res.data[0] !== null) {
convertData(res.data)
// todo 后台没有返回有下游表的 可能有问题
setChildren(chartDataObj.value.children, item, res.data)
// 还是不要这样改 不然就会指着上级做下游
// if (branchDirectionType.value === 2) {
// setChildren(chartDataObj.value.children, item, res.data)
// } else {
// chartDataObj.value = {
// ...res.data,
// children: chartDataCopy,
// }
// }
} else {
proxy.$message({
message: `表名${item?._cfg?.model.tableName}没有${
branchDirectionType.value === 1 ? '上游' : '下游'
}表信息`,
duration: 1000,
type: 'warning',
})
return
}
nextTick(() => {
renderTree()
})
})
}
})
完整代码
<!--
* @Date: 2024-04-15 14:26:26
* @LastEditors: mingongze (andersonmingz@gmail.com)
* @LastEditTime: 2024-04-16 17:05:34
* @FilePath: /datagov_web/src/components/views/metadata/build/buildBlood.vue
-->
<script setup lang="ts">
import {
onMounted,
reactive,
watch,
ref,
watchEffect,
nextTick,
onBeforeUnmount,
} from 'vue'
import { getBuildBlood } from '@/api/metaData'
import G6 from '@antv/g6'
import treeG6Config, { addNewlines, convertData } from '@/utils/treeG6Config'
const props = defineProps({
row: {
type: Object,
default: () => {},
},
})
const branchDirectionType = ref(2)
let rowCopy = JSON.parse(JSON.stringify(props.row))
const { proxy } = getCurrentInstance()
// 查询下游表名 找到了就给它 当作children
const setChildren = (data, item, res) => {
data.forEach(d => {
if (d.tableId === item?._cfg?.model.tableId) {
d.children = res
} else {
if (d.children?.length) {
setChildren(d.children, item, res)
}
}
})
}
G6.registerNode('card-node', treeG6Config)
const chartDataObj = ref<any>(null) // 是一个对象啊
// TODO 这个不知道定义什么类型
let graphInstance: any = null
const renderTree = () => {
if (chartDataObj.value) {
graphInstance.data(chartDataObj.value)
graphInstance.render()
// graphInstance.fitView();
}
}
const tooltip = new G6.Tooltip({
offsetX: 10,
offsetY: 20,
trigger: 'mouseenter',
getContent(e) {
const outDiv = document.createElement('div')
outDiv.style.width = '270px'
outDiv.innerHTML = `
<ul>
<li>
<span style="width: 120px; display: inline-block;">表名:</span> ${
e.item.getModel().tableName
}</li>
<li><span style="width: 120px; display: inline-block;">数据库名:</span> ${
e.item.getModel().dataBaseName || '-'
}</li>
<li><span style="width: 120px; display: inline-block;">创建人:</span> ${
e.item.getModel().creator || '-'
}</li>
<li><span style="width: 120px; display: inline-block;">创建时间信息:</span> ${
e.item.getModel().createTime || '-'
}</li>
</ul>`
return outDiv
},
itemTypes: ['node'],
})
const initialRender = () => {
const container = document.getElementById('container')
const width = container.scrollWidth || 1024
const height = container.scrollHeight || 300
graphInstance = new G6.TreeGraph({
container: 'container',
width,
height,
plugins: [tooltip],
modes: {
default: ['drag-canvas'],
},
defaultNode: {
type: 'card-node',
size: [100, 40],
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
endArrow: true,
},
},
layout: {
type: 'indented',
direction: 'LR',
dropCap: false,
indent: 200,
getHeight: () => {
return 60
},
},
})
graphInstance.on('node:click', e => {
if (e.target.get('name') === 'collapse-icon') {
e.item.getModel().collapsed = !e.item.getModel().collapsed
graphInstance.setItemState(
e.item,
'collapsed',
e.item.getModel().collapsed
)
graphInstance.layout()
}
const item = e.item // 被操作的节点 item
const target = e.target // 被操作的具体图形
// 点击节点 看有没有下级节点 有就新增 没有就提示
if (!item?._cfg?.model.children || !item?._cfg?.model.children?.length) {
console.log('yellow----', item)
let tableId = Number(item?._cfg?.id)
getBuildBlood({
tableId: tableId,
type: branchDirectionType.value,
}).then(res => {
// let chartDataCopy = JSON.parse(JSON.stringify(chartDataObj.value))
if (res.data?.length && res.data[0] !== null) {
convertData(res.data)
// todo 后台没有返回有下游表的 可能有问题
setChildren(chartDataObj.value.children, item, res.data)
// 还是不要这样改 不然就会指着上级做下游
// if (branchDirectionType.value === 2) {
// setChildren(chartDataObj.value.children, item, res.data)
// } else {
// chartDataObj.value = {
// ...res.data,
// children: chartDataCopy,
// }
// }
} else {
proxy.$message({
message: `表名${item?._cfg?.model.tableName}没有${
branchDirectionType.value === 1 ? '上游' : '下游'
}表信息`,
duration: 1000,
type: 'warning',
})
return
}
nextTick(() => {
renderTree()
})
})
}
})
if (typeof window !== 'undefined')
window.onresize = () => {
if (!graphInstance || graphInstance.get('destroyed')) return
if (!container || !container.scrollWidth || !container.scrollHeight)
return
graphInstance.changeSize(container.scrollWidth, container.scrollHeight)
}
}
onMounted(() => {
// 只是渲染dom 没有数据
initialRender()
})
watchEffect(() => {
if (props.row?.id) {
getBuildBlood({
tableId: props.row.id,
type: branchDirectionType.value,
}).then(res => {
rowCopy.id = String(rowCopy.id)
rowCopy.dataBaseName = rowCopy.databases
let data = {
...rowCopy,
type: 0,
children: [],
tableName: addNewlines(rowCopy.tableName, 8),
}
if (res.data?.length && res.data[0] !== null) {
convertData(res.data)
data.children = res.data
} else {
data.children = []
}
chartDataObj.value = data
console.log('japna', data)
nextTick(() => {
renderTree()
})
})
}
})
onBeforeUnmount(() => {
if (!graphInstance || graphInstance.get('destroyed')) return
graphInstance.destroy()
// console.log("ss", graphInstance);
})
</script>
<template>
<div>
<el-select
v-model="branchDirectionType"
clearable
placeholder="请选择"
class="!w-[300px] mb-3"
>
<el-option label="下游表信息" :value="2"></el-option>
<el-option label="上游表信息" :value="1"></el-option>
</el-select>
<div class="jsmind pt-[190px]">
<div
id="container"
ref="mindRef"
class="overflow-y-auto overflow-x-hidden"
></div>
</div>
</div>
</template>
<style lang="less" scoped>
.jsmind {
width: 1024px;
height: 400px;
border: solid 1px #ccc;
}
</style>
退出页面销毁组件
参考链接