Echarts Bar 柱状图标签label重叠问题处理

本文介绍了如何在ECharts中处理堆叠柱状图中标签的重叠问题,通过检测像素位置并调整标签偏移来确保清晰显示。作者提供了代码示例,展示了如何获取柱子信息、计算标签位置以及应用偏移策略。
摘要由CSDN通过智能技术生成

先展示一下效果:

处理前

处理后

整体思路:

        通过获取每个标签label的像素位置看是否跟别的标签label有重叠,如果重叠了就在柱子范围内进行位置偏移。

实现步骤:

        1.先生成echarts图像,然后获取图像里每个柱子的起始像素位置、高度、宽度。echarts对象里存在属性,可以直接获取到每个柱子bar的信息。如图所示:

         对于标签label设置为inside的情况,可以知道标签label的默认位置都是在柱子的中间,所以标签label的像素位置也是能确定的。 为了即使在柱子特别小的情况下也能显示标签label,建议设置柱子的最小高度barMinHeight。(堆叠柱状图这个属性有bug,简单柱状图可以使用)

         2.遍历标签label像素位置,判断是否重叠。我这里是拿当前的标签label跟之前已经生成好的进行比较。如果标签label重叠,需要对标签label的位置进行调整。echarts 可以对单个data的标签label位置进行偏移调整,如图:

        乐观情况就是标签label有合适的位置被找到,但是也存在所有的地方都试过之后都有重叠,这种情况暂时没有处理,只能重叠显示(可以考虑不显示)。

代码实现:

注:我这里是堆叠柱状图,共有三组数据。

主程序: 

let color = ['#e58361', '#47982b', '#3dc7f5'];
let pxH = 12;   // 一个数字的像素高度
let radix = 3; // 偏移基数

myChart && myChart.dispose();   // echarts 对象

myChart = echarts.init(document.getElementById('chart'));

// 绘制图表
    let option = {
        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'shadow'
            }
        },
        grid: {
            left: '0%',
            right: '4%',
            bottom: '5%',
            containLabel: true
        },
        xAxis: [
            {
                type: 'category',
                axisLabel: {
                    interval: 0,
                    rotate: 40
                },
                data: []
            }
        ],
        yAxis: [
            {
                type: 'value'
            }
        ],
        series: [
            {
                name: 'Item1Names',
                type: 'bar',
                stack: 'Ad',
                barMinHeight: 20,
                itemStyle: {
                    normal: {
                        color: color[0]
                    }
                },
                data: []
            }, {
                name: 'Item2Names',
                type: 'bar',
                stack: 'Ad',
                barMinHeight: 20,
                itemStyle: {
                    normal: {
                        color: color[1]
                    }
                },
                data: []
            }, {
                name: 'Item3Names',
                type: 'bar',
                stack: 'Ad',
                barMinHeight: 20,
                itemStyle: {
                    normal: {
                        color: color[2]
                    }
                },
                data: []
            }
        ]
    }

    // pointsTable为数据表,数据结构:
    // {
    //     values: [
    //         {
    //             date: "", 
    //             Item1: 1,
    //             Item2: 2,
    //             Item3: 3,
    //         }
    //     ],
    //     max: 3     // Item1、Item2、Item3 的最大值,用来判断label的最大像素长度
    // }


    option.xAxis[0].data = pointsTable.values.map(i => {  
        return i.date;
    })

    option.series[0].data.length = 0;
    option.series[1].data.length = 0;
    option.series[2].data.length = 0;

    pointsTable.values.forEach(i => {
        option.series[0].data.push(i.Item1);
        option.series[1].data.push(i.Item2);
        option.series[2].data.push(i.Item3);
    })

myChart.setOption(option);  // 先生成图像才能获取到柱子bar的像素信息

let offFlag = false;   // 重叠处理标志位,如果为true说明已经处理过了

myChart.on('finished', () => {
        // 柱状图加载完之后,获取柱子像素信息,然后计算标签位置
        if (offFlag) return;
        let echartPX = getEchartPX(myChart);   // getEchartPX:获取echarts的像素信息
        let offSet = changeLabelPosition(echartPX[0], echartPX[1], myChart, pointsTable.max);    // changeLabelPosition:计算每个标签的偏移量


        // 设置偏移量
        option.series[0].data = option.series[0].data.map((d, i) => {
            return {value: d, label: {normal: {offset: [0, offSet[0][i]]}}}
        })
        option.series[1].data = option.series[1].data.map((d, i) => {
            return {value: d, label: {normal: {offset: [0, offSet[1][i]]}}}
        })
        option.series[2].data = option.series[2].data.map((d, i) => {
            return {value: d, label: {normal: {offset: [0, offSet[2][i]]}}}
        })

        option.label = {
            show: true,
            position: 'inside',
            color: '#000'
        }

        myChart.setOption(option);

        offFlag = true;
    })

getEchartPX方法: 

// 获取整个图表的像素信息
function getEchartPX(chart) {
    let barWidth = -1;    // 柱子宽度
    let barHeights = [];  // 有三个系列,包括了每个柱子的像素信息

    chart._chartsViews.forEach(bars => {  // 获取每一个小serious的bar像素信息
        let barH = []   // 单个系列的柱子像素信息
        bars.group._children.forEach(bar => {  // 每个x对应bar的像素信息
            let shape = bar.shape;
            if (shape) {
                barH.push([shape.x, shape.y, 0 - shape.height])   // 像素信息: [横坐标x, 纵坐标y, 高度height]
                barWidth = shape.width     // 柱子宽度
            }
        })
        barHeights.push(barH)
    })

    return [barWidth, barHeights];
}

 changeLabelPosition方法:

// 调整label位置信息
function changeLabelPosition(barWidth, barHeights, chart, max) {
    let maxPx = max.toString().length * 8  // 数据最长占用像素
    let minSpaceBar = Math.ceil(maxPx * 100 / barWidth)  // 最小不重叠间隔 = 最长数据 / 柱子宽,  向上取整

    // 算当前label的像素位置
    let position = []
    barHeights.forEach(it => {
        let posi = []
        it.forEach(bar => {
            posi.push(bar[1] - bar[2] / 2)
        })
        position.push(posi)
    })

    let offset = [];  // 存储所有的偏移位置

    // 重叠判断
    position.forEach((p, pi) => {   // p : 不同系列
        let off = [0];  // 默认第一个不偏移
        for (let i = 1; i < p.length; i++) {  // 遍历系列内的数据
            let ind = 1,  // 计数器,往前数柱子
                calFlag = true, // f:上移,t:下移
                offVal = 0; // 当前偏移值
            let barH = barHeights[pi][i][2],
                offCount = Math.floor(barH / pxH) // 看当前的柱子可以有多少个偏移值

            while (ind <= minSpaceBar && i - ind >= 0) {   // 跟之前的几个标签判断是否重叠
                if (Math.abs(p[i] + offVal - p[i - ind] - off[i - ind]) < pxH ||    // 跟当前系有重叠
                    (pi === 1 && (Math.abs(p[i] + offVal - position[0][i - ind] - offset[0][i - ind]) < pxH ||  // 跟上一个系有重叠
                        Math.abs(p[i] + offVal - position[0][i + ind] - offset[0][i + ind]) < pxH ||
                        Math.abs(p[i] + offVal - position[0][i] - offset[0][i]) < pxH)) ||  // 跟自己有重叠
                    (pi === 2 && (Math.abs(p[i] + offVal - position[1][i - ind] - offset[1][i - ind]) < pxH ||
                        Math.abs(p[i] + offVal - position[1][i + ind] - offset[1][i + ind]) < pxH ||  // 跟前两个系有重叠
                        Math.abs(p[i] + offVal - position[0][i - ind] - offset[0][i - ind]) < pxH ||
                        Math.abs(p[i] + offVal - position[0][i + ind] - offset[0][i + ind]) < pxH ||
                        Math.abs(p[i] + offVal - position[1][i] - offset[1][i]) < pxH ||    // 跟自己有重叠
                        Math.abs(p[i] + offVal - position[0][i] - offset[0][i]) < pxH))) {

                    // 标签重叠了
                    if (offCount < 1) {
                        // 偏移值太少了,无法偏移
                        break;
                    } else {
                        // 寻找合适的偏移位置
                        offVal += calFlag ? pxH / radix : -pxH / radix;
                        if (p[i] + offVal > barHeights[pi][i][1]) { // 超出柱子下限
                            offVal = -pxH / radix; // 改成向上偏移
                            calFlag = false;
                        }
                        if (p[i] + offVal < (barHeights[pi][i][1] - barH)) { // 超出柱子上限
                            offVal = 0;
                            break;
                        }
                    }
                    ind = 0;
                }
                ind++;
            }
            off.push(offVal);
        }
        offset.push(off)
    })

    // 二次判断,判断当前标签跟前后几个柱子的所有标签看是否重叠,重叠再进行偏移
    // 原因:主要是有部分后面生成的柱子没有多的偏移量,只能让前面的柱子调整位置
    position.forEach((p, pi) => {
        for (let i = 1; i < p.length; i++) {  // 遍历系列内的数据
            let ind = 1,  // 计数器
                calFlag = true, // f:上移,t:下移
                offVal = 0; // 当前偏移值
            let barH = barHeights[pi][i][2],
                offCount = Math.floor(barH / pxH) // 看当前的柱子可以有多少个偏移值

            while (ind < minSpaceBar && i - ind >= 0) {   // 跟之前的几个标签判断是否重叠
                if ((pi === 0 && (Math.abs(p[i] + offset[0][i] + offVal - position[1][i] - offset[1][i]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[2][i] - offset[2][i]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[0][i - ind] - offset[0][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[1][i - ind] - offset[1][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[2][i - ind] - offset[2][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[0][i + ind] - offset[0][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[1][i + ind] - offset[1][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[0][i] + offVal - position[2][i + ind] - offset[2][i + ind]) < pxH
                    ))
                    || (pi === 1 && (Math.abs(p[i] + offset[1][i] + offVal - position[0][i] - offset[0][i]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[2][i] - offset[2][i]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[0][i - ind] - offset[0][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[1][i - ind] - offset[1][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[2][i - ind] - offset[2][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[0][i + ind] - offset[0][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[1][i + ind] - offset[1][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[1][i] + offVal - position[2][i + ind] - offset[2][i + ind]) < pxH))
                    || ((pi === 2 && (Math.abs(p[i] + offset[2][i] + offVal - position[0][i] - offset[0][i]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[1][i] - offset[1][i]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[0][i - ind] - offset[0][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[1][i - ind] - offset[1][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[2][i - ind] - offset[2][i - ind]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[0][i + ind] - offset[0][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[1][i + ind] - offset[1][i + ind]) < pxH ||
                        Math.abs(p[i] + offset[2][i] + offVal - position[2][i + ind] - offset[2][i + ind]) < pxH)))) {
                    // 标签重叠了
                    if (offCount < 1) {
                        // 偏移值太少了,无法偏移
                        break;
                    } else {
                        // 寻找合适的偏移位置
                        offVal += calFlag ? pxH / radix : -pxH / radix;
                        if (p[i] + offset[pi][i] + offVal > barHeights[pi][i][1]) { // 超出柱子下限
                            offVal = -pxH / radix; // 改成向上偏移
                            calFlag = false;
                        }
                        if (p[i] + offset[pi][i] + offVal < (barHeights[pi][i][1] - barH)) { // 超出柱子上限
                            offVal = 0;
                            break;
                        }
                    }
                    ind = 0;
                }
                ind++;
            }
            offset[pi][i] += offVal;
        }
    })

    return offset;
}

支持原创!!
欢迎加入讨论!!!

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值