我正在使用D3.js绘制一个饼图,脚本非常简单。问题是当切片很小时,它们的标签重叠。
我有什么选择来防止它们重叠? D3.js是否有我可以利用的内置机制?
演示:http://jsfiddle.net/roxeteer/JTuej/
var container = d3.select("#piechart");
var data = [
{ name: "Group 1", value: 1500 },
{ name: "Group 2", value: 500 },
{ name: "Group 3", value: 100 },
{ name: "Group 4", value: 50 },
{ name: "Group 5", value: 20 }
];
var width = 500;
var height = 500;
var radius = 150;
var textOffset = 14;
var color = d3.scale.category20();
var svg = container.append("svg:svg")
.attr("width", width)
.attr("height", height);
var pie = d3.layout.pie().value(function(d) {
return d.value;
});
var arc = d3.svg.arc()
.outerRadius(function(d) { return radius; });
var arc_group = svg.append("svg:g")
.attr("class", "arc")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")");
var label_group = svg.append("svg:g")
.attr("class", "arc")
.attr("transform", "translate(" + (width/2) + "," + (height/2) + ")");
var pieData = pie(data);
var paths = arc_group.selectAll("path")
.data(pieData)
.enter()
.append("svg:path")
.attr("stroke", "white")
.attr("stroke-width", 0.5)
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d) {
return arc({startAngle: d.startAngle, endAngle: d.endAngle});
});
var labels = label_group.selectAll("path")
.data(pieData)
.enter()
.append("svg:text")
.attr("transform", function(d) {
return "translate(" + Math.cos(((d.startAngle + d.endAngle - Math.PI) / 2)) * (radius + textOffset) + "," + Math.sin((d.startAngle + d.endAngle - Math.PI) / 2) * (radius + textOffset) + ")";
})
.attr("text-anchor", function(d){
if ((d.startAngle +d.endAngle) / 2 < Math.PI) {
return "beginning";
} else {
return "end";
}
})
.text(function(d) {
return d.data.name;
});
答案 0 :(得分:4)
D3没有提供任何内置功能,但你可以在添加标签后迭代它们并检查它们是否重叠。如果他们这样做,请移动其中一个。
var prev;
labels.each(function(d, i) {
if(i > 0) {
var thisbb = this.getBoundingClientRect(),
prevbb = prev.getBoundingClientRect();
// move if they overlap
if(!(thisbb.right < prevbb.left ||
thisbb.left > prevbb.right ||
thisbb.bottom < prevbb.top ||
thisbb.top > prevbb.bottom)) {
var ctx = thisbb.left + (thisbb.right - thisbb.left)/2,
cty = thisbb.top + (thisbb.bottom - thisbb.top)/2,
cpx = prevbb.left + (prevbb.right - prevbb.left)/2,
cpy = prevbb.top + (prevbb.bottom - prevbb.top)/2,
off = Math.sqrt(Math.pow(ctx - cpx, 2) + Math.pow(cty - cpy, 2))/2;
d3.select(this).attr("transform",
"translate(" + Math.cos(((d.startAngle + d.endAngle - Math.PI) / 2)) *
(radius + textOffset + off) + "," +
Math.sin((d.startAngle + d.endAngle - Math.PI) / 2) *
(radius + textOffset + off) + ")");
}
}
prev = this;
});
这将检查每个标签是否与前一个标签重叠。如果是这种情况,则计算半径偏移(off
)。此偏移量由文本框中心之间的距离的一半确定(这只是一种启发式方法,没有特定的理由使其成为此),并在重新计算标签的位置时添加到半径+文本偏移量中。
数学有点牵扯,因为一切都需要在两个方面进行检查,但这很简单。最终结果是,如果标签与先前的标签重叠,则将其进一步推出。完整示例here。
答案 1 :(得分:2)
这里的实际问题是标签杂乱。 因此,您可以尝试不为非常窄的弧显示标签:
.text(function(d) {
if(d.endAngle - d.startAngle<4*Math.PI/180){return ""}
return d.data.key; });
这不像替代解决方案那样优雅,或者代码解析器对该问题的解决方案,但可能有助于减少那些拥有太多问题的人的标签数量。如果您需要能够显示标签,鼠标悬停可能会起到作用。
答案 2 :(得分:0)
@LarsKotthoff
最后我解决了这个问题。我使用堆栈方法来显示标签。我在左侧和右侧都制作了一个虚拟堆栈。根据切片的角度,我分配了堆栈行。如果堆栈行已经填满,那么我会在所需行的顶部和底部找到最近的空行。如果没有找到行,则从堆栈中移除具有最小共享角度的值(在当前侧)并相应地调整标签。
请参阅此处的工作示例: http://manicharts.com/#/demosheet/3d-donut-chart-smart-labels
答案 3 :(得分:0)
对于小角度(小于饼图的5%),我更改了各个标签的质心值。我使用了以下代码:
arcs.append("text")
.attr("transform", function(d,i) {
var centroid_value = arc.centroid(d);
var pieValue = ((d.endAngle - d.startAngle)*100)/(2*Math.PI);
var accuratePieValue = pieValue.toFixed(0);
if(accuratePieValue <= 5){
var pieLableArc = d3.svg.arc().innerRadius(i*20).outerRadius(outer_radius + i*20);
centroid_value = pieLableArc.centroid(d);
}
return "translate(" + centroid_value + ")";
})
.text(function(d, i) { ..... });