antv/g6 实现区县架构组织图
记录一下
点击查看代码
<template>
<div ref="container" style="width: 100%; height: 100vh; border: 1px solid #ddd;"></div>
</template>
<script>
import G6 from '@antv/g6'
import * as Hierarchy from '@antv/hierarchy'
// 显式注册布局,防止 tree-shaking
G6.registerLayout('dendrogram', Hierarchy.dendrogram)
export default {
name: 'ReservoirTopology',
data() {
return {
graph: null
}
},
mounted() {
this.initGraph()
},
methods: {
initGraph() {
const container = this.$refs.container
let graphRef = null
const data = {
id: 'JiNan',
label: '济南市',
isCity: true,
children: [
{
id: 'ChangQingQu',
label: '长清区',
children: [
{
id: 'ZhangXiaZhen',
label: '张夏镇',
children: [
{ id: 'PuTaoWan', label: '葡萄湾水库' },
{ id: 'TengJiaGou', label: '滕家沟水库' },
{ id: 'DuZhuang', label: '杜庄水库' }
]
}
]
},
{
id: 'PingYinXian',
label: '平阴县',
children: [
{
id: 'DongEzhen',
label: '东阿镇',
children: [
{ id: 'DongE', label: '东阿水库' }
]
}
]
},
{
id: 'ZhangQiuShi',
label: '章丘市',
children: [
{
id: 'GuanZhuangZhen',
label: '官庄镇',
children: [
{ id: 'DongFeng', label: '东风水库' }
]
},
{
id: 'PuJiZhen',
label: '普集镇',
children: [
{ id: 'LongHua', label: '龙华水库' }
]
}
]
}
]
}
// 【新增】注册组织架构图风格的边
G6.registerEdge('org-chart-edge', {
draw(cfg, group) {
const { startPoint, endPoint } = cfg
const x1 = startPoint.x
const y1 = startPoint.y
const x2 = endPoint.x
const y2 = endPoint.y
const midY = y1 + (y2 - y1) * 0.5 // 中间水平线高度
const path = [
['M', x1, y1],
['L', x1, midY], // 向下
['L', x2, midY], // 横向
['L', x2, y2] // 向下
]
return group.addShape('path', {
attrs: {
path,
stroke: '#AAB7C4',
lineWidth: 1.5,
lineCap: 'round',
lineJoin: 'round'
},
name: 'org-edge-path'
})
}
})
//节点注册
G6.registerNode('reservoir-node', {
draw(cfg, group) {
const isLeaf = !cfg.children || cfg.children.length === 0
const displayLabel = isLeaf
? cfg.label.split('').join('\n')
: cfg.label
const lines = displayLabel.split('\n')
const lineHeight = 5
const width = isLeaf ? 35 : Math.max(60, (cfg.label.length + 1) * 12)
const height = isLeaf ? 70 : Math.max(lines.length * lineHeight, 32)
const backImage = isLeaf
? require('@/assets/images/screen/sizhi/hengBlueLabel.png')
: require('@/assets/images/screen/sizhi/shuBlueLabel.png')
// 1. 添加背景图片(必须用 image shape)
const bg = group.addShape('image', {
attrs: {
x: -width / 2,
y: -height / 2,
width: width,
height: height,
img: backImage, // 注意:属性名是 img,不是 backgroundImage
},
name: 'bg-image',
draggable: false,
});
group.addShape('rect', {
attrs: {
x: -width / 2,
y: -height / 2,
width,
height:height,
radius: 4,
fill: 'transparent', // 使用透明填充,因为我们已经有了背景图片
// stroke: cfg.isCity ? '#1890ff' : '#597ef7',
// lineWidth: cfg.isCity ? 2 : 1.5,
},
name: 'rect'
})
group.addShape('text', {
attrs: {
text: displayLabel,
x: 0,
y: 0,
textAlign: 'center',
textBaseline: 'middle',
fontSize: 12,
fill: isLeaf ? '#333' : '#000',
fontWeight: cfg.isCity ? 'bold' : 'normal'
},
name: 'label'
})
if (!isLeaf) {
const buttonX = 0
const buttonY = height / 2
const badge = group.addShape('circle', {
attrs: {
x: buttonX,
y: buttonY,
r: 8,
fill: '#fff',
stroke: '#999',
lineWidth: 1
},
name: 'collapse-btn-bg'
})
const icon = group.addShape('text', {
attrs: {
text: cfg.collapsed ? '+' : '−',
x: buttonX,
y: buttonY,
textAlign: 'center',
textBaseline: 'middle',
fontSize: 10,
fill: '#666',
fontWeight: 'bold'
},
name: 'collapse-icon'
})
const handleClick = (e) => {
e.preventDefault()
e.stopPropagation()
const nodeItem = graphRef.findById(cfg.id)
if (!nodeItem) return
const model = nodeItem.getModel()
model.collapsed = !model.collapsed
const getAllDescendants = (nodeModel) => {
let result = []
if (nodeModel.children) {
nodeModel.children.forEach(child => {
result.push(child)
result = result.concat(getAllDescendants(child))
})
}
return result
}
const descendants = getAllDescendants(model)
if (model.collapsed) {
descendants.forEach(desc => {
const item = graphRef.findById(desc.id)
if (item && item.isVisible()) {
graphRef.hideItem(item)
}
})
} else {
descendants.forEach(desc => {
const item = graphRef.findById(desc.id)
if (item && !item.isVisible()) {
graphRef.showItem(item)
}
})
}
const group = nodeItem.getContainer()
const iconShape = group.find(child => child.get('name') === 'collapse-icon')
if (iconShape) {
iconShape.attr('text', model.collapsed ? '+' : '−')
}
}
badge.on('click', handleClick)
icon.on('click', handleClick)
}
return bg
}
})
// 创建 TreeGraph
this.graph = new G6.TreeGraph({
container,
width: container.clientWidth,
height: container.clientHeight,
modes: {
default: ['drag-canvas', 'zoom-canvas']
},
layout: {
type: 'dendrogram',
direction: 'TB',
nodeSep: 80,
rankSep: 80
},
defaultNode: {
type: 'reservoir-node',
anchorPoints: [
[0.5, 0],
[0.5, 1]
]
},
defaultEdge: {
type: 'org-chart-edge', // 使用新边类型
style: {
stroke: '#AAB7C4',
lineWidth: 1.5
}
},
animate: true,
animateCfg: {
duration: 300,
easing: 'easeCubic'
},
fitView: true,
fitViewPadding: [10, 10, 10, 10],
autoLayout: false
})
graphRef = this.graph
function mapData(node) {
const newNode = {
...node,
visible: true,
collapsed: false
}
if (node.children && Array.isArray(node.children)) {
newNode.children = node.children.map(child => mapData(child))
}
return newNode
}
this.graph.data(mapData(data))
this.graph.render()
this.graph.on('node:mouseenter', (evt) => {
const node = evt.item.getModel()
if (!node.children) {
console.log(`当前水库: ${node.label}`)
}
})
window.addEventListener('resize', () => {
if (this.graph) {
this.graph.changeSize(container.clientWidth, container.clientHeight)
this.graph.fitView()
}
})
}
},
beforeDestroy() {
if (this.graph) {
this.graph.destroy()
this.graph = null
}
}
}
</script>


浙公网安备 33010602011771号