草庐IT

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

coder 2023-08-05 原文

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

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

我想我可以使用 getBBox() 来获取文本尺寸......但我怎样才能获得(并比较)每个弧线中可用空间的尺寸。

谢谢...!

最佳答案

这个问题has been asked several times前。

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

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

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

overflow hidden 的标签

比较 Original ,其中有 >= 65标签溢出到 Solution溢出的标签消失了。

减少问题

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

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

一个点是否位于楔形内部

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

  • 点到中心的距离小于radius
  • 中心点所对的 Angular 在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);
    }
    

    找到标签的边界框

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

    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) + ')'给它。一旦我们有了中心,我们只需计算 topLeft , topRight , bottomLeftbottomRight从中点,看看它们是否都在wedge内.

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

    工作演示

    Original

    Solution

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

  • xy被翻转和y计算 Math.atan2 时有负号.这是因为 Math.atan2 之间的差异。和 d3.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)
  • 关于javascript - 如果有足够的空间,D3 将弧形标签放在饼图中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19792552/

    有关javascript - 如果有足够的空间,D3 将弧形标签放在饼图中的更多相关文章

    1. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

      我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

    2. ruby - 在院子里用@param 标签警告 - 2

      我试图使用yard记录一些Ruby代码,尽管我所做的正是所描述的here或here#@param[Integer]thenumberoftrials(>=0)#@param[Float]successprobabilityineachtrialdefinitialize(n,p)#initialize...end虽然我仍然得到这个奇怪的错误@paramtaghasunknownparametername:the@paramtaghasunknownparametername:success然后生成的html看起来很奇怪。我称yard为:$yarddoc-mmarkdown我做错了什么?

    3. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

      如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

    4. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

      我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

    5. ruby-on-rails - 如果我将 ruby​​ 版本 2.5.1 与 rails 版本 2.3.18 一起使用会怎样? - 2

      如果我使用ruby​​版本2.5.1和Rails版本2.3.18会怎样?我有基于rails2.3.18和ruby​​1.9.2p320构建的rails应用程序,我只想升级ruby的版本,而不是rails,这可能吗?我必须面对哪些挑战? 最佳答案 GitHub维护apublicfork它有针对旧Rails版本的分支,有各种变化,它们一直在运行。有一段时间,他们在较新的Ruby版本上运行较旧的Rails版本,而不是最初支持的版本,因此您可能会发现一些关于需要向后移植的有用提示。不过,他们现在已经有几年没有使用2.3了,所以充其量只能让更

    6. css - 用 watir 检查标签类? - 2

      我有一个div,它根据表单是否正确提交而改变。我想知道是否可以检查类的特定元素?开始元素看起来像这样。如果输入不正确,添加错误类。 最佳答案 试试这个:browser.div(:id=>"myerrortest").class_name更多信息:http://watir.github.com/watir-webdriver/doc/Watir/HTMLElement.html#class_name-instance_method另一种选择是只查看具有您期望的类的div是否存在browser.div((:id=>"myerrortes

    7. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

      我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

    8. ruby - 如果它是标点符号,我怎么能从字符串中删除最后一个字符,在 ruby​​ 中? - 2

      啊,正则表达式有点困惑。我正在尝试删除字符串末尾所有可能的标点符号:ifstr[str.length-1]=='?'||str[str.length-1]=='.'||str[str.length-1]=='!'orstr[str.length-1]==','||str[str.length-1]==';'str.chomp!end我相信有更好的方法来做到这一点。有什么指点吗? 最佳答案 str.sub!(/[?.!,;]?$/,'')[?.!,;]-字符类。匹配这5个字符中的任何一个(注意,。在字符类中并不特殊)?-前一个字符或组

    9. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

      我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

    10. ruby - 如果满足给定条件,则结束 ruby​​ 程序 - 2

      基本上,我只是试图在满足特定条件时停止程序运行其余行。unlessraw_information.firstputs"Noresultswerereturnedforthatquery"breakend然而,在程序运行之前我得到了这个错误:Invalidbreakcompileerror(SyntaxError)执行此操作的正确方法是什么? 最佳答案 abort("Noresultswerereturnedforthatquery")unlesscondition或unlessconditionabort("Noresultswer

    随机推荐