首先说明一下,本文没有具体画饼图的源码,可以去看我之前写的源码文章:https://blog.csdn.net/m0_37777005/article/details/90693746
关于给饼图添加标签会产生覆盖的问题,没有找到完美的解决方案,只能解决一部分,需要应援,需要应援,需要应援。
一般给饼图标签分为两种:
第一种是饼图内部的标签(红框)
第二种是饼图外部用弧线标记的标签(蓝框)
下图是最终成果的效果图。
从第一种情况开始说明:内部标签覆盖。
扇形区域过小不能完全显示标签内容,就算你缩小字体那也是不能解决滴。所以看起来就是这样的,如果不进行处理,设计师40m的大刀怕是已经准备好了。
所以第一种解决方案,就是把过小的区域的文字直接不显示。
只贴关键代码,需要全部源码的请留言(有时间我再改,画饼图的源码我也有写:https://blog.csdn.net/m0_37777005/article/details/90693746
或者左转出门百度Google)。
.text(d => {
const total = d3.sum(data, a => a.rate);
if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return ""; }
return `${(d.value / total * 100).toFixed(2)}%`;
});
但是你会发现,即使你隐藏了,但是某个区域的标签离边界很近,还是会有部分溢出。
所以第二种解决方案就是对文字进行旋转。
那么计算文字的角度就是第一步了。
假设文字是一条直线,与圆心到弧线的中点的夹角(嫌弃我画图丑的,请左转->https://www.cnblogs.com/ystrdy/p/10324466.html)
A(x3,y3)
B(x1,y1)
C(x2,y2)
旋转角度α就Lab 与Lac 的夹角(正值锐角)。为什么要是正值呢,因为你会发现有的需要顺时针旋转有的需要逆时针旋转,为了好把控,所以以正值锐角为值,判断旋转方向。
第二象限需要顺时针旋转,其它三个象限逆时针旋转。
getAngle({ x: x1, y: y1 }, { x: x2, y: y2 }) {
const dot = x1 * x2 + y1 * y2;
const det = x1 * y2 - y1 * x2;
const angle = Math.abs(Math.atan2(det, dot) / Math.PI * 180);
return angle;
}
arcs
.append('text')
.attr('text-anchor', 'middle')
.attr('font-size', fonsize)
.attr('transform', d => {
const x1 = 0;
const y1 = 0;
const x3 = arc.centroid(d)[0];
const y3 = arc.centroid(d)[1];
const x2 = x3 + 100;
const y2 = y3;
const angle = this.getAngle({ x: Math.abs(x1 - x3), y: Math.abs(y1 - y3) },
{ x: Math.abs(x2 - x3), y: Math.abs(y2 - y3) });
console.log(d, arc.centroid(d), angle);
if (x3 < 0 && y3 < 0) {
return `translate(${x3},${y3}) rotate(${angle})`;
}
return `translate(${x3},${y3}) rotate(${-angle})`;
})
.text(d => {
const total = d3.sum(data, a => a.rate);
if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return ''; }
return `${(d.value / total * 100).toFixed(2)}%`;
});
第二种情况:添加外部标签产生覆盖。
出现的原因依旧是扇形区域小而密集,到时外部标签聚合在一起了。
就像酱紫:
所以第一种解决的思路就是将小而密集的弧线的间距增大
核心就是:如果扇形区域较小,留出两倍fontsize的空间
.attr('y2', (d, i) => {
if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return arc.centroid(d)[1] * 2.5 + fontsize * i; }
return arc.centroid(d)[1] * 2.5;
});
添加外部标签的方法。这个方法并不完美,在某些情况下依然会遇到标签重叠的情况,但是有范围限制的情况下直接使用问题不大---- **求应援**
第二种解决方案:因为扇形区域小又密集才产生的问题,所以把小扇形区域打散。解决方案来源:https://blog.csdn.net/chancemagic/article/details/78872572
我们先看一个数据结构
未处理前的结构
处理后的数据结构
渲染效果:
学好数据结构还是很重要的。
reorderData(data) {
/* ----------- Reorder data to avoid label overlap -------------------- */
//Get the value list, sort it, then reord it.
let value_only_list = []
let value_list_temp = []
// data.forEach((d, i) => { value_only_list.push(d.rate) });s
value_only_list = data.sort(NumCompare('rate'));
let length = value_only_list.length;
const half = parseInt(length / 2);
const left = length % 2;
let tag = 0;
while (tag < half) {
value_list_temp.push(value_only_list[tag]);
value_list_temp.push(value_only_list[length - 1]);
tag += 1;
length -= 1;
}
if (left === 1) { value_list_temp.push(value_only_list[half]); }
console.log('value_only_list-->', value_only_list);
console.log('value_list_temp-->', value_list_temp);
//Re-construct the dataset for pie chart
return value_list_temp;
function NumCompare(pro) {
return (a, b) => a[pro] - b[pro];
}
}
以下是添加外部标签的方法
drawOutTip(arc, arcs, data) {
const fontsize = 15;
// 添加连接弧外文字的直线元素
arcs
.append('line')
.attr('stroke', 'black')
.attr('x1', d => {
return arc.centroid(d)[0] * 2;
})
.attr('y1', d => {
return arc.centroid(d)[1] * 2;
})
.attr('x2', (d, i) => {
return arc.centroid(d)[0] * 2.5;
})
.attr('y2', (d, i) => {
//第一种方案去掉注释
//if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { return arc.centroid(d)[1] * 2.5 + fontsize * i; }
return arc.centroid(d)[1] * 2.5;
});
arcs
.append('line')
.style('stroke', 'black')
.each(d => {
d.textLine = {
x1: 0,
y1: 0,
x2: 0,
y2: 0,
};
})
.attr('x1', d => {
d.textLine.x1 = arc.centroid(d)[0] * 2.5;
return d.textLine.x1;
})
.attr('y1', (d, i) => {
d.textLine.y1 = arc.centroid(d)[1] * 2.5;
//第一种方案去掉注释
// if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { d.textLine.y1 = arc.centroid(d)[1] * 2.5 + fontsize * i; }
return d.textLine.y1;
})
.attr('x2', d => {
const strLen = getPixelLength(d.data.name, fontsize) * 1.5;
const bx = arc.centroid(d)[0] * 2.5;
d.textLine.x2 = bx >= 0 ? bx + strLen : bx - strLen;
return d.textLine.x2;
})
.attr('y2', (d, i) => {
d.textLine.y2 = arc.centroid(d)[1] * 2.5;
//第一种方案去掉注释
//if (d.endAngle - d.startAngle < 18 * Math.PI / 180) { d.textLine.y2 = arc.centroid(d)[1] * 2.5 + fontsize * i; }
return d.textLine.y2;
});
arcs
.append('text')
.attr('transform', (d) => {
let x = 0;
let y = 0;
x = (d.textLine.x1 + d.textLine.x2) / 2;
y = d.textLine.y1;
y = y > 0 ? y + fontsize * 1.1 : y - fontsize * 0.4;
return `translate(${x},${y})`;
})
.style('text-anchor', (d) => {
return midAngle(d) < Math.PI ? 'start' : 'end';
})
.style('font-size', fontsize)
.text((d) => {
return d.data.name;
});
function getPixelLength(str, fontsize) {
let curLen = 0;
for (let i = 0; i < str.length; i += 1) {
const code = str.charCodeAt(i);
const pixelLen = code > 255 ? fontsize : fontsize / 2;
curLen += pixelLen;
}
return curLen;
}
function midAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2;
};
}