Given this SVG:
<svg xmlns="http://www.w3.org/2000/svg">
<rect width="50" height="50"
transform="translate(75,75) rotate(45) translate(-25,-25)" />
<script>
var bb = document.querySelector('rect').getBBox();
console.log([bb.x,bb,y,bb.width,bb.height]);
</script>
</svg>
The resulting output is [0, 0, 50, 50]
.
The desired result is [39.645,39.645,70.711,70.711]
.
Visual Version: http://jsfiddle.net/2wpju/7/
What's the simplest, most efficient way to calculate the bounding box of an element with respect to its parent element?
Below is the best answer I've come up with so far, but there are two problems:
- It seems like a lot of work for what might be handled better, and
- As seen in this demo it's not necessarily the minimum bounding box (notice the upper circle), since the axis-aligned bounding box of a rotated axis-aligned bounding box always increases the size.
// Calculate the bounding box of an element with respect to its parent element
function transformedBoundingBox(el){
var bb = el.getBBox(),
svg = el.ownerSVGElement,
m = el.getTransformToElement(el.parentNode);
// Create an array of all four points for the original bounding box
var pts = [
svg.createSVGPoint(), svg.createSVGPoint(),
svg.createSVGPoint(), svg.createSVGPoint()
];
pts[0].x=bb.x; pts[0].y=bb.y;
pts[1].x=bb.x+bb.width; pts[1].y=bb.y;
pts[2].x=bb.x+bb.width; pts[2].y=bb.y+bb.height;
pts[3].x=bb.x; pts[3].y=bb.y+bb.height;
// Transform each into the space of the parent,
// and calculate the min/max points from that.
var xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity;
pts.forEach(function(pt){
pt = pt.matrixTransform(m);
xMin = Math.min(xMin,pt.x);
xMax = Math.max(xMax,pt.x);
yMin = Math.min(yMin,pt.y);
yMax = Math.max(yMax,pt.y);
});
// Update the bounding box with the new values
bb.x = xMin; bb.width = xMax-xMin;
bb.y = yMin; bb.height = yMax-yMin;
return bb;
}
All you need to use is this simple Javascript function:
object.getBoundingClientRect();
It is supported in:
Chrome Firefox (Gecko) Internet Explorer Opera Safari
1.0 3.0 (1.9) 4.0 (Yes) 4.0
Look here for more about it. Link
One way is to make a raphael set and get bb of it. Or wrap all elements inside g and get bb of g, i assume that g should be fastest.
That seems to be the current method, unless/until getBBox() considers the transform on the object as directly defining the bounding box, even though SVG 1.1 SE defines:
SVGRect getBBox()
Returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects). Note that getBBox must return the actual bounding box at the time the method was called, even in case the element has not yet been rendered.
I guess its all about how you define 'current user space'
This is an answer that accounts for rotated ellipses. The transformation method works OK for rectangles but on ellipses returns a bounding box considerably larger than the object if it is rotated.
The simpler answer of using the builtin JavaScript function isn't very useful to me because it would still have to be translated back into the drawing space.
This code doesn't handle skew or scale for ellipses (but does for any other object), but in my program at http://nwlink.com/~geoffrey/svgBuild I intend to handle skew and scale by drawing an entirely new ellipse with the skew or scale applied.
The formula for the ellipses came from here: How do you calculate the axis-aligned bounding box of an ellipse? much thanks for the help there.
el is a d3.js element, not the node.
function toNumberIfNumeric(x) {
var result = parseFloat(x);
return isNaN(result) ? x : result;
}
function getAttrs(el, attributes) {
var result = {};
attributes.forEach(function (attr) {
result[attr] = toNumberIfNumeric(el.attr(attr));
});
return result;
}
function getTransformedBbox(el, referenceNode) {
var node = el.node();
switch (node.nodeName) {
case "ellipse":
var rotate = getTransform(el, "rotate");
if (rotate.angle === 0) {
return node.getBBox();
}
var attr = getAttrs(el, ["cx", "cy", "rx", "ry"]);
var radians = rotate.angle / 180 * Math.PI;
var radians90 = radians + Math.PI / 2;
var ux = attr.rx * Math.cos(radians);
var uy = attr.rx * Math.sin(radians);
var vx = attr.ry * Math.cos(radians90);
var vy = attr.ry * Math.sin(radians90);
var halfWidth = Math.sqrt(ux * ux + vx * vx);
var halfHeight = Math.sqrt(uy * uy + vy * vy);
return {
x: attr.cx - halfWidth,
y: attr.cy - halfHeight,
width: 2 * halfWidth,
height: 2 * halfHeight
};
default:
var bb = node.getBBox(),
svg = node.ownerSVGElement,
m = node.getTransformToElement(referenceNode);
// Create an array of all four points for the original bounding box
var pts = [
svg.createSVGPoint(), svg.createSVGPoint(),
svg.createSVGPoint(), svg.createSVGPoint()
];
pts[0].x = bb.x;
pts[0].y = bb.y;
pts[1].x = bb.x + bb.width;
pts[1].y = bb.y;
pts[2].x = bb.x + bb.width;
pts[2].y = bb.y + bb.height;
pts[3].x = bb.x;
pts[3].y = bb.y + bb.height;
// Transform each into the space of the parent,
// and calculate the min/max points from that.
var xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity;
pts.forEach(function (pt) {
pt = pt.matrixTransform(m);
xMin = Math.min(xMin, pt.x);
xMax = Math.max(xMax, pt.x);
yMin = Math.min(yMin, pt.y);
yMax = Math.max(yMax, pt.y);
});
// Update the bounding box with the new values
bb.x = xMin;
bb.width = xMax - xMin;
bb.y = yMin;
bb.height = yMax - yMin;
return bb;
}
}