【问题标题】:How to avoid labels overlapping in a D3.js pie chart?如何避免标签在 D3.js 饼图中重叠?
【发布时间】:2013-10-30 12:03:47
【问题描述】:

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

【问题讨论】:

标签: javascript d3.js pie-chart


【解决方案1】:

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

【讨论】:

  • 这种方法有效,但有一些怪癖。最值得注意的是,如果您使用过渡,它将无法工作,除非您必须等到它们移动。通过一些细微的更改,通过使用 d3.svg.arc() 来定位标签来预先计算位置并不难。我在这里的 AngularD3 库中完成了这项工作:github.com/WealthBar/angular-d3/blob/…
  • 或者您可以简单地进行上述计算并存储位置,重置为原始位置,然后开始转换到先前计算的位置。
  • 我就是这么做的。但是,除非您已经定位它们,否则您不能使用边界框。对于过渡,这会导致重叠项目移动两次时产生“抖动”效果。
  • 太棒了-这对我有用(不使用过渡),但由于仅调整切片 2 到 N 的位置,因此无法检测到第 N 个切片和第一个切片的标签之间的重叠。幸运的是,通过使用相同的逻辑将 label-N 与 label-1 进行比较并使用不同的textOffset (以避免四处走动),这很容易解决。
  • @nothingisnecessary This question 也有一些在这种情况下可能会有所帮助的答案。
【解决方案2】:

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

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

这不如替代解决方案或密码诺克对该问题的解决方案那么优雅,但可能有助于减少标签数量过多的人。如果您需要能够显示标签,鼠标悬停可能会解决问题。

【讨论】:

    【解决方案3】:

    @LarsKotthoff

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

    在此处查看工作示例: http://manicharts.com/#/demosheet/3d-donut-chart-smart-labels

    【讨论】:

    • 链接失效了,很遗憾。
    • 是的...实际上已售罄该产品,因此我不得不将其删除。将来会尝试安排类似的事情。
    • 有类似安排的更新吗?我遇到了重叠标签的问题,我很想看看你是如何解决这个问题的@codesnooker
    【解决方案4】:

    对于小角度(小于饼图的 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) { ..... });
    

    【讨论】:

      猜你喜欢
      • 2018-01-26
      • 1970-01-01
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      • 2019-06-15
      • 1970-01-01
      • 2011-12-24
      相关资源
      最近更新 更多