最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - D3 - Create Dynamic "Border" Rectangle around SVG group - Stack Overflow

programmeradmin3浏览0评论

I have an SVG group with a rect inside of it, and would like the rect to act as a border for the group...

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

but the group is dynamic and its content changes. I am attempting to resize the rect in my update function as such

.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 })

But what seems to happen is that it expands relatively fine, but then cannot shrink properly since the group's bounding box width is now the same as the expanded rect's width (the rect's width is the group's width, but the group's width is now the rect's width).

Is there any way to get a rectangle inside an SVG group to properly resize and act as a border?

I have an SVG group with a rect inside of it, and would like the rect to act as a border for the group...

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

but the group is dynamic and its content changes. I am attempting to resize the rect in my update function as such

.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 })

But what seems to happen is that it expands relatively fine, but then cannot shrink properly since the group's bounding box width is now the same as the expanded rect's width (the rect's width is the group's width, but the group's width is now the rect's width).

Is there any way to get a rectangle inside an SVG group to properly resize and act as a border?

Share Improve this question asked Aug 1, 2014 at 18:46 agilgur5agilgur5 81214 silver badges33 bronze badges 2
  • 1 Why do you need the rect to be a child of the g element? – methodofaction Commented Aug 1, 2014 at 19:42
  • @Duopixel That made things more organized (as you would expect a border to be inside its group, not outside), but you're right, I didn't need it to be a child! If you add this as answer I'll accept it because this obvious solution fixed my problem :) – agilgur5 Commented Aug 1, 2014 at 23:11
Add a ment  | 

3 Answers 3

Reset to default 8

There's more than one way to solve this.

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

    <svg xmlns="http://www.w3/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/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.

Both of the above will automatically update to whatever size the group has, without the need for DOM modifications.

Yes, you can find the new bounding box by selecting all child elements of the group that are not the bounding rect itself, and then calculating the overall bounding box based on the individual bounding boxes of the children.

Lets say your bounding rect had a class of bounding-rect, you could do the following:

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

This would set the x and y values of the overall bounding box to be the minimum x and y values in the childrens' bounding boxes. Then the overall width is calculated by finding the maximum right boundary bb.x + bb.width and subtracting the overall box's x. The overall height is then calculated in the same way as the width.

HERE is an example of this.

The simplest, cross-browser patible way is to implement a border is to use a rect exactly as I did, but place it outside of the group, as mentioned by @Duopixel in his ment. As it is still positioned by the bounding box, it will have the correct width, height, x, and y.

<rect></rect>
<g></g>
发布评论

评论列表(0)

  1. 暂无评论