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 には、これを行う組み込みの機能はありませんが、ラベルを追加した後、それらを繰り返し処理し、重複しているかどうかを確認することで実行できます。そうした場合、そのうちの 1 つを移動します。

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)。このオフセットは、テキスト ボックスの中心間の距離の半分によって決定され (これはヒューリスティックであり、これであるという特定の理由はありません)、元のラベルの位置を再計算するときに半径 + テキスト オフセットに追加されます。

すべてを 2 次元でチェックする必要があるため、数学は少し複雑ですが、非常に簡単です。最終的な結果として、ラベルが前のラベルと重なっている場合は、さらに外側に押し出されます。ここに完全な例があります。

于 2013-12-17T21:40:54.880 に答える
2

ここでの実際の問題は、ラベルの乱雑さの 1 つです。したがって、非常に狭い円弧のラベルを表示しないようにすることができます。

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

これは、代替ソリューションやその問題に対するコードスヌーカーの解決策ほどエレガントではありませんが、ラベルが多すぎる場合は、ラベルの数を減らすのに役立つ可能性があります。ラベルを表示できるようにする必要がある場合は、マウスオーバーでうまくいくかもしれません。

于 2015-05-25T01:56:18.083 に答える