vue3使用AntV G6 (图可视化引擎)历程[二]

上期回顾:历程[一]描述了基本的树状图的绘制,默认节点类型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 shape
    nextTick(() => {
      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 shape
      const 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?.scrollWidth
  const height = container?.scrollHeight

  const graph = new G6.TreeGraph({
    container: props.domId,
    width,
    height,
    modes: {
      default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
    },
    defaultNode: {
      type: 'dom-node', // 矩形 rect/ 默认circle
      size: [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')) return
      if (!container || !container.scrollWidth || !container.scrollHeight) return
      graph.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: [],
		}]
	}]
}
  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值