2013-10-30 146 views
5

我正在用一个非常简单的脚本使用D3.js绘制饼图。问题是当切片很小时,它们的标签重叠。如何避免在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

[防止在D3中重叠文本]可能的重复(http://stackoverflow.com/questions/14534024/preventing-overlap-of-text-in-d3) –

+0

这个问题也在这里解决:[http: //stackoverflow.com/a/14803104/674700](http://stackoverflow.com/a/14803104/674700)。 –

+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

+0

这种方法可行,但它有一些怪癖。最值得注意的是,如果你正在使用转换,它不会工作,除非你必须等到他们移动。通过一些较小的更改,不难通过使用d3.svg.arc()来定位标签来预先计算位置。我已经在我的AngularD3库中这样做了:https://github.com/WealthBar/angular-d3/blob/2b14ca11051bbe50c874f0e8dad42de5f69cbd65/angularD3.js#L597 –

+0

或者您可以简单地按照上述方法进行计算并存储位置,重置到原来的位置,然后开始转换到先前计算的位置。 –

+0

这就是我所做的。但是,除非已经定位它们,否则不能使用边界框。随着转换,这会导致“重击”效果,因为重叠的项目会移动两次。 –

0

@LarsKotthoff

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

看到这里的工作示例: http://manicharts.com/#/demosheet/3d-donut-chart-smart-labels

2

的实际问题,这里是标签混乱的一个。 所以,你可以尝试不是很窄的弧线显示标签:

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

这不是作为替代的解决方案,或codesnooker的解决该问题的优雅,但可能会降低标签的数量为那些谁拥有太多。如果您需要标签才能显示,则可以使用鼠标悬停功能。