先展示一下效果:
![](https://img-blog.csdnimg.cn/direct/9a3a167bb820472f9d5202fc3a7700c8.jpeg)
![](https://img-blog.csdnimg.cn/direct/7686ef36985d4d4f8cd48473f70d2612.jpeg)
整体思路:
通过获取每个标签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;
}
支持原创!!
欢迎加入讨论!!!