繁体   English   中英

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

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

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

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

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

谢谢...!

之前已经多次 询问过这个问题。

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

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

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

隐藏溢出的标签

比较Original ,其中>= 65标签溢出到解决方案 ,其中溢出标签消失。

减少问题

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

问题可以减少到找出矩形的所有点是否位于楔形内部。 如果他们这样做,则矩形位于弧内。

有一点在楔子里面

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

  1. 点与中心的距离小于radius
  2. 中心点所对的角度位于弧的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);
}

找到标签的边界框

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

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功能。 我们首先将文本放在正确的位置,以便DOM呈现它。 然后我们使用getBBox()方法来获得的边框text用户空间 新的用户空间任何设置了transform属性的元素创建。 在我们的例子中,该元素是text框本身。 因此返回的边界框相对于文本的中心,因为我们已将text-anchor设置为middle

由于我们已将变换'translate(' + arc.centroid(d) + ')'应用于它,因此可以计算text相对于arc的位置。 一旦我们有了中心,我们只需计算它的topLefttopRightbottomLeftbottomRight点,看看它们是否都位于wedge

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

工作演示

原版的

注意

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

    失败的情况

  2. 在计算Math.atan2 xy被翻转, y具有负号。 这是因为Math.atan2d3.svg.arc如何查看坐标系以及正y的方向与svg之间的区别。

    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)

你不能用边界框真正做到这一点,因为边界框比饼图楔形的楔形大得多。 也就是说,即使外边缘处的楔形宽度足以容纳文本,这并不意味着它在文本的实际位置上足够宽。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM