8

我正在使用 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;
    });
4

4 回答 4

5

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)。此偏移量由文本框中心之间距离的一半确定(这只是一种启发式方法,没有具体原因),并在重新计算标签位置时添加到半径 + 文本偏移量。

数学有点复杂,因为一切都需要在二维中检查,但它非常简单。最终结果是,如果一个标签与前一个标签重叠,它就会被推得更远。完整的例子在这里

于 2013-12-17T21:40:54.880 回答
2

这里的实际问题是标签混乱之一。因此,您可以尝试不显示非常窄的弧线的标签:

.text(function(d) {
    if(d.endAngle - d.startAngle<4*Math.PI/180){return ""}
    return d.data.key; });

这不像替代解决方案或codenooker对该问题的解决方案那样优雅,但可能有助于减少那些拥有太多标签的人的数量。如果您需要能够显示标签,鼠标悬停可能会成功。

于 2015-05-25T01:56:18.083 回答
1

@LarsKotthoff

最后我解决了这个问题。我使用堆栈方法来显示标签。我在左侧和右侧都做了一个虚拟堆栈。根据切片的角度,我分配了堆栈行。如果堆栈行已经填满,那么我会在所需行的顶部和底部找到最近的空行。如果没有找到行,则从堆栈中删除具有最小共享角度的值(在当前侧),并相应地调整标签。

请参阅此处的工作示例:http: //manicharts.com/#/demosheet/3d-donut-chart-smart-labels

于 2015-04-15T18:46:41.123 回答
1

对于小角度(小于饼图的 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) { ..... });
于 2020-07-04T17:32:52.770 回答