D3 - 在SVG群组周围创建动态“边框”矩形

5

我有一个包含矩形的SVG组,希望这个矩形能够作为该组的边框使用...

<g>
  <rect></rect>
</g>

但群组是动态的,其内容会更改。我尝试在我的更新函数中调整矩形大小,如下所示。
.attr("x", function(d) { return this.parentNode.getBBox().x })
.attr("y", function(d) { return this.parentNode.getBBox().y })
.attr("width", function(d) { return this.parentNode.getBBox().width })
.attr("height", function(d) { return this.parentNode.getBBox().height })

但是看起来现在的情况是,它能够很好地扩展,但是收缩时无法正确缩小,因为群组的边界框宽度现在与扩展矩形的宽度相同(矩形的宽度是群组的宽度,但群组的宽度现在是矩形的宽度)。

有没有办法让 SVG 组内的矩形正确地调整大小并作为边框?


1
为什么需要将 rect 设为 g 元素的子元素? - methodofaction
@Duopixel 这使得事情更有组织性(因为边框应该在其组内而不是外部),但你说得对,我不需要它成为子元素!如果您将此作为答案添加,我会接受它,因为这个显而易见的解决方案解决了我的问题 :) - agilgur5
3个回答

8

有多种方法可以解决这个问题。

  • Use the outline property (2014-08-05 status: works in Chrome and Opera)

    <svg xmlns="http://www.w3.org/2000/svg" width="500px" height="500px">
      <g style="outline: thick solid black; outline-offset: 10px;">
        <circle cx="50" cy="60" r="20" fill="yellow"/>
        <rect x="80" y="80" width="200" height="100" fill="blue"/>
      </g>
    </svg>
    

    See live example.

  • Use a filter to generate the border (2014-08-05 status: works in Firefox, but Chrome/Opera has a bug on feMorphology, but it should be possible to work around that by using other filter primitives).

    <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
      <defs>
        <filter id="border" x="-5%" y="-5%" width="110%" height="110%">
          <feFlood flood-color="black" result="outer"/>
          <feMorphology operator="erode" radius="2" in="outer" result="inner"/>
          <feComposite in="inner" in2="outer" operator="xor"/>
          <feComposite in2="SourceGraphic"/>
        </filter>
      </defs>
      <g filter="url(#border)">
        <circle cx="50" cy="60" r="20" fill="yellow"/>
        <rect x="80" y="80" width="200" height="100" fill="blue"/>
      </g>
    </svg>
    

    See live example.

上述两者都会自动更新为组的大小,无需进行DOM修改。


为什么我从未听说过outline属性?我在SO上看到的每篇关于SVG边框的帖子都涉及到rect,但这种方法要简单得多!然而,我似乎无法在IE9上运行它,尽管MDN说它应该与IE8+兼容。:\ - agilgur5

5

是的,您可以通过选择组的所有子元素(不包括bounding rect本身)来找到新的边界框,然后根据子元素的各自边界框计算总体边界框。

假设您的边界矩形具有 bounding-rect 类,您可以执行以下操作:

function updateRect() {
  // SELECT ALL CHILD NODES EXCEPT THE BOUNDING RECT
  var allChildNodes = theGroup.selectAll(':not(.bounding-rect)')[0]

  // `x` AND `y` ARE SIMPLY THE MIN VALUES OF ALL CHILD BBOXES
  var x = d3.min(allChildNodes, function(d) {return d.getBBox().x;}),
      y = d3.min(allChildNodes, function(d) {return d.getBBox().y;}),

      // WIDTH AND HEIGHT REQUIRE A BIT OF CALCULATION
      width = d3.max(allChildNodes, function(d) {
        var bb = d.getBBox();
        return (bb.x + bb.width) - x;
      }),

      height = d3.max(allChildNodes, function(d) {
        var bb = d.getBBox();
        return (bb.y + bb.height) - y;
      });

  // UPDATE THE ATTRS FOR THE RECT
  svg.select('.bounding-rect')
     .attr('x', x)
     .attr('y', y)
     .attr('width', width)
     .attr('height', height);
}

这将把整个外框的x和y值设置为子框中最小的x和y值。然后计算宽度,找到右边界bb.x + bb.width的最大值,并减去整个框的x。高度的计算方式与宽度相同。 这里有一个例子。

感谢您的详细回答!不幸的是,即使经过一些尝试,这个解决方案对我来说仍然相当复杂,而且并没有完全奏效。我的许多组都嵌套在整体组内,因此它们的内部元素都是相对定位的...我尝试使用.getCTM()并获取翻译,但经过一两个小时的试错后,它并没有起作用:\ - agilgur5
边界框已经相对于本地坐标系了。即使您将组嵌套在应用了平移和缩放的多个组内,边界框仍将正确地限制您的元素。请参见更新的示例HERE。也许我误解了您的问题。无论如何,看起来您已经找到了适合自己的解决方案,祝您好运。 - jshanley
我已经找到了解决方案,再次感谢!为了澄清一下,我的内部元素是嵌套在组内的。在您的示例中,圆形的父级不会与矩形的父级相同,因为它们也将嵌套在其他一些组中。因此,在调用.getBBox()时,我得到的x和y位置是相对于嵌套的内部组(而不仅仅是相对于theGroup,就像您的示例中一样)。 示例:<g><rect></rect><g transform='translate(50,50)'><g transform='translate(100,30)'><circle x='2' y='3'></circle></g></g></g> - agilgur5

1
最简单、跨浏览器兼容的实现边框的方法是使用一个 rect,就像我所做的那样,但将其放置在组外,如@Duopixel在评论中提到的。由于它仍然由边界框定位,因此它将具有正确的宽度、高度、x和y。
<rect></rect>
<g></g>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接