antvis/G6

自定义节点高度不一致的时候, 节点无法居中对齐, 导致直线变成了折线 #5231

oo10 posted onGitHub

问题描述

minmap 里自定义节点高度不一致的时候, 直线变成了折线相连, 节点无法居中对齐。 请问应该如何解决?

重现链接

*

重现步骤

        const TREELIST = {
            id: '1',
            name: '分析',
            children: [
                {
                    id: '1-1指标ID',
                    name: '收入',
                    compareText: '环比',
                    contributionText: '贡献度',
                    nodeType: 'IND',
                    nowValue: 19,
                    hisValue: 10,
                    changeRate: 0.1,
                    contributionDegree: 1.2,
                    children: [
                        {
                            id: '1-1-1',
                            name: '利息收入',
                            children: [
                                {
                                    id: '1-1-1-1',
                                    name: '利息收入111'
                                },
                                {
                                    id: '1-1-1-2',
                                    name: '罚息收入222'
                                }
                            ]
                        },
                        {
                            id: '1-1-2',
                            name: '罚息收入'
                        }
                    ]
                },
                {
                    id: '1-2',
                    name: '支出',
                    compareText: '环比下降',
                    contributionText: '贡献度',
                    nodeType: 'IND',
                    nowValue: 17888,
                    hisValue: 10,
                    changeRate: -0.9,
                    contributionDegree: -1.9,
                    children: [
                        {
                            id: '1-5-1',
                            name: '第三层1',
                            nodeType: 'IND',
                            children: [
                                {
                                    id: '1-5-1-1',
                                    name: '第四层1第四层1第四层1第四层1第四层1',
                                    children: [
                                        {
                                            id: '1-5-1-1-1',
                                            name: '支出111',
                                            nodeType: 'IND'
                                        }
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
            //     }
            // ]
        };

// 文本超出隐藏 (字段, 最大长度, 字体大小) const fittingString = (str, maxWidth, fontSize) => { str = String(str); 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.nodeType === 'IND') { rect = group.addShape('rect', { attrs: { x: 0, // x 轴移动距离 y: 0, // y 轴移动距离 width: 180, // 宽 height: 120, // 高 fill: '#EBF0FA', stroke: '#EBF0FA', // 边框色 // fontSize: 16, fontWeight: 600, radius: 4 }, name: 'big-rect-shape-IND' }); // 指标名称 group.addShape('text', { attrs: { text: fittingString(cfg.name, 180, 14), x: 12, // y + padding y: 8 + 4, fontSize: 14, textAlign: 'left', textBaseline: 'top', fill: '#22365E', lineHeight: 22 }, name: 'text-shape' }); // 指标数据 group.addShape('text', { attrs: { text: fittingString(cfg.nowValue, 180, 20), x: 12, // y + padding y: 28 + 4, fontSize: 20, textAlign: 'left', textBaseline: 'top', fill: '#000000', lineHeight: 28 }, name: 'text-shape' });

                    // 分隔直线
                    group.addShape('path', {
                        attrs: {
                            path: [
                                ['M', 78 + 12, 60],
                                ['L', 78 + 12, 61]
                            ],
                            stroke: '#d8e1f5',
                            lineWidth: 156,
                            lineAppendWidth: 1
                        },
                        // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性
                        name: 'path-shape'
                    });

                    // 对比数据
                    group.addShape('text', {
                        attrs: {
                            text: fittingString(cfg.compareText, 180, 14),
                            x: 12,
                            // y + padding
                            y: 65 + 4,
                            fontSize: 14,
                            textAlign: 'left',
                            textBaseline: 'top',
                            fill: '#000000',
                            lineHeight: 22
                        },
                        name: 'text-shape'
                    });

                    group.addShape('text', {
                        attrs: {
                            text: fittingString(cfg.changeRate * 100 + '%', 180, 14),
                            x: 180 - 12,
                            // y + padding
                            y: 65 + 4,
                            fontSize: 14,
                            textAlign: 'right',
                            textBaseline: 'top',
                            fill: cfg.changeRate > 0 ? '#E53232' : '#4DB378',
                            lineHeight: 22
                        },
                        name: 'text-shape'
                    });

                    // 贡献度数据
                    group.addShape('text', {
                        attrs: {
                            text: fittingString(cfg.contributionText, 180, 14),
                            x: 12,
                            // y + padding
                            y: 89 + 4,
                            fontSize: 14,
                            textAlign: 'left',
                            textBaseline: 'top',
                            fill: '#000000',
                            lineHeight: 22
                        },
                        name: 'text-shape'
                    });
                    if (cfg.children && cfg.children.length > 0) {
                        group.addShape('marker', {
                            attrs: {
                                x: 180 + 10,
                                y: 60,
                                r: 6,
                                symbol: cfg.collapsed ? G6.Marker.expand : G6.Marker.collapse,
                                stroke: '#666',
                                fill: '#fff',
                                lineWidth: 1,
                                cursor: 'pointer' // 鼠标变手
                            },
                            name: 'collapse-icon'
                        });
                    }
                } else {
                    rect = group.addShape('rect', {
                        attrs: {
                            x: 0, // x 轴移动距离
                            y: 0, // y 轴移动距离
                            width: getTextSize(cfg.name, 180, 16) + 20, // 宽
                            height: 40, // 高
                            fill: '#EBF0FA',
                            stroke: '#EBF0FA', // 边框色
                            fontSize: 16,
                            fontWeight: 600,
                            radius: 4
                        },
                        name: 'big-rect-shape'
                    });
                    group.addShape('text', {
                        attrs: {
                            text: fittingString(cfg.name, 180, 16),
                            x: 10,
                            y: 20,
                            fontSize: 16,
                            textAlign: 'left',
                            textBaseline: 'middle',
                            fill: '#000'
                        },
                        name: 'text-shape'
                    });
                }

                if (cfg.depth === 0) {
                    rect = group.addShape('rect', {
                        attrs: {
                            x: 0, // x 轴移动距离
                            y: 0, // y 轴移动距离
                            width: getTextSize(cfg.name, 180, 20) + 48, // 宽
                            height: 60, // 高
                            fill: '#466EC0',
                            stroke: '#466EC0', // 边框色
                            fontSize: 16,
                            fontWeight: 600,
                            radius: 4
                        },
                        name: 'big-rect-shape'
                    });
                    group.addShape('text', {
                        attrs: {
                            text: fittingString(cfg.name, 180, 20),
                            x: 24,
                            y: 30,
                            fontSize: 20,
                            textAlign: 'left',
                            textBaseline: 'middle',
                            fill: '#fff'
                        },
                        name: 'text-shape'
                    });
                }

                return rect;
            },
            // afterDraw: function (cfg, group) {
            //     const children = group.get('children');
            //     console.log(children.length);
            //     console.log(cfg, group);
            //     // 将所有图形平移
            //     children.forEach((child) => {
            //         const { x, y } = child.attr();
            //         const size = [cfg.width, cfg.height]
            //         console.log(99999, height)
            //         console.log('cfg.size', cfg.size);
            //         child.attr({
            //             x: x + size.width / 2,
            //             y: y + size.height / 2
            //         });
            //     });
            // },
            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) {
                console.log(cfg);
                const startPoint = cfg.startPoint;
                const endPoint = cfg.endPoint;
                let shape = group.addShape('path', {
                    // 线条
                    attrs: {
                        stroke: '#dfe2ec',
                        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: '#dfe2ec',
                            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-container').scrollWidth || 1600;
        const height = document.getElementById('g6-container').scrollHeight || 960;
        // 实例化G6
        // 因为我们用的是树图,所以这里是G6.TreeGraph(),还有其他,像是普通图的配置G6.Graph(),一般y用的比较多的就像是树图这种,还有组织架构图一类的。
        const graph = new G6.TreeGraph({
            // 图的  DOM 容器,对应上面我们定义的id
            container: 'g6-container',
            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
                },
                anchorPoints: [
                    [1, 0.5],
                    [0, 0.5]
                ]
            },
            // 配置边的属性
            defaultEdge: {
                // 指定边的类型,可以是内置边的类型名称,也可以是自定义边的名称。
                // line:直线,polyline:折线,arc:圆弧线,quadratic:二阶贝塞尔曲线,cubic:三阶贝塞尔曲线,cubic-vertica:垂直方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。cubic-horizontal:水平方向的三阶贝塞尔曲线,不考虑用户从外部传入的控制点。loop:自环
                type: 'kaimo-line'
            },
            // 布局配置项
            layout: {
                // 布局名称,这个可就太多了,这个只是树图结构中的一种。比如还有compactBox:紧凑树布局,dendrogram:生态树布局,indented:缩紧树布局。
                type: 'mindmap', // 脑图树布局
                direction: 'H', // H / V / LR / RL / TB / BT 这些是控制节点分布位置,从左往右、从右往左、从中间往上下延伸、从中间往左右延伸...具体可以看官网
                workerEnabled: true, // 开启 Web-Worker
                // 节点 id 的回调函数
                getId: function getId(d) {
                    return d.id;
                },
                // 下面都是一些控制节点与节点间距离的回调函数,具体可以试着修改一下值。
                // 节点高度的回调函数
                getHeight: function getHeight(cfg) {
                    console.log(cfg);
                    return 120;
                },
                // 节点宽度的回调函数
                getWidth: function getWidth(cfg) {
                    return 180;
                },
                // 节点纵向间距的回调函数
                getVGap: function getVGap() {
                    return 10;
                },
                // 节点横向间距的回调函数
                getHGap: function getHGap(val) {
                    return 30;
                },
                // 这个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: false
        });
        // 默认全部展开
        G6.Util.traverseTree(data, function (item) {
            item.collapsed = false;
        });
        // 初始化的图数据
        graph.data(data);
        // 根据提供的数据渲染视图。
        graph.render();
        // 让画布内容适应视口
        graph.fitView();

预期行为

只有单个节点的时候,应该垂直居中用直线相连。

平台

  • 操作系统: [macOS, Windows, Linux, React Native ...]
  • 网页浏览器: [Google Chrome, Safari, Firefox]
  • G6 版本: [4.5.1 ... ]

屏幕截图或视频(可选)

<img width="1112" alt="image" src="https://github.com/antvis/G6/assets/15247637/acd5c670-e709-4551-bf71-8c681076cd8c">

补充说明(可选)

No response


hi @oo10, welcome!

posted by github-actions[bot] over 1 year ago

Hi @oo10, Please star this repo if you find it useful! Thanks :star:! 你好 @oo10。如果该仓库对你有用,可以 star 一下,感谢你的 :star:!

posted by github-actions[bot] over 1 year ago

加上borderRadius就更离谱了

posted by yuguaa over 1 year ago

目前找到思路, draw的时候判断跟父节点高度是否一致,子节点的Y加上 (parentHeight - height) / 2 的偏移,但是如何在draw的时候获取父节点的高度呢? 或者有没有更省事的办法, 让只有一个子节点的时候垂直居中对齐,变成直线呢? 求教~

posted by oo10 over 1 year ago
posted by yuguaa over 1 year ago

目前我是强制分组,组的高度一致,动态draw y的偏移量

posted by yuguaa over 1 year ago

同问呀

posted by keminu 10 months ago

遇到同样的问题,只有一个子节点时高度不一致,又不是垂直居中,导致不是直线

posted by keminu 10 months ago

经过试验后可以这样解决: 1、在layout配置中,getHeight高度根据不同的节点取节点keyShape的高度; 2、自定义节点中draw函数keyShape的原点位置y取0; 3、anchorPoints取中心就行; 3、所有节点都得是上述的配置; 4、V 方向对齐也是类似的配置;

posted by keminu 10 months ago

补充: 节点的宽度和高度可以不一样的,重点是getHeight动态获取高度

posted by keminu 10 months ago

补充: 节点的宽度和高度可以不一样的,重点是getHeight动态获取高度

getHeight如何获取keyShape的高度?我打印出来无法得到

posted by warriorhj 9 months ago

Fund this Issue

$0.00
Funded

Pull requests