最近做大屏用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: []
}
出来的效果如图:
image.png
这里有个比较大的问题,标签显示会重叠在一起。我给标签设置了事件,鼠标放上去之后,对应标签会亮起,其它会变暗。然而,客户仍然希望不要出现遮挡的情况。
查了一下echart文档的scatter部分,labelLayout有两个跟标签重叠相关的参数。
image.png
hideOverlap参数会在标签重叠时隐藏一部分。这是一种解决办法,不过客户希望显示所有标签,不要隐藏。
image.png
第二个参数是moveOverlap, 在重叠时它能将图标移动放置重叠。这时labelLayout需要以函数的方式传参。
labelLayout: function(params) {
return {
x: params.rect.x,
moveOverlap: 'shiftY'
}
},
实现的效果如图:
image.png
虽然不重叠了,但看起来有点凌乱。而且当图标超过一定数量,其实还是会重叠。所以,它只是优化,尽可能不重叠,但不能确保不重叠。
image.png
我想到了echart里面还有个关系图,关系图有力导向布局,图标可以进行排斥,然而试了半天,关系图似乎不能使用地图坐标。而且设置坐标点也挺麻烦的。于是我打算自己设计一个排布算法。
仍然使用labelLayout参数,然而返回的dx, dy 我需要自己计算。
我需要有一个方法得到标签的偏移值。
labelLayout: function(params) {
var dPos = posHandler.getDeltaPos(params);
return {
dx: dPos.dx,
dy: dPos.dy
}
},
现在试想一下, 我把地图划分成固定的格子。
image.png
如此每一个散点都会在一个格子里面。当labelLayout方法执行时,从params里面可以得到这些参数
image.png
其中params.rect是散点映射到画布的矩形对象。通过x,y 除以 格子的长宽取整,可以知道散点在哪个格子。
如果这个格子是空的,就把它分配给该标签,如果非空,则按照一个九宫格的顺序,在它的附近查找空的格子。如果找一圈找不到,以最后查找的格子作为当前格子继续查找。
image.png
函数的主体部分是这样的:
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();
})
最后实现的效果,不管有多少标签,都会整齐排布,而且缩放之后会自动调整:
image.png
网友评论