G6绘制树形图(自定义节点、自定义边、自定义布局)
1 设计节点
在 registerNode 中定义所有的节点
G6.registerNode('tree-node', {drawShape: function drawShape(cfg, group) {定义图中需要的节点}
}, 'single-node',);
为了使用内置的布局方式,选择参数为 ‘tree-node’ 树节点类型,数据格式可以存在children子节点,效果自动生成子树
cfg 可以拿到数据,如cfg.id、cfg.name
1.1 定义节点和文本
使用 group.addShape(‘rect’, {}) 定义节点 rect
配置参数:https://antv-g6.gitee.io/zh/docs/api/shapeProperties/#fill
// 定义节点 rect const rect = group.addShape('rect', { // 'rect'表示矩形图形attrs: {// 节点定义参数:颜色、阴影...},name: 'rect-shape', // 为这个节点起名字 不过没有使用过这个名字});
使用 group.addShape(‘text’, {}) 定义文本 text
// 定义文本textconst text = group.addShape('text', { // 'text'表示文本attrs: {// 参数:颜色、文字...},name: 'text-shape',});
节点和文字生成后,再定义他们的相对位置
参考官网定义复杂图样式的方式:https://antv-g6.gitee.io/zh/examples/tree/customItemTree#customTree
使用 .getBBox() 获得该文本的盒子bbox,使用文本盒子的相对位置后面的位置坐标
const bbox = text.getBBox(); // 获得文本的盒子// 设置rect 节点的位置rect.attr({x: -bbox.width / 2 - 5, // x坐标y: -bbox.height, // y坐标width: bbox.width + 12 , // 宽height: bbox.height + 8, // 高});// 设置text文本的位置text.attr({x: -bbox.width / 2,y: -bbox.height / 2 + 3,})
效果如下
1.2 增加节点
如果想为节点再增加一个小节点,并且位置随着大节点移动,如图
新增节点和文本 rect2 text2
rect2 = group.addShape('rect', {attrs: {// 参数},name: 'rect-shape2',
});
const text2 = group.addShape('text', {attrs: {// 参数},name: 'text-shape2',
});
为rect2 text2设置坐标,以bbox作为参考位置
// 设置坐标轴和宽高rect2.attr({x: -bbox.width / 2 - 24,y: -bbox.height / 2 - 1,width: 14,height: 10,});text2.attr({x: -bbox.width / 2 - 23,y: -bbox.height / 2 + 4,})
1.3 自定义节点样式
roup.addShape('dom', {attrs: {x: -bbox.width / 2 - 24 + 14, // 即:rect的坐标 + rect的宽 y: -bbox.height / 2 - 1,width: 10,height: 10,html: `<div style="border: 5px solid red;">自定义dom</div>`,},draggable: true,});
使用自定义dom,在 new G6.TreeGraph中 需要设置
renderer : 'svg', // 奇怪的是设置之后原来节点的布局有些影响
2 树图配置
2.1 允许使用自定义dom节点
renderer : 'svg',
2.2 内置行为
https://antv-g6.gitee.io/zh/docs/manual/middle/states/defaultBehavior#%E5%86%85%E7%BD%AE-behavior
modes: {default: [{type: 'collapse-expand',onChange: function onChange(item, collapsed) {const data = item.get('model');graph.updateItem(item, {collapsed,});data.collapsed = collapsed;return true;},},'drag-canvas', // 允许拖动'zoom-canvas', // ....],},
自定义边
defaultEdge: {type: 'cubic-horizontal',style: {stroke: 'red' //红色},},
layout布局
https://antv-g6.gitee.io/zh/docs/manual/middle/layout/tree-graph-layout
layout: {type: 'indented',direction: 'LR', // 节点从左向右分布dropCap: false,indent: 190,getHeight: () => {return 13;},getVGap: function getVGap () {return 10;},},
demo
<template><div class="main-content-box"><div id="container"></div></div>
</template><script>import G6 from '@antv/g6';export default {name: 'multTagsSec',data () {return {gDatas:{"id": "1","name": "storehouse A","children": [{"id": "2","name": "B","percentage": "60%","children": [{"id": "3","name": "storehouse C","percentage": "80%","children": [{"name": "storehouse C","percentage": "80%","children": [{"name": "D","percentage": "20%"},{"name": "storehouselllllll C","percentage": "20%"}]},{"name": "storehouse D","percentage": "20%"}]},{"name": "storehouse D","percentage": "20%"}]},{"name": "storehouse C","percentage": "100%"},{"name": "storehouse B","percentage": "20%"},{"name": "storehouse C","percentage": "20%"},{"name": "storehouse C","percentage": "20%","children": [{"name": "D","percentage": "20%"},{"name": "storehouse A","percentage": "20%"}]}]}}},mounted() {this.getInit();},methods: {getInit () {// var mycfg = null;G6.registerNode('tree-node', {drawShape: function drawShape(cfg, group) {// console.log(cfg)// --------------------标签内容节点----------------------var hasChildren = cfg.children && cfg.children.length > 0; // 是否有孩子节点var strokeColor = hasChildren == true ? 'red' : null // 有孩子 为红色// 节点设置 const rect = group.addShape('rect', {attrs: {fill: '#fff',stroke: strokeColor, // 边框颜色lineWidth: 1, // 边框粗细radius: 2,shadowBlur: 15,shadowColor: '#666',// shadowOffsetX: 2,// shadowOffsetY: 2},name: 'rect-shape',});// 文本设置const text = group.addShape('text', {attrs: {text: cfg.name, // 赋值name属性fontFamily: 'normal',fontSize: 11,fontWeight: 800,x: 0,y: 0,textAlign: 'left',textBaseline: 'middle',fill: '#666'},name: 'text-shape',});const bbox = text.getBBox(); // 获得文本的盒子 之后的两个节点的xy轴坐标参考bbox//const minbbox = rect.getBBox();// 设置 rect方框和text文本 的 x y坐标轴rect.attr({x: -bbox.width / 2 - 5,y: -bbox.height,// width: bbox.width + (hasChildren ? 20 : 12),width: bbox.width + 12 ,height: bbox.height + 8,});text.attr({x: -bbox.width / 2,y: -bbox.height / 2 + 3,})// -----------百分比节点----------var hasPercentage = cfg.percentage;var rect2 = 0;if(hasPercentage){// 节点设置 2rect2 = group.addShape('rect', {attrs: {fill: '#4682B4',stroke: '', // 边框颜色lineWidth: 0, // 边框粗细shadowBlur: 0,shadowColor: '',},name: 'rect-shape2',});// 文本设置 2const text2 = group.addShape('text', {attrs: {text: cfg.percentage, // 赋值name属性fontFamily: 'normal',fontSize: 5,fontWeight: 500,textAlign: 'left',textBaseline: 'middle',fill: 'white'},name: 'text-shape2',});// 设置坐标轴和宽高rect2.attr({x: -bbox.width / 2 - 24,y: -bbox.height / 2 - 1,width: 14,height: 10,});text2.attr({x: -bbox.width / 2 - 23,y: -bbox.height / 2 + 4,})// -------连接两个节点的小节点----------// const rect3 = group.addShape('rect', {// attrs: {// fill: '#00BFFF',// stroke: '', // 边框颜色// lineWidth: 0, // 边框粗细// shadowBlur: 0,// shadowColor: '',// },// name: 'rect-shape3',// });// rect3.attr({// x: -bbox.width / 2 - 24 + 14, // 即:rect的坐标 + rect的宽 // y: -bbox.height / 4 + 1,// width: 4,// height: 4// });// -------连接两个节点的小节点 三角形----------// 需要设置svg才能使用group.addShape('dom', {attrs: {x: -bbox.width / 2 - 24 + 14, // 即:rect的坐标 + rect的宽 y: -bbox.height / 2 - 1,width: 10,height: 10,html: `<div style="border-left: 5px solid red; border-right: 5px solid transparent;border-top: 5px solid transparent;border-bottom: 5px solid transparent;"></div>`,},draggable: true,});}// 小圆圈if (hasChildren) {const redcircle = group.addShape('marker', {attrs: {symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,// symbol: cfg.collapsed ? COLLAPSE_ICON : EXPAND_ICON,stroke: 'red',fill: 'red',lineWidth: 1.8,},name: 'collapse-icon',});redcircle.attr({x: bbox.width / 2 + 7,y: -3 ,r: 4,})}return rect;},update: (cfg, item) => {const group = item.getContainer();const icon = group.find((e) => e.get('name') === 'collapse-icon');icon.attr('symbol', cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse);},},'single-node',);const container = document.getElementById('container');const width = container.scrollWidth;const height = container.scrollHeight || 500;const graph = new G6.TreeGraph({renderer : 'svg', // 创建自定义DMO时定义 会报一个错 但好像不影响 container: 'container',width,height,modes: {default: [{type: 'collapse-expand',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: 'tree-node',anchorPoints: [[0, 0.5],[1, 0.5],],},// 设置边的参数defaultEdge: {type: 'cubic-horizontal',style: {stroke: 'red'},},layout: {type: 'indented',direction: 'LR',dropCap: false,indent: 190,getHeight: () => {return 13;},getVGap: function getVGap () {return 10;},},});graph.data(this.gDatas);graph.render();graph.fitView();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><style scoped></style>