Combining two Matrix Transformations under the same Transformation with SVG - svg

My current task is attempting to combine objects with similar matrices under the same transformation matrix. The two matrices will always have the first 4 digits of it's transform be equal. I am having difficulty calculating the x="???" and y="???" for the second tspan. Any help towards the proper equation would be greatly appreciated.
Input
<svg>
<text transform="matrix(0 1 1 0 100 100)"><tspan x=0 y=0>foo</tspan></text>
<text transform="matrix(0 1 1 0 110 110)"><tspan x=0 y=0>bar</tspan></text>
</svg>
Output
<svg>
<text transform="matrix(0 1 1 0 100 100)">
<tspan x="0" y="0">foo</tspan>
<tspan x="???" y="???">bar</tspan>
</text>
</svg>
EDIT 1
I guess my question is more along the lines of given a point (x,y), how do I apply an existing matrix transformation to that point so that the position will not move, but the element will now be nested inside of another element.
EDIT 2
I have got this code to work for matrices with 0s in the (a,d) or (b,c) positions. Slanted/Skewed matrices I still have not got working. Any thoughts on this?
var aX = floatX[0];
var bX = floatX[1];
var cX = floatX[2];
var dX = floatX[3];
var eX = floatX[4];
var fX = floatX[5];
var aY = floatY[0];
var bY = floatY[1];
var cY = floatY[2];
var dY = floatY[3];
var eY = floatY[4];
var fY = floatY[5];
var xX = (eX * aX) + (fX * bX);
var xY = (eX * cX) + (fX * dX);
var yX = (eY * aY) + (fY * bY);
var yY = (eY * cY) + (fY * dY);
var c1 = cX - aX;
var c2 = dX + bX;
return new float[] { (yX - xX) / (c1 * c2), (yY - xY) / (c1 * c2) };

One thought that may work if my logic isn't flawed, is to find the transform for one element to the other, which can then be used to transform a point of 0,0 (as that's the original x,y) to a new location.
Once we know what the difference in transforms is (assuming that the first 4 figures in the matrix are the same as mentioned in the question, it won't work otherwise), we can figure what the difference in x,y is.
First, there's a bit of code as some browsers have removed this feature..
SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
This is an svg method that some browsers support, but including as a polyfill in case yours doesn't (like Chrome). It finds the transform from one element to another.
We can then use this, to find the transform from the first to the second text element.
var text1 = document.querySelector('#myText1')
var text2 = document.querySelector('#myText2')
var transform = text2.getTransformToElement( text1 )
Or if you don't want the polyfill, this 'may' work (matrices aren't a strong point of mine!). getCTM() gets the current transformation matrix of an element.
var transform = text1.getCTM().inverse().multiply( text2.getCTM() )
Now we know what the transform between them was. We also know the original x,y was 0,0. So we can create an svg point 0,0 and then transform it with that matrix we've just figured, to find the new x,y.
var pt = document.querySelector('svg').createSVGPoint();
pt.x = 0; pt.y = 0;
var npt = pt.matrixTransform( transform );
Then just a delayed example to show it being moved. Set the tspan with the new x,y we've just figured from the previous transform.
setTimeout( function() {
alert('new x,y is ' + npt.x + ',' + npt.y)
tspan2.setAttribute('x', npt.x);
tspan2.setAttribute('y', npt.y);
},2000);
jsfiddle with polyfill
jsfiddle without polyfill

Related

SVG or Canvas bevel & emboss to find center-line of text

The photoshop bevel & emboss effect makes it easy to find the central 'ridge' which coincides with the center-line of text characters. This is done by increasing the appropriate effect settings to max-out the bevel, thereby creating such a ridge.
This Photoshop example was processed to further accentuate the center ridge
Is it possible to achieve the same effect with an SVG filter or Canvas technique in the browser?
Once this effect is in place, I could obtain the coordinates of the center line which I want.
Alternatively, is there an existing algorithm to get this center line via mathematical means from a raster image or vector shape?
SVG filters are a powerful feature that can be like photoshop in the browser. You can achieve the desired result by chaining a handful of filter primitives together.
<filter id="filterData">
<feGaussianBlur stdDeviation="5" />
<feDiffuseLighting surfaceScale="500">
<feDistantLight azimuth="90" elevation="90" />
</feDiffuseLighting>
<feComposite result="composite" operator="in" in2="SourceGraphic" />
</filter>
The first primitive blurs the text. Then a lighting primitive uses the result of the blurred primitive as a bump map to give the text depth. You will have to play with the surfaceScale attribute depending to the thickness of the text. The composite primitive will cut the final result to the area of the unfiltered text, the 'SourceGraphic'.
[codepen example] https://codepen.io/lahaymd/pen/EdNXam
Somehow this one tickled my fancy, although I am not sure this is an efficient way to get a result.
What is the center line? I define it as the set of all points inside the contour that fullfill the following condition: There must be at least one straight line going through the point where the distance to the nearest contour line is a local maximum along the line just at that point. In practice, testing a horizontal and a vertical line is enough.
I tried to implement that using two functions from the SVGGeometryElement interface: .getPointAtLength() and .isPointInFill(). The second one has so far only been implemented in Chrome, so that is the only browser this will work with.
The <text> element does not implement the SVGGeometryElement interface, so it must be converted to a <path>. That is something that cannot be done in a browser, you'll need an appropriate grafics program for that.
Finding, for 1000 * 500 points, which of ca. 5000 points along the contour of the two letters is the nearest one is a lot of computation. Therefore this contains a crude mechanism to only test those contour points that are in the vincinity. Nonetheless, give it a few seconds to complete. If you compute only one letter at that size and halve the canvas size, the execution time will aproximately quarter.
const width = 1000;
const height = 500;
const letter = document.querySelector('path');
const svg = document.querySelector('svg');
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
function isInside(x, y) {
const point = svg.createSVGPoint();
point.x = x;
point.y = y;
return letter.isPointInFill(point);
}
// a 21 * 11 array of arrays
const fields = new Array(21).fill(0).map(() => {
return new Array(11).fill(0).map(() => []);
});
// a list of points along the contour
const length = Math.floor(letter.getTotalLength());
Array.from(new Array(length), (x, i) => {
return letter.getPointAtLength(i);
}).forEach(point => {
// find out if a contour point is inside a 100 * 100 rectangle
let rx1= Math.round(point.x / 100) * 2;
let ry1 = Math.round(point.y / 100) * 2;
// or a 100 * 100 rectangle that is offset by 50
let rx2 = Math.round((point.x + 50) / 100) * 2 - 1;
let ry2 = Math.round((point.y + 50) / 100) * 2 - 1;
// push the point into all four lists for the rectangles it is part of
fields[rx1][ry1].push(point);
fields[rx1][ry2].push(point);
fields[rx2][ry1].push(point);
fields[rx2][ry2].push(point);
});
const data = new Float32Array(width * height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// only handle points inside the contour
if (isInside(x, y)) {
// find out which 50 * 50 rectangle the inside point is part of
const rx = Math.round(x / 50);
const ry = Math.round(y / 50);
// find the nearest contour point from the list for the
// appropriate 100 * 100 rectangle
const d = fields[rx][ry].reduce((min, point) => {
const dist = Math.hypot(point.x - x, point.y - y)
return Math.min(min, dist);
}, 100);
// store that distance value
data[y * width + x] = d;
}
}
}
data.forEach((v, i, a) => {
// find out if the distance to the nearest contour point
// is a local maximum, vertically or horizontally
const vert = a[i - width] < v && a[i + width] < v;
const hor = a[i - 1] < v && a[i + 1] < v;
if (vert || hor) {
// color that point as part of the center line
ctx.fillRect(i % width, Math.floor(i / width), 1, 1);
}
});
<svg width="1000" height="500" style="position:absolute">
<path id="letter" d="M 374.512,316.992 H 220.703 L 193.75,379.687 Q 183.789,402.832 183.789,414.258 183.789,423.34 192.285,430.371 201.074,437.109 229.785,439.16 V 450 H 104.688 V 439.16 Q 129.59,434.766 136.914,427.734 151.855,413.672 170.02,370.605 L 309.766,43.6523 H 320.02 L 458.301,374.121 Q 475,413.965 488.477,425.977 502.246,437.695 526.562,439.16 V 450 H 369.824 V 439.16 Q 393.555,437.988 401.758,431.25 410.254,424.512 410.254,414.844 410.254,401.953 398.535,374.121 Z M 366.309,295.312 298.926,134.766 229.785,295.312 Z M 810.742,247.266 Q 852.051,256.055 872.559,275.391 900.977,302.344 900.977,341.309 900.977,370.898 882.227,398.145 863.477,425.098 830.664,437.695 798.145,450 731.055,450 H 543.555 V 439.16 H 558.496 Q 583.398,439.16 594.238,423.34 600.977,413.086 600.977,379.687 V 123.047 Q 600.977,86.1328 592.48,76.4648 581.055,63.5742 558.496,63.5742 H 543.555 V 52.7344 H 715.234 Q 763.281,52.7344 792.285,59.7656 836.23,70.3125 859.375,97.2656 882.52,123.926 882.52,158.789 882.52,188.672 864.355,212.402 846.191,235.84 810.742,247.266 Z M 657.227,231.445 Q 668.066,233.496 681.836,234.668 695.898,235.547 712.598,235.547 755.371,235.547 776.758,226.465 798.437,217.09 809.863,198.047 821.289,179.004 821.289,156.445 821.289,121.582 792.871,96.9727 764.453,72.3633 709.961,72.3633 680.664,72.3633 657.227,78.8086 Z M 657.227,421.289 Q 691.211,429.199 724.316,429.199 777.344,429.199 805.176,405.469 833.008,381.445 833.008,346.289 833.008,323.145 820.41,301.758 807.812,280.371 779.395,268.066 750.977,255.762 709.082,255.762 690.918,255.762 678.027,256.348 665.137,256.934 657.227,258.398 Z"/>
</svg>
<canvas width="1000" height="500" style="position:absolute"></canvas>

Fill a closed path in easeljs

Is there a way to fill a closed drawn path in easeljs? I have along string of mt(x_t,y_t).lt(x_(t+1),y_(t+1)) that draws a wacky shape. the shape closes off, but I can't find a way to have it actually fill in the closed area. Any ideas?
T is how many coordinates there are to connect, [round.X, round.Y] is the Tx2 array of coordinate pairs, ghf is the graphics object. xline.y is just a the lowest y value.
for(var i=0;i<T;i++){
x0 = round.X[i];
y0 = round.Y[i];
// scale for drawing
px0 = Math.round(xscale * x0);
py0 = Math.round(yscale * y0) + xline.y;
if(x0>gp.xmin){ // if not first point ...
ghf.mt(prevx,prevy).lt(px0,py0); // draw line from prev point to this point
}
// set this point as prev point
prevx = px0;
prevy = py0;
}
// fill out thing
ghf.mt(prevx,prevy).lt(px0,xline.y);
ghf.mt(px0,xline.y).lt(0,xline.y);
x0 = round.X[0];
y0 = round.Y[0];
px0 = Math.round(xscale * x0);
py0 = Math.round(yscale * y0) + xline.y;
ghf.mt(0,xline.y).lt(px0,py0);
ghf.f('red');
Your code is not very helpful, but I think what you need is the beginFill method. See link.
You can use it like this:
var ball = new createjs.Shape();
ball.graphics.setStrokeStyle(5, 'round', 'round');
ball.graphics.beginStroke(('#000000'));
ball.graphics.beginFill("#FF0000").drawCircle(0,0,50);
ball.graphics.endStroke();
ball.graphics.endFill();
ball.graphics.setStrokeStyle(1, 'round', 'round');
ball.graphics.beginStroke(('#000000'));
ball.graphics.moveTo(0,0);
ball.graphics.lineTo(0,50);

How do I translate a set of svg elements under a g when dragging?

JSFiddle: http://jsfiddle.net/Umugb/19/
I've written the code below to drag a set of elements around on an svg. Each g element has a circle that is draggable (but otherwise nothing else is). However, when I drag the circle, I'd like for the rest of the g to translate with it.
The problem is that the dragging goes haywire. If I take out the transform and instead just change the circle's cx and cy to the new_cx and new_cy, then the circle is movable as expected. However, using transform, all of the elements fly off the page with d3.event.dx/dy of abnormally large absolute values.
Is there anything obviously wrong with the code below? Or does anyone have further insight into why this is happening?
var drag = d3.behavior.drag().on("drag", translate);
function translate() {
var g = $("#" + this.parentNode.id);
var circle = d3.select(this);
var r = parseInt(circle.attr("r"));
var box_width = Math.max(g[0].getElementsByClassName('circletext')[0].getBBox().width, 2*r);
var cx = parseInt(circle.attr("cx"));
var cy = parseInt(circle.attr("cy"));
if (g.attr("transform")) {
var translate = g.attr("transform").split('translate')[1].split(',');
cx = cx + parseInt(translate[0].slice(1));
cy = cy + parseInt(translate[1].slice(0,-1));
}
var new_cx = Math.max(r, Math.min(width - box_width + r, d3.event.dx + cx));
var new_cy = Math.max(r, Math.min(height - r, d3.event.dy + cy));
d3.select("#" + this.parentNode.id).
attr("transform",
"translate(" + (new_cx-cx) + "," + (new_cy-cy) + ")");
EDIT: Updated with JSFiddle at top

D3 JS upside down path text

Is it possible to show the text not upside down in this case?
http://jsfiddle.net/paulocoelho/Hzsm8/1/
Code:
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",35)
.append("textPath")
.attr("xlink:href","#yyy")
.attr("startOffset",50)
.text("some text")
;
A great example is Placing Texts on Arcs with D3.js by Nadieh Bremer. A lengthy blog with many images from which the following is an extract:
Flipping the Text on the Bottom Half
You could already feel like it’s finished with that look. But I find those labels along the bottom half, that are upside down, rather hard to read. I’d prefer it if those labels were flipped, so I can read them from left to right again.
To accomplish this, we need to switch the start and end coordinates of the current arc paths along the bottom half so they are drawn from left to right. Furthermore, the sweep-flag has to be set to 0 to get the arc that runs in a counterclockwise fashion from left to right
So for the final act, let’s add a few more lines of code to the .each() statement
//Create the new invisible arcs and flip the direction for those labels on the bottom half
.each(function(d,i) {
//Search pattern for everything between the start and the first capital L
var firstArcSection = /(^.+?)L/;
//Grab everything up to the first Line statement
var newArc = firstArcSection.exec( d3.select(this).attr("d") )[1];
//Replace all the commas so that IE can handle it
newArc = newArc.replace(/,/g , " ");
//If the end angle lies beyond a quarter of a circle (90 degrees or pi/2)
//flip the end and start position
if (d.endAngle > 90 * Math.PI/180) {
var startLoc = /M(.*?)A/, //Everything between the capital M and first capital A
middleLoc = /A(.*?)0 0 1/, //Everything between the capital A and 0 0 1
endLoc = /0 0 1 (.*?)$/; //Everything between the 0 0 1 and the end of the string (denoted by $)
//Flip the direction of the arc by switching the start and end point (and sweep flag)
var newStart = endLoc.exec( newArc )[1];
var newEnd = startLoc.exec( newArc )[1];
var middleSec = middleLoc.exec( newArc )[1];
//Build up the new arc notation, set the sweep-flag to 0
newArc = "M" + newStart + "A" + middleSec + "0 0 0 " + newEnd;
}//if
//Create a new invisible arc that the text can flow along
svg.append("path")
.attr("class", "hiddenDonutArcs")
.attr("id", "donutArc"+i)
.attr("d", newArc)
.style("fill", "none");
});
The only thing that has changed since the previous section is the addition of the if statement. To flip the start and end positions, we can use a few more regular expressions. The current starting x and y location is given by everything in between the capital M and the capital A. The current radius is denoted by everything in between the capital A and the 0 0 1 of the x-axis rotation, large-arc flag and sweep flag. Finally the end location is given by all in between the 0 0 1 and the end of the string (denoted by a $ in regex).
So we save all the pieces in different variables and build/replace up the newArc using the final line in the if statement which has switched the start and end position.
The textPath section needs a small change. For the bottom half arcs, the dy attribute shouldn’t raise the labels above the arc paths, but lower the labels below the arc paths. So we need a small if statement which will result in two different dy values.
(To be able to use the d.endAngle in the if statement I replaced the donutData by pie(donutData) in the .data() step. You can still reference the data itself by using d.data instead of just d, which you can see in the .text() line of code.)
//Append the label names on the outside
svg.selectAll(".donutText")
.data(pie(donutData))
.enter().append("text")
.attr("class", "donutText")
//Move the labels below the arcs for those slices with an end angle greater than 90 degrees
.attr("dy", function(d,i) { return (d.endAngle > 90 * Math.PI/180 ? 18 : -11); })
.append("textPath")
.attr("startOffset","50%")
.style("text-anchor","middle")
.attr("xlink:href",function(d,i){return "#donutArc"+i;})
.text(function(d){return d.data.name;});
It looks like when d3 creates one of those filled arcs it actually creates a filled path shape that always(?) starts on the right and proceeds clockwise - even if you reverse startAngle and endAngle.
If you manually create your own arc path, and put your text on that, you can get it to do the right thing.
var cfg = {
w:400,
h:400
};
var g = d3.select("#testdiv").append("svg").attr("width", cfg.w).attr("height", cfg.h).append("g")
var arct = d3.svg.arc()
.innerRadius(cfg.h / 5)
.outerRadius(cfg.h / 3)
.startAngle(Math.PI/2)
.endAngle(Math.PI*1.5);
var path = g.append("svg:path")
.attr("id","yyy")
.attr("d", arct)
.style("fill","blue")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
// Radius of line text sits on. A value of 3.5 makes it slightly closer to the
// outer radius (so text is placed in the middle of the blue line).
var textpathRadius = (cfg.h / 3.5);
// Make a path for the text to sit on that goes in an anti-clockwise direction.
var textpath = g.append("svg:path")
.attr("id","zzz")
.style("display","none")
.attr("d", "M -"+textpathRadius+" 0 A "+textpathRadius+" "+textpathRadius+" 0 0 0 "+textpathRadius+" 0")
.attr("transform", "translate("+cfg.w/2+","+cfg.h/6+")");
var text = g.append("text")
.style("font-size",30)
.style("fill","#F8F8F8")
.attr("dy",0)
.append("textPath")
.attr("xlink:href","#zzz")
.attr("startOffset","50%")
.style("text-anchor","middle")
.text("some text");
I've never used d3 before so there might be an easier or cleaner way to do what I've done. But at least it should give you a place to start.
Updated fiddle: http://jsfiddle.net/3DfVD/

How is the getBBox() SVGRect calculated?

I have a g element that contains one or more path elements. As I mentioned in another question, I scale and translate the g element by computing a transform attribute so that it fits on a grid in another part of the canvas.
The calculation is done using the difference between two rectangles, the getBBox() from the g element and the rectangle around the grid.
Here is the question -- after I do the transform, I update the contents of the g element and call getBBox() again, without removing the transform. The resulting rectangle appears to be calculated without considering the transform. I would have expected it to reflect the change. Is this behavior consistent with the SVG specification? How do I get the bounding box of the transformed rectangle?
This, BTW, is in an HTML 5 document running in Firefox 4, if that makes any difference.
Update: Apparently this behavior seems pretty clearly in violation of the specification. From the text here at w3c:
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.
Am I reading this correctly? If so this seems to be an errata in the SVG implementation Firefox uses; I haven't had a chance to try any other. I would file a bug report if someone could point me to where.
People often get confused by the behavioral difference of getBBox and getBoundingClientRect.
getBBox is a SVG Element's native method as equivalent to find the offset/clientwidth of HTML DOM element. The width and height is never going to change even when the element is rotated. It cannot be used for HTML DOM Elements.
getBoundingClientRect is common to both HTML and SVG elements. The bounded rectangle width and height will change when the element is rotated or when more elements are grouped.
The behaviour you see is correct, and consistent with the spec.
The transform gets applied, then the bbox is calculated in "current user units", i.e. the current user space. So if you want to see the result of a transform on the element you'd need to look at the bbox of a parent node or similar.
It's a bit confusing, but explained a lot better in the SVG Tiny 1.2 spec for SVGLocatable
That contains a number of examples that clarify what it's supposed to do.
there are at least 2 easy but somewhat hacky ways to do what you ask... if there are nicer (less hacky) ways, i haven't found them yet
EASY HACKy #1:
a) set up a rect that matches the "untransformed" bbox that group.getBBox() is returning
b) apply the group's "unapplied transform" to that rect
c) rect.getBBox() should now return the bbox you're looking for
EASY HACKY #2: (only tested in chrome)
a) use element.getBoundingClientRect(), which returns enough info for you to construct the bbox you're looking for
Apparently getBBox() doesn't take the transformations into consideration.
I can point you here, unfortunately I wasn't able to make it working: http://tech.groups.yahoo.com/group/svg-developers/message/22891
SVG groups have nasty practice - not to accumulate all transformations made. I have my way to cope with this issue. I'm using my own attributes to store current transformation data which I include in any further transformation. Use XML compatible attributes like alttext, value, name....or just x and y for storing accumulated value as atribute.
Example:
<g id="group" x="20" y="100" transform="translate(20, 100)">
<g id="subgroup" alttext="45" transform="rotate(45)">
<line...etc...
Therefore when I'm making transformations I'm taking those handmade attribute values, and when writing it back, I'm writing both transform and same value with attributes I made just for keeping all accumulated values.
Example for rotation:
function symbRot(evt) {
evt.target.ondblclick = function () {
stopBlur();
var ptx=symbG.parentNode.lastChild.getAttribute("cx");
var pty=symbG.parentNode.lastChild.getAttribute("cy");
var currRot=symbG.getAttributeNS(null, "alttext");
var rotAng;
if (currRot == 0) {
rotAng = 90
} else if (currRot == 90) {
rotAng = 180
} else if (currRot == 180) {
rotAng = 270
} else if (currRot == 270) {
rotAng = 0
};
symbG.setAttributeNS(null, "transform", "rotate(" + rotAng + "," + ptx + ", " + pty + ")");
symbG.setAttributeNS(null, "alttext", rotAng );
};
}
The following code takes into account the transformations (matrix or otherwise) from parents, itself, as well as children. So, it will work on a <g> element for example.
You will normally want to pass the parent <svg> as the third argument—toElement—as to return the computed bounding box in the coordinate space of the <svg> (which is generally the coordinate space we care about).
/**
* #param {SVGElement} element - Element to get the bounding box for
* #param {boolean} [withoutTransforms=false] - If true, transforms will not be calculated
* #param {SVGElement} [toElement] - Element to calculate bounding box relative to
* #returns {SVGRect} Coordinates and dimensions of the real bounding box
*/
function getBBox(element, withoutTransforms, toElement) {
var svg = element.ownerSVGElement;
if (!svg) {
return { x: 0, y: 0, cx: 0, cy: 0, width: 0, height: 0 };
}
var r = element.getBBox();
if (withoutTransforms) {
return {
x: r.x,
y: r.y,
width: r.width,
height: r.height,
cx: r.x + r.width / 2,
cy: r.y + r.height / 2
};
}
var p = svg.createSVGPoint();
var matrix = (toElement || svg).getScreenCTM().inverse().multiply(element.getScreenCTM());
p.x = r.x;
p.y = r.y;
var a = p.matrixTransform(matrix);
p.x = r.x + r.width;
p.y = r.y;
var b = p.matrixTransform(matrix);
p.x = r.x + r.width;
p.y = r.y + r.height;
var c = p.matrixTransform(matrix);
p.x = r.x;
p.y = r.y + r.height;
var d = p.matrixTransform(matrix);
var minX = Math.min(a.x, b.x, c.x, d.x);
var maxX = Math.max(a.x, b.x, c.x, d.x);
var minY = Math.min(a.y, b.y, c.y, d.y);
var maxY = Math.max(a.y, b.y, c.y, d.y);
var width = maxX - minX;
var height = maxY - minY;
return {
x: minX,
y: minY,
width: width,
height: height,
cx: minX + width / 2,
cy: minY + height / 2
};
}
I made a helper function, which returns various metrics of svg element (also bbox of transformed element).
The code is here:
SVGElement.prototype.getTransformToElement =
SVGElement.prototype.getTransformToElement || function(elem) {
return elem.getScreenCTM().inverse().multiply(this.getScreenCTM());
};
function get_metrics(el) {
function pointToLineDist(A, B, P) {
var nL = Math.sqrt((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
return Math.abs((P.x - A.x) * (B.y - A.y) - (P.y - A.y) * (B.x - A.x)) / nL;
}
function dist(point1, point2) {
var xs = 0,
ys = 0;
xs = point2.x - point1.x;
xs = xs * xs;
ys = point2.y - point1.y;
ys = ys * ys;
return Math.sqrt(xs + ys);
}
var b = el.getBBox(),
objDOM = el,
svgDOM = objDOM.ownerSVGElement;
// Get the local to global matrix
var matrix = svgDOM.getTransformToElement(objDOM).inverse(),
oldp = [[b.x, b.y], [b.x + b.width, b.y], [b.x + b.width, b.y + b.height], [b.x, b.y + b.height]],
pt, newp = [],
obj = {},
i, pos = Number.POSITIVE_INFINITY,
neg = Number.NEGATIVE_INFINITY,
minX = pos,
minY = pos,
maxX = neg,
maxY = neg;
for (i = 0; i < 4; i++) {
pt = svgDOM.createSVGPoint();
pt.x = oldp[i][0];
pt.y = oldp[i][1];
newp[i] = pt.matrixTransform(matrix);
if (newp[i].x < minX) minX = newp[i].x;
if (newp[i].y < minY) minY = newp[i].y;
if (newp[i].x > maxX) maxX = newp[i].x;
if (newp[i].y > maxY) maxY = newp[i].y;
}
// The next refers to the transformed object itself, not bbox
// newp[0] - newp[3] are the transformed object's corner
// points in clockwise order starting from top left corner
obj.newp = newp; // array of corner points
obj.width = pointToLineDist(newp[1], newp[2], newp[0]) || 0;
obj.height = pointToLineDist(newp[2], newp[3], newp[0]) || 0;
obj.toplen = dist(newp[0], newp[1]);
obj.rightlen = dist(newp[1], newp[2]);
obj.bottomlen = dist(newp[2], newp[3]);
obj.leftlen = dist(newp[3], newp[0]);
// The next refers to the transformed object's bounding box
obj.BBx = minX;
obj.BBy = minY;
obj.BBx2 = maxX;
obj.BBy2 = maxY;
obj.BBwidth = maxX - minX;
obj.BBheight = maxY - minY;
return obj;
}
and full functional example is here:
http://jsbin.com/acowaq/1

Resources