antv G6 脑图设计
首先是要安装antv G6插件
安装命令:
npm install --save @antv/g6
Vue页面开发:
<template> <div id="container"></div> </template> <script lang="tsx" setup> import { onMounted, ref, reactive} from 'vue' import G6 from '@antv/g6' onMounted(() => { const mapData: any = mockData||[] const temp = { ...mapData, label: mapData.name, children: mapData?.children.length && formatData(mapData?.children) } formItems[1].selectOptions = temp datas.TreeSelectorDatas = [...temp?.children, { id: '0', nodeName: '根节点' }] const minWidth = 60 const BaseConfig = { nameFontSize: 12, childCountWidth: 22, countMarginLeft: 0, itemPadding: 16, nameMarginLeft: 24, // nameMarginLeft: 4, rootPadding: 18 } G6.registerNode('treeNode', { draw: function drawShape(cfg: any, group) { const { image, label, selected, children, depth } = cfg const rootNode = depth === 0 const hasChildren = children && children.length !== 0 const { childCountWidth, countMarginLeft, itemPadding, nameMarginLeft, rootPadding } = BaseConfig let width = 0 const height = 28 const x = 0 const y = -height / 2 // 名称文本 const text = group.addShape('text', { attrs: { text: label, x: x * 2 + 10, y, textAlign: 'left', textBaseline: 'top', fontFamily: 'PingFangSC-Regular' }, cursor: 'pointer', name: 'name-text-shape' }) // 字体图标 const img = group.addShape('image', { attrs: { img: image, x: x * 2 + 12, y: y - 9, width: 16, height: 16, opacity: 0.85 }, cursor: 'pointer', name: 'name-text-shape' }) const textWidth = text.getBBox().width width = textWidth + itemPadding + nameMarginLeft width = width < minWidth ? minWidth : width if (!rootNode && hasChildren) { width += countMarginLeft width += childCountWidth } const keyShapeAttrs: any = { x, y, width, height, radius: 4 } // keyShape根节点选中样式 if (rootNode && selected) { keyShapeAttrs.fill = '#e8f7ff' keyShapeAttrs.stroke = '#e8f7ff' } const keyShape = group.addShape('rect', { attrs: keyShapeAttrs, name: 'root-key-shape-rect-shape' }) if (!rootNode) { // 底部横线 group.addShape('path', { attrs: { path: [ ['M', x - 1, 0], ['L', width, 0] ], stroke: '#AAB7C4', lineWidth: 1 }, name: 'node-path-shape' }) } const mainX = x const mainY = -height + 15 if (rootNode) { // 根节点 group.addShape('rect', { attrs: { x: mainX, y: mainY, width: width, height, radius: 14, fill: '#e8f7ff', cursor: 'pointer' }, name: 'main-shape' }) } let nameColor = 'rgba(0, 0, 0, .65)' if (selected) { nameColor = '#40A8FF' } // 名称 if (rootNode) { group.addShape('text', { attrs: { text: label, x: mainX + rootPadding, y: 1, textAlign: 'left', textBaseline: 'middle', fill: nameColor, fontSize: 12, fontFamily: 'PingFangSC-Regular', cursor: 'pointer' }, name: 'root-text-shape' }) } else { group.addShape('text', { attrs: { text: label, x: selected ? mainX + 36 + nameMarginLeft : mainX + 36, y: y - 5, textAlign: 'start', textBaseline: 'top', fill: nameColor, fontSize: 12, fontFamily: 'PingFangSC-Regular', cursor: 'pointer' }, name: 'not-root-text-shape' }) } cfg.children?.length && group.addShape('marker', { attrs: { x: width, y: 0, r: 6, cursor: 'pointer', symbol: G6.Marker.collapse, stroke: '#666', lineWidth: 1, fill: '#fff' }, name: 'collapse-icon' }) return keyShape }, 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) } } }) /** * 工具栏 */ const toolbar = new G6.ToolBar({ getContent: () => { return `<ul class="g6-component-toolbar" style="top: 10px; left: 10px;"> <li code="zoomOut"> <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"> <path d="M658.432 428.736a33.216 33.216 0 0 1-33.152 33.152H525.824v99.456a33.216 33.216 0 0 1-66.304 0V461.888H360.064a33.152 33.152 0 0 1 0-66.304H459.52V296.128a33.152 33.152 0 0 1 66.304 0V395.52H625.28c18.24 0 33.152 14.848 33.152 33.152z m299.776 521.792a43.328 43.328 0 0 1-60.864-6.912l-189.248-220.992a362.368 362.368 0 0 1-215.36 70.848 364.8 364.8 0 1 1 364.8-364.736 363.072 363.072 0 0 1-86.912 235.968l192.384 224.64a43.392 43.392 0 0 1-4.8 61.184z m-465.536-223.36a298.816 298.816 0 0 0 298.432-298.432 298.816 298.816 0 0 0-298.432-298.432A298.816 298.816 0 0 0 194.24 428.8a298.816 298.816 0 0 0 298.432 298.432z"></path> </svg> </li> <li code="zoomIn"> <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"> <path d="M639.936 416a32 32 0 0 1-32 32h-256a32 32 0 0 1 0-64h256a32 32 0 0 1 32 32z m289.28 503.552a41.792 41.792 0 0 1-58.752-6.656l-182.656-213.248A349.76 349.76 0 0 1 480 768 352 352 0 1 1 832 416a350.4 350.4 0 0 1-83.84 227.712l185.664 216.768a41.856 41.856 0 0 1-4.608 59.072zM479.936 704c158.784 0 288-129.216 288-288S638.72 128 479.936 128a288.32 288.32 0 0 0-288 288c0 158.784 129.216 288 288 288z" p-id="3853"></path> </svg> </li> <li code="realZoom"> <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="24"> <path d="M384 320v384H320V320h64z m256 0v384H576V320h64zM512 576v64H448V576h64z m0-192v64H448V384h64z m355.968 576H92.032A28.16 28.16 0 0 1 64 931.968V28.032C64 12.608 76.608 0 95.168 0h610.368L896 192v739.968a28.16 28.16 0 0 1-28.032 28.032zM704 64v128h128l-128-128z m128 192h-190.464V64H128v832h704V256z"></path> </svg> </li> <li code="autoZoom"> <svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="24"> <path d="M684.288 305.28l0.128-0.64-0.128-0.64V99.712c0-19.84 15.552-35.904 34.496-35.712a35.072 35.072 0 0 1 34.56 35.776v171.008h170.944c19.648 0 35.84 15.488 35.712 34.432a35.072 35.072 0 0 1-35.84 34.496h-204.16l-0.64-0.128a32.768 32.768 0 0 1-20.864-7.552c-1.344-1.024-2.816-1.664-3.968-2.816-0.384-0.32-0.512-0.768-0.832-1.088a33.472 33.472 0 0 1-9.408-22.848zM305.28 64a35.072 35.072 0 0 0-34.56 35.776v171.008H99.776A35.072 35.072 0 0 0 64 305.216c0 18.944 15.872 34.496 35.84 34.496h204.16l0.64-0.128a32.896 32.896 0 0 0 20.864-7.552c1.344-1.024 2.816-1.664 3.904-2.816 0.384-0.32 0.512-0.768 0.768-1.088a33.024 33.024 0 0 0 9.536-22.848l-0.128-0.64 0.128-0.704V99.712A35.008 35.008 0 0 0 305.216 64z m618.944 620.288h-204.16l-0.64 0.128-0.512-0.128c-7.808 0-14.72 3.2-20.48 7.68-1.28 1.024-2.752 1.664-3.84 2.752-0.384 0.32-0.512 0.768-0.832 1.088a33.664 33.664 0 0 0-9.408 22.912l0.128 0.64-0.128 0.704v204.288c0 19.712 15.552 35.904 34.496 35.712a35.072 35.072 0 0 0 34.56-35.776V753.28h170.944c19.648 0 35.84-15.488 35.712-34.432a35.072 35.072 0 0 0-35.84-34.496z m-593.92 11.52c-0.256-0.32-0.384-0.768-0.768-1.088-1.088-1.088-2.56-1.728-3.84-2.688a33.088 33.088 0 0 0-20.48-7.68l-0.512 0.064-0.64-0.128H99.84a35.072 35.072 0 0 0-35.84 34.496 35.072 35.072 0 0 0 35.712 34.432H270.72v171.008c0 19.84 15.552 35.84 34.56 35.776a35.008 35.008 0 0 0 34.432-35.712V720l-0.128-0.64 0.128-0.704a33.344 33.344 0 0 0-9.472-22.848zM512 374.144a137.92 137.92 0 1 0 0.128 275.84A137.92 137.92 0 0 0 512 374.08z"></path> </svg> </li> </ul>` } }) // 实例化 minimap 插件 const minimap = new G6.Minimap({ size: [200, 100], className: 'minimap', type: 'keyShape' }) // 菜单插件 const contextMenu = new G6.Menu({ getContent(val) { const record = val.item._cfg.model if (!record.depth) { return '' } let indexItem: any = '' if (record.category === 1) { // 新增文件夹过滤指标画像和模型编辑菜单 } else { indexItem = '<li>指标画像</li>' } return ` <ul class="meun-list"> ${indexItem} </ul>` }, handleMenuClick: (target, item: any) => { switch (target.innerHTML) { case '新增文件夹': onAddItem(item._cfg.model) break case '关联指标': onSelectedItem(item._cfg.model) break case '指标画像': toPortrait(item._cfg.model) break case '编辑': handleEdit(item._cfg.model) break case '删除': onDeleteItem(item._cfg.model) break } }, offsetX: 10, offsetY: 10, // 在哪些类型的元素上响应 itemTypes: ['node'] }) G6.registerEdge('hvh', { draw(cfg, group) { const startPoint = cfg.startPoint const endPoint = cfg.endPoint const shape = group.addShape('path', { attrs: { stroke: '#AAB7C4', path: [ ['M', startPoint.x, startPoint.y], ['A', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], // 三分之一处 ['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], // 三分之二处 ['L', endPoint.x, endPoint.y] ], endArrow: { path: G6.Arrow.triangle(5, 5, 0), // 使用内置箭头路径函数,参数为箭头的 宽度、长度、偏移量(默认为 0,与 d 对应) d: 0, fill: '#409EFF', opacity: 0.5, lineWidth: 1 } }, // must be assigned in G6 3.3 and later versions. it can be any value you want name: 'path-shape' }) return shape } }) const container = document.getElementById('container') const width = parentContent.value.clientWidth // 画布宽度 const height = parentContent.value.clientHeight - 65 // 画布高度 const graph = new G6.TreeGraph({ container: 'container', width, height, plugins: [toolbar, minimap, contextMenu], // 将 minimap 实例配置到图上 modes: { default: [ { type: 'drag-canvas', onChange: function onChange(item, collapsed) { const data = item.get('model') graph.updateItem(item, { collapsed }) data.collapsed = collapsed return true } }, 'drag-canvas', 'zoom-canvas' ] }, defaultNode: { type: 'treeNode', anchorPoints: [ [0, 0.5], [1, 0.5] ] }, defaultEdge: { type: 'hvh', style: { stroke: '#A3B1BF' } }, layout: { type: 'compactBox', direction: 'LR', getId: function getId(d) { return d.id }, getHeight: function getHeight() { return 16 }, getWidth: function getWidth(d) { const labelWidth = G6.Util.getTextSize( d.label, BaseConfig.nameFontSize )[0] const width = BaseConfig.itemPadding + BaseConfig.nameMarginLeft + labelWidth + BaseConfig.rootPadding + BaseConfig.childCountWidth return width }, getVGap: function getVGap() { return 15 }, getHGap: function getHGap() { return 30 } } }) treeGraph.value = graph graph.data(temp) graph.render() graph.fitCenter() graph.on('node:click', (e: any) => { // 单击图标展开收起子节点 if (e.target.get('name') === 'collapse-icon') { e.item.getModel().collapsed = !e.item.getModel().collapsed graph.setItemState(e.item, 'collapsed', e.item.getModel().collapsed) graph.layout() } }) 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) } }) </script>