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>

浙公网安备 33010602011771号