javascript 如果有足够的空间,D3 将圆弧标签放在饼图中

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19792552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 16:49:00  来源:igfitidea点击:

D3 put arc labels in a Pie Chart if there is enough space

javascripthtmlsvgd3.js

提问by Kollisionskurs

I will put a text element in every arc of my Pie Chart (center) - as shown in this example: http://bl.ocks.org/mbostock/3887235

我将在饼图(中心)的每个弧线中放置一个文本元素 - 如本例所示:http: //bl.ocks.org/mbostock/3887235

But I will only put the text element if the room is sufficient for the whole text, so im must compare the size of my text element with the "available" space in every arc.

但是,如果空间足以容纳整个文本,我只会放置文本元素,因此我必须将文本元素的大小与每个弧中的“可用”空间进行比较。

I think I can do this with getBBox() to get the text dimensions... but how can I get (and compare) the dimension of the available space in every arc.

我想我可以使用 getBBox() 来获取文本尺寸……但是我如何获得(并比较)每个弧中可用空间的尺寸。

thx...!

谢谢...!

回答by musically_ut

This question has beenasked several timesbefore.

这个问题之前已经被问过几次了

The solutions I have suggested there is to rotatethe label but it has never quite satisfied me. Part of it was the horrible font rendering done by some browsers and loss in legibility that brings and the weird flipwhen one label crosses over the 180°line. In some cases, the results were acceptable and unavoidable, e.g. when the labels were too long.

我建议的解决方案是针对rotate标签的,但它从来没有让我感到满意。部分原因是某些浏览器所做的可怕的字体渲染和易读性的损失,以及flip当一个标签越过180°线时带来的怪异。在某些情况下,结果是可以接受且不可避免的,例如当标签太长时。

One of the other solution, the one suggested by Lars, is to put the labels outside the pie chart. However, that just pushes the labels outside, granting them a larger radius, but does not solve the overlapproblem completely.

Lars 建议的另一种解决方案是将标签放在饼图之外。然而,这只是将标签推到外面,赋予它们更大的半径,但并不能overlap完全解决问题。

The other solution is actually using the technique you suggest: just removethe labels which do not fit.

另一种解决方案实际上是使用您建议的技术:只需删除不合适的标签。

Hide overflowing labels

隐藏溢出的标签

Compare Original, which has >= 65label overflowing to Solutionwhere the overflowing label is gone.

比较Original>= 65标签溢出到Solution溢出标签消失的地方。

Reducing the problem

减少问题

The key insight is to see that this problem is of finding whether one convex polygon (a rectangle, the bounding box) is containedinside another convex polygon(-ish) (a wedge).

关键的见解是看这个问题是找到一个凸多边形(矩形,边界框)是否包含在另一个凸多边形(-ish)(楔形)内。

The problem can be reduced to finding whether all the points of the rectangle lie inside the wedge or not. If they do, then the rectangle lies inside the arc.

问题可以简化为寻找矩形的所有点是否都在楔形内。如果是,则矩形位于圆弧内。

Does a point lie inside a wedge

点在楔子里面吗

Now that part is easy. All one needs to do is to check:

现在这部分很容易。所有需要做的就是检查:

  1. The distance of the point from the center is less than the radius
  2. The angle subtended by the point on the center is between the startAngleand endAngleof the arc.
  1. 点到中心的距离小于 radius
  2. 中心点所对的角度在圆弧的startAngle和之间endAngle
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);
}

Find the bounding box of the labels

找到标签的边界框

Now that we have that out of the way, the second part is figuring out what are the four corners of the rectangle. That, also, is easy:

现在我们已经解决了这个问题,第二部分是弄清楚矩形的四个角是什么。这也很容易:

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"; });

The pith of the solution is in the eachfunction. We first place the text at the right place so that the DOM renders it. Then we use the getBBox()method to get the bounding box of the textin the user space. A new user spaceis created by any element which has a transformattribute set on it. That element, in our case, is the textbox itself. So the bounding box returned is relative to the center of the text, as we have set the text-anchorto be middle.

解决方案的精髓在于each函数。我们首先将文本放在正确的位置,以便 DOM 呈现它。然后我们使用的getBBox()方法来获得的边界框text用户空间任何具有属性集的元素都会创建一个新的用户空间。在我们的例子中,那个元素就是盒子本身。因此返回的边界框相对于文本的中心,因为我们已将 设置为。transformtexttext-anchormiddle

The position of the textrelative to the arccan be calculated since we have applied the transformation 'translate(' + arc.centroid(d) + ')'to it. Once we have the center, we just calculate the topLeft, topRight, bottomLeftand bottomRightpoints from it and see whether they all lie inside the wedge.

可以计算text相对于 的位置,arc因为我们已经对其应用了变换'translate(' + arc.centroid(d) + ')'。一旦我们有了中心,我们只是计算topLefttopRightbottomLeftbottomRight从中分,看到里面的是否所有的谎言wedge

Finally, we determine if all the points lie inside the wedgeand if they do not fit, set the displayCSS property to none.

最后,我们确定所有点是否都位于楔形内,如果它们不适合,则将displayCSS 属性设置为none

Working demo

工作演示

Original

Original

Solution

Solution

Note

笔记

  1. I am using the innerRadiuswhich, if non zero, makes the wedgenon-convex which will make the calculations much more complex! However, I think the danger here is not significant since the only case it might fail is this, and, frankly, I don't think it'll happen often (I had trouble finding this counter example):

    The failure case

  2. xand yare flipped and yhas a negative sign while calculating Math.atan2. This is because of the difference between how Math.atan2and d3.svg.arcview the coordinate system and the direction of positive ywith svg.

    Coordinate system for Math.atan2

    Coordinate system for <code>Math.atan2</code>θ = Math.atan2(y, x) = Math.atan2(-svg.y, x)

    Coordinate system for d3.svg.arc

    Coordinate system for <code>d3.svg.arc</code>θ = Math.atan2(x, y) = Math.atan2(x, -svg.y)

  1. 我正在使用innerRadiuswhich,如果非零,会使wedge非凸的,这将使计算更加复杂!但是,我认为这里的危险并不重要,因为它可能失败的唯一情况是这种情况,而且坦率地说,我认为它不会经常发生(我很难找到这个反例):

    失败案例

  2. x并且在计算时y被翻转并y带有负号Math.atan2。这是因为如何之间的差的Math.atan2d3.svg.arc的图坐标系和正的方向ysvg

    坐标系为 Math.atan2

    <code>Math.atan2</code>的坐标系θ = Math.atan2(y, x) = Math.atan2(-svg.y, x)

    坐标系为 d3.svg.arc

    <code>d3.svg.arc</code>的坐标系θ = Math.atan2(x, y) = Math.atan2(x, -svg.y)

回答by Lars Kotthoff

You can't really do this with the bounding box because the bounding box is much larger than a wedge for the pie chart wedges. That is, even though the wedge at the outer edge would be wide enough to accommodate the text, that doesn't mean that it's wide enough at the actual position of the text.

您不能真正使用边界框执行此操作,因为边界框比饼图楔形的楔形大得多。也就是说,即使外边缘的楔形足够宽以容纳文本,但这并不意味着它在文本的实际位置处足够宽。

Unfortunately, there's no easy way of doing what you're trying to do (pixel-level overlap testing). See e.g. this questionfor some more information. I would suggest simply putting the text labels outside of the pie chart so you don't run into this problem.

不幸的是,没有简单的方法来做你想做的事情(像素级重叠测试)。有关更多信息,请参见例如这个问题。我建议简单地将文本标签放在饼图之外,这样您就不会遇到这个问题。