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 theg
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
3 Answers
Reset to default 8There'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>