echarts:地图上标签重叠问题解决

最近做大屏用echart里面的矢量地图,上面需要显示一些标签。 标签使用常规的做法,即用散点图,坐标则设为地图坐标:

   var seriesData= {
            coordinateSystem: 'geo',
            symbolSize: 12,
            type: 'scatter',
            labelLine: {
                show: true,
                length2: 15,
                smooth: 0.2,
                minTurnAngle: 90,
                lineStyle: {
                    color: '#fff',
                    width: 2
                }
            },
            label: {
                color: '#fff',
                backgroundColor: 'rgba(219, 218, 217, 0.8)',
                borderColor: '#8C8D8E',
                borderWidth: 1,
                lineHeight: 25,
                padding: 0,
                show: true,
                position: 'right',
                rich: {
                // 此处省略富文本定义
                }
            },
            labelLayout: {
                dy: -40,
                dx: 20,
                align: 'left'
            },
           data: []
}


这里有个比较大的问题,标签显示会重叠在一起。我给标签设置了事件,鼠标放上去之后,对应标签会亮起,其它会变暗。然而,客户仍然希望不要出现遮挡的情况。

查了一下echart文档的scatter部分,labelLayout有两个跟标签重叠相关的参数。

hideOverlap参数会在标签重叠时隐藏一部分。这是一种解决办法,不过客户希望显示所有标签,不要隐藏。
第二个参数是moveOverlap, 在重叠时它能将图标移动放置重叠。这时labelLayout需要以函数的方式传参。

    labelLayout: function(params) {
                return {
                    x: params.rect.x,
                    moveOverlap: 'shiftY'
                }
            },

虽然不重叠了,但看起来有点凌乱。而且当图标超过一定数量,其实还是会重叠。所以,它只是优化,尽可能不重叠,但不能确保不重叠。
我想到了echart里面还有个关系图,关系图有力导向布局,图标可以进行排斥,然而试了半天,关系图似乎不能使用地图坐标。而且设置坐标点也挺麻烦的。于是我打算自己设计一个排布算法。

仍然使用labelLayout参数,然而返回的dx, dy 我需要自己计算。
我需要有一个方法得到标签的偏移值。

   labelLayout: function(params) {
                var dPos = posHandler.getDeltaPos(params);
                return {
                    dx: dPos.dx,
                    dy: dPos.dy
                }
            },

现在试想一下, 我把地图划分成固定的格子。
如此每一个散点都会在一个格子里面。当labelLayout方法执行时,从params里面可以得到这些参数
其中params.rect是散点映射到画布的矩形对象。通过x,y 除以 格子的长宽取整,可以知道散点在哪个格子。
如果这个格子是空的,就把它分配给该标签,如果非空,则按照一个九宫格的顺序,在它的附近查找空的格子。如果找一圈找不到,以最后查找的格子作为当前格子继续查找。

函数的主体部分是这样的:

var posHandler = {
    posMap: {},
    reset: function() {
        this.posMap = {};
    },
    getDeltaPos: function(params){
            var rect = params.rect;
            var labelWidth = 160, labelHeight = 30;
            var gridx = Math.floor(rect.x / labelWidth);
            var gridy = Math.floor(rect.y / labelHeight);
            var currCell = [gridx, gridy], currPos = [];
            var increaseArr = [[0, -1], [1, -1], [1, 0], [1, 1], [0, 1], [-1, -1], [-1, 0], [-1, -1]];
            // 将显示区域划分成一个个定宽和定高的区域
            // 将标签放在离它最近的区域
            // 如果最近的那格没有,向周围九宫格上面找
            // 找到一个没有放过的,分配给它,然后得出一个偏移量
            var found = false;
            // 如果格子已被占,循环查找
            if(this.posMap[currCell[0] + '-' + currCell[1]]) {
                while(!found) {
                    for(var i = 0;i<increaseArr.length;i++) {
                        currCell[0] = currCell[0] + increaseArr[i][0];
                        currCell[1] = currCell[1] + increaseArr[i][1];
                        if (!this.posMap[currCell[0] + '-' + currCell[1]]) {
                            found = true;
                            this.posMap[currCell[0]+'-'+currCell[1]] = params.text;
                            currPos = [currCell[0]* labelWidth, currCell[1] * labelHeight];
                            break;
                        }
                    }
                    if(found) {
                        break;
                    }
                }
            } else {
                // 如果格子没有被占,就它了
                this.posMap[gridx + '-' + gridy] = params.text
            }
            currPos = [currCell[0]* labelWidth, currCell[1] * labelHeight];
            var deltaPos = {
                dx: currPos[0] - rect.x,
                dy: currPos[1] - rect.y
            }
            return deltaPos;
        }
      };

算法的关键在于通过posMap对象记录已经分配的格子,由于在地图缩放时,param.rect坐标会发生变化,所以,要在缩放之后将posMap对象清空再通过调用实例的resize方法重新计算。

  myMap.on('georoam', function() {
                console.log('resize');
                posHandler.reset();
                myMap.resize();
            })

最后实现的效果,不管有多少标签,都会整齐排布,而且缩放之后会自动调整:


本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):