前言
之前一直使用Echarts或Highcharts实现图表,也有听说G2/G6这些玩意,于是在项目中使用看看效果,还不错的样子。
一、示例代码
javascript
<template>
<div class="index">
<div id="org-tree"></div>
</div>
</template>
<script>
/**
一、SVG中,path的指令有以下几种:
1. M/m:移动到指定点
2. L/l:从当前点画一条直线到指定点
3. H/h:从当前点画一条水平线到指定x坐标
4. V/v:从当前点画一条垂直线到指定y坐标
5. C/c:从当前点画一条三次贝塞尔曲线到指定点
6. S/s:从当前点画一条光滑的三次贝塞尔曲线到指定点
7. Q/q:从当前点画一条二次贝塞尔曲线到指定点
8. T/t:从当前点画一条光滑的二次贝塞尔曲线到指定点
9. A/a:从当前点画一条椭圆弧到指定点
10. Z/z:闭合路径
二、在SVG中,path是用来绘制各种形状的元素,其中M、L、H、Z和V是path中的指令,具体含义如下:
- M:移动到指定的坐标点,不画线。例如,M10 10表示将当前点移动到坐标(10,10)。
- L:从当前点画一条直线到指定的坐标点。例如,L50 50表示从当前点画一条直线到坐标(50,50)。
- H:从当前点画一条水平线到指定的x坐标点。例如,H100表示从当前点画一条水平线到x坐标为100的点。
- Z:闭合路径,即从当前点画一条直线到路径起点,形成一个封闭的图形。例如,Z表示从当前点画一条直线到路径起点,形成一个封闭的图形。
- V:从当前点画一条垂直线到指定的y坐标点。例如,V100表示从当前点画一条垂直线到y坐标为100的点。
这些指令可以组合使用,用来绘制各种复杂的形状。
*/
import G6 from '@antv/g6'
export default {
data() {
return {
treeData: {
id: "root",
label: "新机场建设项目",
children: [
{
id: "c1",
label: "航站区",
children: [
{
id: "c1-1",
label: "旅客航站楼",
children: [
{
id: "c1-1-1",
label: "主航站楼",
},
{
id: "c1-1-2",
label: "连接楼",
},
{
id: "c1-1-3",
label: "登机楼",
},
],
},
{
id: "c1-2",
label: "塔台",
},
{
id: "c1-3",
label: "机场酒店、停车楼",
},
{
id: "c1-4",
label: "大关小学",
},
],
},
{
id: "c2",
label: "飞行区",
children: [
{
id: "c2-1",
label: "跑道",
children: [
{
id: "c2-1-1",
label: "一号跑道",
},
{
id: "c2-1-2",
label: "二号跑道",
},
],
},
{
id: "c2-2",
label: "滑行道",
children: [
{
id: "c2-2-1",
label: "滑行道入口",
},
{
id: "c2-2-2",
label: "滑行道出口",
},
],
},
{
id: "c2-3",
label: "停机坪",
children: [
{
id: "c2-3-1",
label: "客机坪",
},
{
id: "c2-3-2",
label: "货机坪",
},
{
id: "c2-3-3",
label: "商务机坪",
},
],
},
],
},
{
id: "c3",
label: "工作区",
children: [
{
id: "c3-1",
label: "飞机维修区",
},
{
id: "c3-2",
label: "航空食品区",
},
{
id: "c3-3",
label: "油库区",
},
{
id: "c3-4",
label: "环保设施区",
},
{
id: "c3-5",
label: "机场当局、航空公司办公楼",
},
],
},
],
},
}
},
mounted() {
this.init_G6_OrgTree()
},
methods: {
/**
* 初始化一个 G6 树图
*/
init_G6_OrgTree() {
// 自定义 icon-node 节点
G6.registerNode(
'icon-node',
{
draw(cfg, group) {
const styles = this.getShapeStyle(cfg)
const { labelCfg = {} } = cfg
const w = styles.width
const h = styles.height
const keyShape = group.addShape('rect', {
attrs: {
...styles,
},
})
if (cfg.label) {
group.addShape('text', {
attrs: {
...labelCfg.style,
text: cfg.label.split('').join('\n'), // 换行
y: 108- h / 2+ cfg.label.length * 8,
},
})
}
return keyShape
},
update: undefined,
},
'rect'
)
// 自定义 flow-line 线
G6.registerEdge('flow-line', {
draw(cfg, group) {
const startPoint = cfg.startPoint
const endPoint = cfg.endPoint
const { style } = cfg
const shape = group.addShape('path', {
attrs: {
stroke: style.stroke,
lineWidth: 2,// 描边粗细
lineCap: 'round', // 设置线条的结束端点样式
lineJoin: 'round', // 两条线相交时,所创建的拐角形状(bevel斜角、round圆角、miter尖角)
endArrow: style.endArrow,
path: [
['M', startPoint.x, startPoint.y],
['V', (startPoint.y) + 150], // 画一条垂直线到指定x坐标
['H', endPoint.x - 0],
['L', endPoint.x, endPoint.y],
],
// path: [
// ['M', startPoint.x, startPoint.y],
// ['L', startPoint.x, (startPoint.y + endPoint.y) / 2],
// ['L', endPoint.x, (startPoint.y + endPoint.y) / 2],
// ['L', endPoint.x, endPoint.y],
// ],
},
})
return shape
},
})
// 机构树 DOM 节点
const container = document.getElementById("org-tree")
const width = container.scrollWidth
const height = container.scrollHeight
// 实例化树图
const that = this
that.graph = new G6.TreeGraph({
container: 'org-tree',
width,
height,
linkCenter: true, // 设置边连入节点的中心,以保证美观性
modes: {
default: ['drag-canvas', 'zoom-canvas'],
// default: ['drag-canvas', 'drag-node'],
}, // 树图模式
// 节点总览:https://g6.antv.antgroup.com/manual/middle/elements/nodes/default-node
defaultNode: {
type: 'icon-node', // 指定节点类型 icon-node、circle、rect
// size: [120, 40],
size: [40, 216], // 节点的大小 [120, 40]、[48, 216]
style: {
fill: '#000',
stroke: 'l(30) 0:#ffffff 0.5:#7ec2f3 1:#1890ff', // #40a9ff、l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff
radius: 4, // 描边圆角
lineWidth: 2,// 描边粗细
// lineDash: [3, 5], // 描边虚线,数组代表实、虚长度
},
labelCfg: {
style: {
fill: '#fff',
fontSize: 14,
fontWeight: 'bolder',
textAlign:'center',
},
}, // 文本配置项
},
// 边总览:https://g6.antv.antgroup.com/manual/middle/elements/edges/default-edge
defaultEdge: {
type: 'flow-line', // 指定边的类型 line、flow-line
style: {
stroke: 'l(45) 0:#ffffff 0.5:#27bda8 1:#5e7ce0', // #5294FF、l(270) 0:#ffffff 0.5:#7ec2f3 1:#1890ff
fillOpacity: 0.5,
lineOpacity: 0.1,
shadowBlur: 10,
// endArrow: {
// path: "M 0,0 L 12, 6 L 9,0 L 12, -6 Z",
// fill: "#5294FF",
// d: -110,
// }, // 结束箭头
}, // 边的样式属性
},
// 节点状态:https://g6.antv.antgroup.com/manual/middle/states/state
nodeStateStyles: {
type: 'polyline-edge',
hover: {
stroke: "#1890ff",
lineWidth: 2,
},
},
// 边状态:https://g6.antv.antgroup.com/manual/middle/states/state
edgeStateStyles: {},
layout: {
type: 'compactBox', // 布局类型,支持 dendrogram、compactBox、mindmap、indeted
direction: 'TB', // 布局方向,有 LR , RL , TB , BT , H , V ,说明(L:左;R:右;T:上;B:下;H:垂直;V:水平)
getId: (d) => {return d.id},
getWidth: () => {return 80}, // 每个节点的宽度
getHeight: () => {return 200}, // 每个节点的高度
getVGap: () => {return 50}, // 每个节点的水平间隙
getHGap: () => {return 40}, // 每个节点的垂直间隙
},
})
that.graph.data(this.treeData) // 载入数据
that.graph.render() // 渲染视图
that.graph.fitView() // 自适应画布
// 监听鼠标移入事件
that.graph.on('node:mouseenter', (evt) => {
const { item } = evt
that.graph.setItemState(item, 'hover', true)
})
// 监听鼠标移出事件
that.graph.on('node:mouseleave', (evt) => {
const { item } = evt
that.graph.setItemState(item, 'hover', false)
})
// 监听画布缩放事件
if (typeof window !== 'undefined')
window.addEventListener('resize', () => {
if (!that.graph || that.graph.get('destroyed'))
return
if (!container || !container.scrollWidth || !container.scrollHeight)
return
that.graph.changeSize(0, 0)
that.graph.changeSize(container.scrollWidth, container.scrollHeight-3)
that.graph.fitView()
})
},
},
}
</script>
<style lang="less" scoped>
.index {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #000;
#org-tree {
width: 100%;
height: 100%;
}
}
</style>
二、运行效果
// Todo