一、饼图label线的布局优化
labelLayoutList为初始布局位置信息;如上图,可看到name1到name6是重叠的状态
var labelLayoutList = [
{
"x": 23.40783269959437,
"y": -114.94949619851135,
"data": {
"name": "name1",
"value": 1
},
"position": "out",
"side": "right",
"height": 17,
"len": 15,
"len2": 15,
"linePoints": [
[
2.963332782255974,
-99.95608365087944
],
[
3.40783269959437,
-114.94949619851135
],
[
18.40783269959437,
-114.94949619851135
]
],
"textAlign": "start",
"inside": false
},
...
]
1.1 将labelLayoutList以side属性分成左、右两个部分
function avoidOverlap(labelLayoutList, radius, width, height) {
var leftList = [];
var rightList = [];
for (var i = 0; i < labelLayoutList.length; i++) {
if (isPositionCenter(labelLayoutList[i])) {
continue;
}
if (labelLayoutList[i].side === 'left') {
leftList.push(labelLayoutList[i]);
} else {
rightList.push(labelLayoutList[i]);
}
}
}
1.2 用adjustSingleSide函数调整右侧的布局(左侧同理)
function adjustSingleSide(list, radius, dir, width, height) {
//
}
1.2.1 先调整右侧、再调整左侧
function avoidOverlap(labelLayoutList, radius, width, height) {
...
adjustSingleSide(rightList, radius, 1, width, height);
// adjustSingleSide(leftList, radius, -1, width, height);
}
1.2.2 adjustSingleSide函数
1.2.3 按属性y进行正向排序
function adjustSingleSide(list, radius, dir, width, height) {
list.sort(function (a, b) {
return a.y - b.y;
});
}
1.2.4 定义label下移、上移位置的内部函数shiftDown、shiftUp
function adjustSingleSide(list, radius, dir, width, height) {
list.sort(function (a, b) {
return a.y - b.y;
});
function shiftDown(start, end, delta, dir) {
for (var j = start; j < end; j++) {
list[j].y += delta;
if (j > start
&& j + 1 < end
&& list[j + 1].y > list[j].y + list[j].height
) {
shiftUp(j, delta / 2);
return;
}
}
shiftUp(end - 1, delta / 2);
}
function shiftUp(end, delta) {
for (var j = end; j >= 0; j--) {
list[j].y -= delta;
if (j > 0
&& list[j].y > list[j - 1].y + list[j - 1].height
) {
break;
}
}
}
}
1.2.5 调整代码实现
function adjustSingleSide(list, radius, dir, width, height) {
...
var lastY = Number.NEGATIVE_INFINITY
var delta;
var len = list.length;
list.forEach((d, i) => {
delta = d.y - lastY;
if (delta < 0) {
shiftDown(i, len, -delta, dir);
}
lastY = d.y + d.height;
});
if (height / 2 - lastY < 0) {
shiftUp(len - 1, lastY - height);
}
}
1.2.6 以第1个label开始进行调整步骤
(1)以第1个label为基准,lastY = list[0].y + list[0].height;从第2个label到第8个label(先布局右侧的label)依次查看是否与第1个label有重叠,即delta = list[i].y - lastY,delta < 0则为重叠
(2)若label有重叠,则依次将这些label往下移动-delta值直到没有重叠则停止,即调用shiftDown函数,list[i].y += -delta
(3)停止条件(没有重叠):list[i + 1].y > list[i].y + list[i].height
(4)在下移重叠的label时,下一个label与当前的label没有重叠,则从当前的label到第1个label,依次上移半个(-delta / 2)的高度直到没有重叠则停止,即list[i].y -= (-delta / 2)
(5)停止条件(没有重叠):list[i].y > list[i - 1].y + list[i - 1].height
1.2.7 依次以第2、3、4、5、6、7、8个label开始重复1.2.4的步骤,直到没有重叠
...
1.2.8 调整label.x的位置
function adjustSingleSide(list, radius, dir, width, height) {
...
function changeX(list, isDownList, radius, dir) {
var lastDeltaX = dir > 0
? isDownList // right-side
? Number.MAX_VALUE // down
: 0 // up
: isDownList // left-side
? Number.MAX_VALUE // down
: 0; // up
for (var i = 0, l = list.length; i < l; i++) {
var deltaY = Math.abs(list[i].y);
var length = list[i].len;
var length2 = list[i].len2;
var deltaX = (deltaY < radius + length)
? Math.sqrt(
(radius + length + length2) * (radius + length + length2) - deltaY * deltaY
)
: Math.abs(list[i].x);
if (isDownList && deltaX >= lastDeltaX) {
// right-down, left-down
deltaX = lastDeltaX - 10;
}
if (!isDownList && deltaX <= lastDeltaX) {
// right-up, left-up
deltaX = lastDeltaX + 10;
}
list[i].x = deltaX * dir;
lastDeltaX = deltaX;
}
}
var upList = [];
var downList = [];
for (var i = 0; i < len; i++) {
if (list[i].y >= 0) {
downList.push(list[i]);
} else {
upList.push(list[i]);
}
}
changeX(upList, false, radius, dir);
// changeX(downList, true, radius, dir);
}
...
1.2.9 调整polyline的x,y位置
function avoidOverlap(labelLayoutList, radius, width, height) {
...
for (var i = 0; i < labelLayoutList.length; i++) {
if (isPositionCenter(labelLayoutList[i])) {
continue;
}
var linePoints = labelLayoutList[i].linePoints;
if (linePoints) {
var dist = linePoints[1][0] - linePoints[2][0];
if (labelLayoutList[i].x < 0) {
linePoints[2][0] = labelLayoutList[i].x + 3;
} else {
linePoints[2][0] = labelLayoutList[i].x - 3;
}
linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
linePoints[1][0] = linePoints[2][0] + dist;
}
}
}
(1)保存线拐点和末端的距离位dist = linePoints[1][0] - linePoints[2][0]
(2)与将线的末端放置在label计算后的x位置附近,即linePoints[2][0] = labelLayoutList[i].x + 3
(3)调整线拐点和末端的y值,即linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y
(4)调整线拐点的x值,即linePoints[1][0] = linePoints[2][0] + dist
...
1.3 再用adjustSingleSide函数调整左侧的布局(与右侧同理,略)
二、完整代码
function isPositionCenter(layout) {
// center类型的label不需要更改x
return layout.position === 'center';
}
function adjustSingleSide(list, radius, dir, width, height) {
list.sort(function (a, b) {
return a.y - b.y;
});
function shiftDown(start, end, delta, dir) {
console.log('shiftDown============>')
for (var j = start; j < end; j++) {
console.log(`${list[j].data.name} start:${start}, j:${j}; ${list[j].y} + ${delta} = ${list[j].y + delta}`)
list[j].y += delta;
if (j > start
&& j + 1 < end
&& list[j + 1].y > list[j].y + list[j].height
) {
console.log('下一个label与当前的label没有重叠')
console.log(`${list[j].data.name} start:${start}, j:${j}; ${list[j + 1].data.name} > ${list[j].data.name} + height`)
console.log(`${list[j].data.name} start:${start}, j:${j}; ${list[j + 1].y} > ${list[j].y + list[j].height} (${list[j].y} + ${list[j].height})`)
shiftUp(j, delta / 2);
return;
}
}
console.log('==========================')
shiftUp(end - 1, delta / 2);
}
function shiftUp(end, delta) {
console.log(' shiftUp============>')
for (var j = end; j >= 0; j--) {
console.log(` ${list[j].data.name} end:${end}, j:${j}; ${list[j].y} - ${delta} = ${list[j].y - delta}`)
list[j].y -= delta;
if (j > 0
&& list[j].y > list[j - 1].y + list[j - 1].height
) {
break;
}
}
}
function changeX(list, isDownList, radius, dir) {
var lastDeltaX = dir > 0
? isDownList // right-side
? Number.MAX_VALUE // down
: 0 // up
: isDownList // left-side
? Number.MAX_VALUE // down
: 0; // up
for (var i = 0, l = list.length; i < l; i++) {
var deltaY = Math.abs(list[i].y);
var length = list[i].len;
var length2 = list[i].len2;
var deltaX = (deltaY < radius + length)
? Math.sqrt(
(radius + length + length2) * (radius + length + length2) - deltaY * deltaY
)
: Math.abs(list[i].x);
if (isDownList && deltaX >= lastDeltaX) {
// right-down, left-down
deltaX = lastDeltaX - 10;
}
if (!isDownList && deltaX <= lastDeltaX) {
// right-up, left-up
deltaX = lastDeltaX + 10;
}
list[i].x = deltaX * dir;
lastDeltaX = deltaX;
}
}
var lastY = Number.NEGATIVE_INFINITY
var delta;
var len = list.length;
var len2 = Math.min(len, 9)
list.length = len = len2
list.forEach((d, i) => {
delta = d.y - lastY;
if (delta < 0) {
shiftDown(i, len, -delta, dir);
}
lastY = d.y + d.height;
});
if (height / 2 - lastY < 0) {
shiftUp(len - 1, lastY - height);
}
var upList = [];
var downList = [];
for (var i = 0; i < len; i++) {
if (list[i].y >= 0) {
downList.push(list[i]);
}
else {
upList.push(list[i]);
}
}
changeX(upList, false, radius, dir);
changeX(downList, true, radius, dir);
}
function avoidOverlap(labelLayoutList, radius, width, height) {
var leftList = [];
var rightList = [];
for (var i = 0; i < labelLayoutList.length; i++) {
if (isPositionCenter(labelLayoutList[i])) {
continue;
}
if (labelLayoutList[i].side === 'left') {
leftList.push(labelLayoutList[i]);
} else {
rightList.push(labelLayoutList[i]);
}
}
adjustSingleSide(rightList, radius, 1, width, height);
adjustSingleSide(leftList, radius, -1, width, height);
for (var i = 0; i < labelLayoutList.length; i++) {
if (isPositionCenter(labelLayoutList[i])) {
continue;
}
var linePoints = labelLayoutList[i].linePoints;
if (linePoints) {
var dist = linePoints[1][0] - linePoints[2][0];
if (labelLayoutList[i].x < 0) {
linePoints[2][0] = labelLayoutList[i].x + 3;
}
else {
linePoints[2][0] = labelLayoutList[i].x - 3;
}
linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
linePoints[1][0] = linePoints[2][0] + dist;
}
}
}