【问题标题】:D3 put arc labels in a Pie Chart if there is enough space如果有足够的空间,D3 将弧形标签放在饼图中
【发布时间】:2013-11-16 12:53:43
【问题描述】:

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

但我只会在空间足以容纳整个文本的情况下放置文本元素,所以我必须将我的文本元素的大小与每个弧中的“可用”空间进行比较。

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

谢谢...!

【问题讨论】:

    标签: javascript html svg d3.js


    【解决方案1】:

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

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

    【讨论】:

    • 这个解决方案+1。我们使用这种方法。我认为 Microsoft Excel 也是这样做的
    【解决方案2】:

    这个问题has beenasked several times之前。

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

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

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

    隐藏溢出的标签

    比较Original>= 65 标签溢出,Solution 溢出标签消失。

    减少问题

    关键的见解是看到这个问题是寻找一个凸多边形(一个矩形,边界框)是否包含在另一个凸多边形(-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用户空间的边界框。 any element which has a transform attribute set on it 创建了一个新的用户空间。在我们的例子中,那个元素就是text 框本身。所以返回的边界框是相对于文本中心的,因为我们将text-anchor 设置为middle

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

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

    工作演示

    Original

    Solution

    注意

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

    2. xy 被翻转,y 在计算 Math.atan2 时有一个负号。这是因为Math.atan2d3.svg.arc查看坐标系的方式以及ysvg的正方向不同。

      Math.atan2 的坐标系

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

      d3.svg.arc 的坐标系

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

    【讨论】:

      猜你喜欢
      • 2011-12-24
      • 1970-01-01
      • 2014-01-09
      • 2023-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-27
      • 2014-08-17
      相关资源
      最近更新 更多