javascript - 円グラフ - svg 円 グラフ




D3は円のラベルを円グラフに配置します。 (2)

この質問以前に何度か聞か れました

私がそこに提案した解決策は、ラベルをrotateことですが、決して私を満足rotateことはありません。 その一部は、一部のブラウザでは恐ろしいフォントのレンダリングが行われ、読みやすさが損なわれ、1つのラベルが180°線を横切ると奇妙なflipが失われました。 場合によっては、ラベルが長すぎるなど、結果は容認でき、やむを得ないものでした。

Larsによって提案されたもう一つのソリューションの1つは、ラベルを円グラフの外に置くことです。 しかし、それはラベルを外側に押して、より大きな半径を与えますが、 overlap問題を完全には解決しません。

他の解決策は実際にあなたが提案する手法を使用しています:適合しないラベルを削除するだけです。

あふれたラベルを隠す

オーバーフローラベルがなくなったソリューションにオーバーフローする>= 65ラベルのオリジナルを比較します。

問題を軽減する

重要な洞察は、この問題が1つの凸多角形( 四角形 、境界ボックス) 別の凸多角形( -ish )( くさび形 )内に含まれているかどうかを調べることです。

問題は、矩形のすべての点がくさび内にあるかどうかを調べることに減らすことができます。 もしそうであれば、矩形は弧の内側にあります。

ポイントはくさびの中にありますか

今はその部分が簡単です。 必要なのは次の点を確認することだけです。

  1. 中心からの点の距離はradiusより小さい
  2. 中央の点でstartAngleれたendAngleは、円弧のstartAngleendAngle間にあります。
function pointIsInArc(pt, ptData, d3Arc) {
  // Center of the arc is assumed to be 0,0
  // (pt.x, pt.y) are assumed to be relative to the center
  var r1 = d3Arc.innerRadius()(ptData), // Note: Using the innerRadius
      r2 = d3Arc.outerRadius()(ptData),
      theta1 = d3Arc.startAngle()(ptData),
      theta2 = d3Arc.endAngle()(ptData);

  var dist = pt.x * pt.x + pt.y * pt.y,
      angle = Math.atan2(pt.x, -pt.y); // Note: different coordinate system.

  angle = (angle < 0) ? (angle + Math.PI * 2) : angle;

  return (r1 * r1 <= dist) && (dist <= r2 * r2) && 
         (theta1 <= angle) && (angle <= theta2);
}

ラベルの境界ボックスを見つける

今度はそれを分けてしまったので、2番目の部分は四角形の四隅が何であるかを計算しています。 それも簡単です:

g.append("text")
    .attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
    .attr("dy", ".35em")
    .style("text-anchor", "middle")
    .text(function(d) { return d.data.age; })
    .each(function (d) {
       var bb = this.getBBox(),
           center = arc.centroid(d);

       var topLeft = {
         x : center[0] + bb.x,
         y : center[1] + bb.y
       };

       var topRight = {
         x : topLeft.x + bb.width,
         y : topLeft.y
       };

       var bottomLeft = {
         x : topLeft.x,
         y : topLeft.y + bb.height
       };

       var bottomRight = {
         x : topLeft.x + bb.width,
         y : topLeft.y + bb.height
       };

       d.visible = pointIsInArc(topLeft, d, arc) &&
                   pointIsInArc(topRight, d, arc) &&
                   pointIsInArc(bottomLeft, d, arc) &&
                   pointIsInArc(bottomRight, d, arc);

    })
    .style('display', function (d) { return d.visible ? null : "none"; });

解の枝はeach関数内にeachます。 まずテキストを適切な場所に置き、DOMがレンダリングするようにします。 次に、 getBBox()メソッドを使用して、 ユーザー空間内のtextバウンディングボックスを取得します。 新しいユーザー空間transform属性が設定された要素によって作成されます 。 この要素は、この場合はtextボックスです。 したがって、 text-anchormiddle設定したので、返されるバウンディングボックスはテキストの中心を基準にしています。

変換'translate(' + arc.centroid(d) + ')'を適用しているので、 arcに対するtextの位置を計算できます。 中心を取得しtopLefttopRightからbottomLefttopRightbottomLeftbottomRight点を計算し、すべてがwedge内側にあるかどうかを確認します。

最後に、すべてのポイントがウェッジの内側にあるかどうかを判断し、一致しない場合は、 display CSSプロパティをnone設定しdisplay

作業デモ

元の

溶液

注意

  1. 私はinnerRadiusを使用しています。非ゼロであれば、 wedge非凸形になり、計算がはるかに複雑になります。 しかし、ここでの危険性は重大ではないと思うのですが、唯一のケースはこれであり、率直に言えば、私はそれが頻繁に起こるとは思わない(この例を見つけるのは難しかった)。

  2. Math.atan2を計算している間、 xyは反転され、 yは負の符号をMath.atan2ます。 これは、 Math.atan2d3.svg.arcが座標系と正のyの方向をsvgでどのように表示するかの違いが原因です。

    Math.atan2座標系

    θ = Math.atan2(y, x) = Math.atan2(-svg.y, x)

    d3.svg.arc座標系

    θ = Math.atan2(x, y) = Math.atan2(x, -svg.y)

次の例に示すように、私の円グラフ(中央)のすべての弧にテキスト要素を配置します: http : //bl.ocks.org/mbostock/3887235

しかし、部屋がテキスト全体に十分である場合にのみテキスト要素を配置するので、テキスト要素のサイズをすべての円の「利用可能な」領域と比較する必要があります。

私はテキストの寸法を取得するgetBBox()でこれを行うことができると思います...しかし、どのように私はすべての弧で利用可能なスペースの次元を取得(比較)することができます。

どうも...!


バウンディングボックスは円グラフのウェッジよりはるかに大きいため、実際には境界ボックスでこれを行うことはできません。 つまり、外縁のくさびがテキストを収容するのに十分な幅であっても、テキストの実際の位置で十分に広いことを意味するわけではありません。

残念ながら、やりたいことを簡単に行う方法はありません(ピクセルレベルのオーバーラップテスト)。 詳細については、 この質問などを参照してください。 私は単にあなたがこの問題にぶつからないように、円グラフの外にテキストラベルを置くことをお勧めします。