上期回顾:历程[一]描述了基本的树状图的绘制,默认节点类型defaultNode中的type是circle,下面这篇描述的是节点抽离自定义节点并做数据静态渲染。
官网地址:https://g6-next.antv.antgroup.com/manual/introduction
一、案例效果
二、自定义节点渲染
1. 主要通过G6.registerNode实现自定义节点的配置,官网地址:https://g6.antv.antgroup.com/manual/middle/elements/nodes/custom-node
G6.registerNode('nodeName',{options: {style: {},stateStyles: {hover: {},selected: {},},},/*** 绘制节点,包含文本* @param {Object} cfg 节点的配置项* @param {G.Group} group 图形分组,节点中图形对象的容器* @return {G.Shape} 返回一个绘制的图形作为 keyShape,通过 node.get('keyShape') 可以获取。* 关于 keyShape 可参考文档 核心概念-节点/边/Combo-图形 Shape 与 keyShape*/draw(cfg, group) {},/*** 绘制后的附加操作,默认没有任何操作* @param {Object} cfg 节点的配置项* @param {G.Group} group 图形分组,节点中图形对象的容器*/afterDraw(cfg, group) {},/*** 更新节点,包含文本* @override* @param {Object} cfg 节点的配置项* @param {Node} node 节点*/update(cfg, node) {},/*** 更新节点后的操作,一般同 afterDraw 配合使用* @override* @param {Object} cfg 节点的配置项* @param {Node} node 节点*/afterUpdate(cfg, node) {},/*** 响应节点的状态变化。* 在需要使用动画来响应状态变化时需要被复写,其他样式的响应参见下文提及的 [配置状态样式] 文档* @param {String} name 状态名称* @param {Object} value 状态值* @param {Node} node 节点*/setState(name, value, node) {},/*** 获取锚点(相关边的连入点)* @param {Object} cfg 节点的配置项* @return {Array|null} 锚点(相关边的连入点)的数组,如果为 null,则没有控制点*/getAnchorPoints(cfg) {},},// 继承内置节点类型的名字,例如基类 'single-node',或 'circle', 'rect' 等// 当不指定该参数则代表不继承任何内置节点类型extendedNodeType,
);
2. 将渲染的节点转换为组件渲染
在 Vue3 中,你可以通过 G6 的自定义节点功能,并在 draw 方法中创建一个新的 Vue 实例,然后将 Vue 组件的 HTML 内容添加到 G6 节点中。以下是一个基本的示例:
<script setup lang="ts">
import { createApp, nextTick } from 'vue'
import G6 from '@antv/g6'
import YourComponent from './YourComponent.vue'G6.registerNode('vue-node', {draw: (cfg, group) => {const container = document.createElement('div')const app = createApp(YourComponent, { ...cfg })app.mount(container)let shapenextTick(() => {shape = group.addShape('dom', {attrs: {width: cfg.size[0],height: cfg.size[1],html: container.innerHTML,},name: 'dom-shape',})})return shape},
})
</script>
在这个示例中,我们创建了一个新的 Vue 实例,并将 YourComponent 组件挂载到一个新创建的 div 元素上。然后,我们在 Vue 的 nextTick 中,将这个 div 的 HTML 内容添加到 G6 节点的 dom 形状中。
然后,你可以在你的图中使用这个新注册的 ‘vue-node’ 节点类型:
<script setup lang="ts">
const graph = new G6.Graph({container: 'graph-container',width: 800,height: 600,defaultNode: {type: 'vue-node',size: [100, 100],},
})graph.data({nodes: [{ id: 'node1', x: 100, y: 100, label: 'Node 1' },{ id: 'node2', x: 200, y: 200, label: 'Node 2' },],edges: [{ source: 'node1', target: 'node2' },],
})graph.render()
</script>
在这个示例中,我们创建了一个新的 G6 图表,并设置了默认节点类型为 ‘vue-node’。然后,我们定义了图表的数据,并调用 graph.render() 方法来渲染图表。
三、组件抽离及案例代码
1. 基本的绘制组件 【TopologyBase.vue】
<template><div :id="domId" class="w-full h-[95%]"></div>
</template>
<script setup lang="ts">
import { initData } from '@/common/constants/topologyData/initRender'
import G6 from '@antv/g6'
import { createApp, nextTick, onMounted, ref, watch } from 'vue'
import StatusNode from './StatusNode.vue'const props = defineProps({domId: {type: String,default: 'container',},treeData: {type: Array,},
})
const initTreeData = ref(props.treeData)
G6.registerNode('dom-node',{draw(cfg: any, group) {let shapeconst container = document.createElement('div')const app = createApp(StatusNode, { domNodeMsg: { ...cfg } })app.mount(container)shape = group.addShape('dom', {attrs: {width: cfg.size[0],height: cfg.size[1],html: container.innerHTML,},name: 'dom-shape',})return shape},},'single-node',
)
const initRender = () => {const container = document.getElementById(props.domId)const width = container?.scrollWidthconst height = container?.scrollHeightconst graph = new G6.TreeGraph({container: props.domId,width,height,modes: {default: ['drag-canvas', 'zoom-canvas', 'drag-node'],},defaultNode: {type: 'dom-node', // 矩形 rect/ 默认circlesize: [80, 30],anchorPoints: [[0, 0.5],[1, 0.5],],},fitCenter: true,renderer: 'svg',linkCenter: true,defaultEdge: {type: 'cubic-horizontal',/* 通过配置边的 style 属性来设置弯曲半径和到端节点的最小距离 */style: {radius: 5,offset: 10,endArrow: true,/* 设置其他样式 */stroke: '#2c3e50',},},layout: {type: 'compactBox',direction: 'LR',getId: function getId(d: { id: string }) {// 节点 id 的回调函数return d.id},getHeight: function getHeight() {// 每个节点的高度return 16},getWidth: function getWidth() {// 每个节点的宽度return 16},getVGap: function getVGap() {// 每个节点的垂直间隙return 30},getHGap: function getHGap() {// 每个节点的水平间隙return 50},},})graph.node(function (node) {return {label: node.id,labelCfg: {position: node.children && node.children.length > 0 ? 'left' : 'right',offset: 5,},}})graph.data(initData)graph.render()graph.fitView()if (typeof window !== 'undefined')window.onresize = () => {if (!graph || graph.get('destroyed')) returnif (!container || !container.scrollWidth || !container.scrollHeight) returngraph.changeSize(container.scrollWidth, container.scrollHeight)}
}
watch(() => props.treeData,(newValue) => {initTreeData.value = newValue},{ immediate: true, deep: true },
)
onMounted(() => {nextTick(() => {initRender()})
})
</script>
2. 节点组件抽离【StatusNode.vue】
<template><div class="status-node"><div :id="domNodeMsg.id" class="dom-node"><span style="cursor: pointer">{{ domNodeMsg.name }}</span></div></div>
</template><script setup lang="ts">
defineProps(['domNodeMsg'])
</script><style scoped>
.status-node {background-color: #fff;border: 1px solid #ccc;border-radius: 10px;text-align: center;font-size: 12px;padding: 0px 5px;color: #5b8ff9;.dom-node {text-overflow: ellipsis;overflow: hidden;white-space: nowrap;}
}
</style>
3. 组件引用
<TopologyBase domId="featureTreeInfoContainer" :treeData="tableData" />
4.数据格式
注意 id 必须为String格式
export const initData = {id: '376',name: '世界',children: [{id: '377',name: '中国',children: [{id: '380',name: '北方',children: [],}]}]
}