文档
antv g6 文档:https://antv-g6.gitee.io/zh/docs/manual/introduction
实现效果
效果如下:只是简单的实现一下,有问题在所难免,有好的方案欢迎分享。
数据
这里需要对数据进行一个处理,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, fontSize) => { 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, i) => { 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, fontSize) => { let width = G6.Util.getTextSize(str, fontSize)[0]; return width > maxWidth ? maxWidth : width; } // 自定义节点 G6.registerNode( 'tree-node', { draw(cfg, group) { 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, item) => { 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, group) { 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>