文档

​antv g6 文档:https://antv-g6.gitee.io/zh/docs/manual/introduction​

实现效果

效果如下:只是简单的实现一下,有问题在所难免,有好的方案欢迎分享。

vue 里使用 antv g6 实现脑图左右布局、文本超出隐藏处理、自定义边、自定义节点、自定义事件功能_antv g6

数据

这里需要对数据进行一个处理,​​state: 'left',​​​ 表示在左边渲染,​​anchorPoints​​ 根据左边还是右边,以及是否有 children 有关

export const TREELIST = {
id: "1",
name: 'kiamo313 测试第一层',
"children": [
{
"id": "1-1",
name: '第二层1',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-2",
name: '第二层2',
state: 'left',
"children": [
{
"id": "1-2-1",
name: '第三层1',
state: 'left',
children: [
{
"id": "1-2-1-1",
name: '第四层1第四层1第四层1第四层1第四层1第四层1',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-1-2",
name: '第四层2',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-1-3",
name: '第四层3第四层3第四层3第四层3第四层3第四层3',
state: 'left',
anchorPoints: [[1, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-2-2",
name: '第三层2第三层2第三层2第三层2第三层2第三层2',
state: 'left',
children: [
{
"id": "1-2-2-1",
name: '第四层4',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-2-2",
name: '第四层5第四层5第四层5第四层5第四层5第四层5',
state: 'left',
anchorPoints: [[1, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-2-3",
name: '第三层3',
state: 'left',
children: [
{
"id": "1-2-3-1",
name: '第四层6第四层6第四层6第四层6第四层6',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-3-2",
name: '第四层7',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-3-3",
name: '第四层8',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-3-4",
name: '第四层9第四层9第四层9第四层9第四层9第四层9',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-3-5",
name: '第四层10',
state: 'left',
anchorPoints: [[1, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-2-4",
name: '第三层4',
state: 'left',
children: [
{
"id": "1-2-4-1",
name: '第四层11',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-4-2",
name: '第四层12',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-4-3",
name: '第四层13',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-2-4-4",
name: '第四层14',
state: 'left',
anchorPoints: [[1, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-3",
name: '第二层3',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-4",
name: '第二层4',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5",
name: '第二层5',
"children": [
{
"id": "1-5-1",
name: '第三层1',
children: [
{
"id": "1-5-1-1",
name: '第四层1第四层1第四层1第四层1第四层1',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-1-2",
name: '第四层2',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-1-3",
name: '第四层3第四层3第四层3',
anchorPoints: [[0, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-5-2",
name: '第三层2',
children: [
{
"id": "1-5-2-1",
name: '第四层4',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-2-2",
name: '第四层5',
anchorPoints: [[0, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-5-3",
name: '第三层3',
children: [
{
"id": "1-5-3-1",
name: '第四层6',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-3-2",
name: '第四层7',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-3-3",
name: '第四层8',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-3-4",
name: '第四层9第四层9第四层9第四层9第四层9',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-3-5",
name: '第四层10',
anchorPoints: [[0, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-5-4",
name: '第三层4',
children: [
{
"id": "1-5-4-1",
name: '第四层11',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-4-2",
name: '第四层12',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-4-3",
name: '第四层13',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-5-4-4",
name: '第四层14',
anchorPoints: [[0, 0.5]]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
}
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
},
{
"id": "1-6",
name: '第二层6',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-7",
name: '第二层7',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-8",
name: '第二层8',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-9",
name: '第二层9',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-10",
name: '第二层10',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-11",
name: '第二层11',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-12",
name: '第二层12',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-13",
name: '第二层13第二层13第二层13第二层13',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-14",
name: '第二层1-14',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-15",
name: '第二层15第二层15第二层15第二层15',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-16",
name: '第二层16',
anchorPoints: [[0, 0.5]]
},
{
"id": "1-27",
name: '第二层27',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-28",
name: '第二层28',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-29",
name: '第二层29',
state: 'left',
anchorPoints: [[1, 0.5]]
},
{
"id": "1-30",
name: '第二层30',
state: 'left',
anchorPoints: [[1, 0.5]]
},
],
anchorPoints: [
[1, 0.5],
[0, 0.5]
]
}

代码实现

<template>
<div id="g6-kaimo"></div>
</template>

<script>import G6 from '@antv/g6';
import { TREELIST } from "./data/list.js";

export default {
name: 'G6Demo',
mounted() {
this.initTree(TREELIST);
},
methods: {
initTree (data) {
// 文本超出隐藏 (字段, 最大长度, 字体大小)
const fittingString = (str, maxWidth,) => {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter,) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
// 获取文本的长度
const getTextSize = (str, maxWidth,) => {
let width = G6.Util.getTextSize(str, fontSize)[0];
return width > maxWidth ? maxWidth : width;
}
// 自定义节点
G6.registerNode(
'tree-node',
{
draw(cfg,) {
let rect;
if(cfg.state === "left") {
rect = group.addShape('rect', {
attrs: {
x: 210 - getTextSize(cfg.name, 210, 16), // x 轴移动距离
y: 0, // y 轴移动距离
width: getTextSize(cfg.name, 210, 16) + 20,// 宽
height: 40,// 高
fill: '#e35e5e',
stroke: '#666',// 边框色
fontSize: 16,
fontWeight: 600,
radius: 4,
},
name: 'big-rect-shape',
});
// 左文本点
group.addShape('text', {
attrs: {
text: fittingString(cfg.name, 210, 16),
x: 210 - getTextSize(cfg.name, 210, 16) + 10,
y: 20,
fontSize: 16,
textAlign: 'left',
textBaseline: 'middle',
fill: "#fff"
},
name: 'text-shape',
});
}
if(cfg.state !== "left") {
rect = group.addShape('rect', {
attrs: {
x: 0, // x 轴移动距离
y: 0, // y 轴移动距离
width: getTextSize(cfg.name, 210, 16) + 20,// 宽
height: 40,// 高
fill: '#2ead65',
stroke: '#666',// 边框色
fontSize: 16,
fontWeight: 600,
radius: 4,
},
name: 'big-rect-shape',
});
group.addShape('text', {
attrs: {
text: fittingString(cfg.name, 210, 16),
x: 10,
y: 20,
fontSize: 16,
textAlign: 'left',
textBaseline: 'middle',
fill: "#fff"
},
name: 'text-shape',
});
if(cfg.depth === 0) {
rect = group.addShape('rect', {
attrs: {
x: 0, // x 轴移动距离
y: 0, // y 轴移动距离
width: getTextSize(cfg.name, 210, 16) + 20,// 宽
height: 40,// 高
fill: '#873bf4',
stroke: '#666',// 边框色
fontSize: 16,
fontWeight: 600,
radius: 4,
},
name: 'big-rect-shape',
});
group.addShape('text', {
attrs: {
text: fittingString(cfg.name, 210, 16),
x: 10,
y: 20,
fontSize: 16,
textAlign: 'left',
textBaseline: 'middle',
fill: "#fff"
},
name: 'text-shape',
});
}
}
// 是否有子节点
if(cfg.children && cfg.children.length > 0) {
if(cfg.state === "left") {
group.addShape('marker', {
attrs: {
x: 210 - getTextSize(cfg.name, 210, 16),
y: 20,
r: 6,
symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
fill: '#fff',
lineWidth: 1,
cursor: 'pointer', // 鼠标变手
},
name: 'collapse-icon',
});
}else{
group.addShape('marker', {
attrs: {
x: getTextSize(cfg.name, 210, 16) + 20,
y: 20,
r: 6,
symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
fill: '#fff',
lineWidth: 1,
cursor: 'pointer', // 鼠标变手
},
name: 'collapse-icon',
});
if(cfg.depth === 0) {
console.log("cfg---->", cfg)
group.addShape('marker', {
attrs: {
x: 0,
y: 20,
r: 6,
symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
stroke: '#666',
fill: '#fff',
lineWidth: 1,
cursor: 'pointer', // 鼠标变手
},
name: 'collapse-icon',
});
}
}

}
return rect;
},
setState: (name, value,) => {
console.log("自定义节点--->setState:", name, value, item);
if (name === "collapsed") {
console.log("item-group", item.get("group"))
const marker = item.get("group").findAll(ele => ele.get("name") === "collapse-icon");
console.log("marker--->", marker)
marker[0] && marker[0].attr("symbol", value ? G6.Marker.collapse : G6.Marker.expand);
// 如果是根节点需要处理两个marker
if(item._cfg.model.depth === 0) {
marker[1] && marker[1].attr("symbol", value ? G6.Marker.collapse : G6.Marker.expand);
}
}
}
},
);
// 自定义边
G6.registerEdge(
'kaimo-line',
{
/**
* 绘制边,包含文本
* @param {Object} cfg 边的配置项
* @param {G.Group} group 图形分组,边中的图形对象的容器
* @return {G.Shape} 绘制的图形,通过 node.get('keyShape') 可以获取到
*/
draw(cfg,) {
const startPoint = cfg.startPoint;
const endPoint = cfg.endPoint;
let shape;
if(cfg.state === 'left') {
shape = group.addShape('path', {//线条
attrs: {
stroke: 'pink',
path: [
['M', startPoint.x, startPoint.y],
['L', 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: 'pink',
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'
});
}else{
shape = group.addShape('path', {//线条
attrs: {
stroke: 'pink',
path: [
['M', startPoint.x, startPoint.y],
['L', 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: 'pink',
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;
},
},
);
// 自定义事件
G6.registerBehavior("kaimo-behavior", {
getEvents() {
return {
"node:click": "onNodeClick"
};
},
// 自定义单击事件
onNodeClick: (evt) => {
// 单击展开/收起
console.log("自定义单击事件--->", evt, graph)
evt.item._cfg.model.collapsed = !evt.item._cfg.model.collapsed;
// 更改 item 的状态,触发自定义节点:setState
graph.setItemState(evt.item, "collapsed", !evt.item.getModel().collapsed);
graph.layout();
},
});

// 定义画布的宽高
const width = document.getElementById('g6-kaimo').scrollWidth || 1600;
const height = document.getElementById('g6-kaimo').scrollHeight || 960;
// 实例化G6
// 因为我们用的是树图,所以这里是G6.TreeGraph(),还有其他,像是普通图的配置G6.Graph(),一般y用的比较多的就像是树图这种,还有组织架构图一类的。
const graph = new G6.TreeGraph({
// 图的 DOM 容器,对应上面我们定义的id
container: 'g6-kaimo',
width,
height,
// 设置画布的交互模式
modes: {
default: [
// 自定义事件
'kaimo-behavior',
// 拖拽画布
'drag-canvas',
// 缩放画布
'zoom-canvas',
],
},
// 配置节点的属性
defaultNode: {
// 节点类型,cicle:圆形,rect:矩形,ellipse:椭圆,diamond:菱形,triangle:三角形,star:五角星,image:图片,modelRect:卡片
type: 'tree-node',
// 节点样式
style: {
// 鼠标经过是的形状,跟css是一样的。
cursor:'pointer',
// 圆角
radius: 4,
},
},
// 配置边的属性
defaultEdge: {
// 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。
// line:直线,polyline:折线,arc:圆弧线,quadratic:二阶贝塞尔曲线,cubic:三阶贝塞尔曲线,cubic-vertica:垂直方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。cubic-horizontal:水平方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。loop:自环
type: 'kaimo-line',
},
// 布局配置项
layout: {
// 布局名称,这个可就太多了,这个只是树图结构中的一种。比如还有compactBox:紧凑树布局,dendrogram:生态树布局,indented:缩紧树布局。
type: 'compactBox', // 脑图树布局
direction: 'H', // H / V / LR / RL / TB / BT 这些是控制节点分布位置,从左往右、从右往左、从中间往上下延伸、从中间往左右延伸...具体可以看官网
// 节点 id 的回调函数
getId: function getId(d) {
return d.id;
},
// 下面都是一些控制节点与节点间距离的回调函数,具体可以试着修改一下值。
// 节点高度的回调函数
getHeight: function getHeight() {
return 16;
},
// 节点宽度的回调函数
getWidth: function getWidth(cfg) {
return 60;
},
// 节点纵向间距的回调函数
getVGap: function getVGap() {
return 20;
},
// 节点横向间距的回调函数
getHGap: function getHGap(val) {
return 100;
},
// 这个getSide就是控制节点位置的属性了,通过数据结构中定义的值做判断,来控制左右,
// 注意的是这个官方写的只能return 'left'和'right',当我们的树结构是竖着的呢?难道是用top和bottom?这里我也试过了,用top和bottom是不好使的,因为人家官方确确实实的只有left和right,通过尝试,其实left就对应top,right对应bottom,所以要控制节点在上面就写left,在下面就写right。
getSide: (node) => {
if(node.data.state === 'left') {
return 'left'
}
return 'right'
}
},
// 动画属性
animate: true,
});
// 默认全部展开
G6.Util.traverseTree(data, function (item) {
item.collapsed = false;
});
// 初始化的图数据
graph.data(data);
// 根据提供的数据渲染视图。
graph.render();
// 让画布内容适应视口
graph.fitView();
}
},
};</script>