2013-10-30 151 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的解決該問題的優雅,但可能會降低標籤的數量爲那些誰擁有太多。如果您需要標籤才能顯示,則可以使用鼠標懸停功能。