Raphael custom attribute (Arc) results in deformed circle - javascript

I am working on a progress bar plugin for jQuery that utilizes Raphael for smooth graphics.
I tried to transform the attribute function provided by this Raphael example (polar clock).
The problem is, that at first I didn't notice that the Raphael example also has the deformation error there. Relatively larger circles just mitigate it. Looking at smaller ones, it is noticeable.
And yes, I have basicly copy-pasted the function with some minor tweaks, but the end result sport the same error.
I have set up a JSBin where I've added reference circles to my scene, so it's easier to spot the issue: http://jsbin.com/ekovir/1
How do I tune the Arc function to draw proper circle?

I think it's a bug in Chrome's SVG rendering implementation. At least in FireFox and Safari it looks much better.
Also, when selecting the arc-to point, I think it's better to use (center.x + radius * cos(a-0.01), center.y + radius * sin(a-0.01)), instead of (center.x + radius * cos(a) - 0.01, center.y + radius * sin(a)), otherwise the center may shift a bit.
As a workaround, I suggest creating one set of segments for the progress bar and then changing their color as the work is done, instead of drawing new ones over the old. This should look fine in any browser, and I don't think the defects are easy to spot without the contrasting background circle.

I have found what caused the circle to be deformed.
I used stroke-width to set the "fatness" / "cap" of the circle, and the larger it gets, the more it deforms.
At least, those are my observations, it could as well technically be caued by something else.
Anyways, in order to get proper donut, I ended up with this method:
/**
* Donut circle drawing
*
* #param integer start Percentage to start with
* #param float diameter
* #param float fat How fat should the circle bar be
* #return object
*/
var fatDonutArc = function (start, diameter, fat)
{
var center = diameter / 2;
var outerRadius = center;
var innerRadius = center - fat; // subtract fat
var alpha = 360 / 100 * start;
var a = (90 - alpha) * Math.PI / -180; // -180 starts to draw from 12 o'clock
// calculate outer ring point coordinates
var outerX = center + outerRadius * Math.cos(a);
var outerY = center + outerRadius * Math.sin(a);
// calculate inner ring point coordinates
var innerX = center + innerRadius * Math.cos(a);
var innerY = center + innerRadius * Math.sin(a);
// path cache
var path;
if (start !== 100)
{
path = [
// move to start point of inner ring
[
"M",
center,
center - innerRadius
],
// draw a line to outer ring
[
"L",
center,
center - outerRadius
],
// arc to outer ring end
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX,
outerY
],
// move to inner ring end
[
"L",
innerX,
innerY
],
// arc to inner ring start
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
center,
center - innerRadius
]
];
}
else
{
path = [
// move to outer ring start
[
"M",
center,
center - outerRadius
],
// arc around the clock
[
"A",
outerRadius,
outerRadius,
0,
+(alpha > 180),
1,
outerX - .1, // subtract, otherwise the path becomes "reset"
outerY
],
// connect
[
"z"
],
// move to inner circle start
[
"M",
innerX,
innerY
],
// arc around the clock
[
"A",
innerRadius,
innerRadius,
0,
+(alpha > 180),
0,
innerX + .1, // subtract, otherwise the path becomes "reset"
innerY
],
// and connect
[
"z"
]
];
}
return {
path : path
};
};
That's a mashup of: raphael.js - converting pie graph to donut graph + http://raphaeljs.com/polar-clock.html
Here I have set up an example, to see it in action: http://jsbin.com/erusos/1
There still is one unanswered question: In Chrome, is it the CSS renderer, that doesn't fully round the circle, or is it the SVG?
Enjoy!

Related

Javascript Canvas Relative Mouse Coordinates on Rotated Rectangle?

I am trying to treat a rectangle in canvas as a piece of paper, and get the same relative coordinates returned when I hover over the same point regardless of scale, rotation, or translation of the page.
Currently I get accurate results when in portrait or inverted portrait rotations and regardless of scale/translation. However, when I switch to landscape or inverted landscape my results are off.
I've attempted to switch to rotating mouse coordinates with some trigonometric functions I found, but math is not my strong suit and it didn't work.
If someone could point me in the right direction, I would be grateful. I suspect I need to swap axis or height/width when rotating landscape but that hasn't been fruitful either.
R key rotates the "page" through 4, 90 degree changes. Coordinates of your mouse relative to the page, clamped to the page's width/height are displayed in console.
https://jsfiddle.net/2hg6u3wd/2/ (Note, JSFiddle offsets coordinates slightly for an unknown reason)
const orientation = Object.freeze({
portrait: 0,
landscape: -90,
invertedPortrait: 180,
invertedLandscape: 90
});
function CameraRotate(rotation) {
// Rotates using the center of target as origin.
ctx.translate(target.width / 2, target.height / 2);
ctx.rotate(-(currentRot * Math.PI / 180)); // Negate currentRot because ctx.rotate() is additive.
ctx.rotate(rotation * Math.PI / 180);
ctx.translate(-(target.width / 2), -(target.height / 2));
currentRot = rotation;
}
function CameraCalcRelTargetCoords(viewX, viewY) {
return {
x: clamp((viewX - ctx.getTransform().e) / ctx.getTransform().a, 0, page.width),
y: clamp((viewY - ctx.getTransform().f) / ctx.getTransform().d, 0, page.height)
};
}
function clamp(number, min, max) {
return Math.max(min, Math.min(number, max));
}
canvas.addEventListener(`mousemove`, function(e) {
console.log(CameraCalcRelTargetCoords(e.x, e.y));
});
When rotating (-)180 degrees, the scale is stored as skew since the axis are flipped. Thus you must divide by m12 and m21. This flipping also means x and y mouse coordinates need to be swapped as well.
Here is my solution:
function CameraCalcRelTargetCoords(viewX, viewY) {
// Mouse coordinates are translated to a position within the target's rectangle.
let relX, relY
if (currentRot == orientation.landscape || currentRot == orientation.invertedLandscape) {
// Landscape rotation uses skewing for scale as X/Y axis are flipped.
relX = clamp((viewY - ctx.getTransform().f) / ctx.getTransform().b, 0, target.width);
relY = clamp((viewX - ctx.getTransform().e) / ctx.getTransform().c, 0, target.height);
} else {
relX = clamp((viewX - ctx.getTransform().e) / ctx.getTransform().a, 0, target.width),
relY = clamp((viewY - ctx.getTransform().f) / ctx.getTransform().d, 0, target.height)
}
return {x: relX, y: relY};
}

How calculate a 1/4 circle arc to move along (bezier curve)?

I'm using JQuery.path to move an object along a bezier curve. When the item is clicked, I can determine the start and end points. How do I calculate the angle and length to make the element move from point A to point B on an arc that's 1/4 of a circle intersecting the start and end point?
I essentially want it to move along a curve that never dips lower than the starting y position and never to the left of the end x position.
var path = {
start: {
x: currentLeft,
y: currentTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
},
end: {
x: endLeft,
y: endTop,
angle: ????, //Don't know how to calculate this
length: ???? //Don't know how to calculate this
}
};
jQuery(myElement).animate(
{
path: new jQuery.path.bezier(path)
}
);
Approx. what I want:
Approx what I'm getting (they're dipping too low):
A generalised solution is slightly tricky because it must handle diagonal movements in each of four diagonal directions, and horizontal, and vertical.
First, you need a couple of utility functions :
function r2d(x) {
/* radians to degrees */
return x * 180 / Math.PI;
}
function smaller(x, y) {
/* returns the closer of x|y to zero */
var x_ = Math.abs(x);
var y_ = Math.abs(y);
return (Math.min(x_, y_) === x_) ? x : y;
}
Now a main function, anim, accepts a jQuery object (containing the element of interest) and an end object (with properties .left and .top ).
function anim($el, end) {
var current = $el.position();
var slope1 = (end.top - current.top) / (end.left - current.left);
var slope2 = 1 / slope1;
var endAngle = r2d(Math.atan(smaller(slope1, slope2)));
var startAngle = -endAngle;
var length = 1/3; //Vary between 0 and 1 to affect the path's curvature. Also, try >1 for an interesting effect.
//For debugging
$("#endAngle").text(endAngle);
$("#startAngle").text(startAngle);
$("#length").text(length);
var path = {
start: {
x: current.left,
y: current.top,
angle: startAngle,
length: length
},
end: {
x: end.left,
y: end.top,
angle: endAngle,
length: length
}
};
$el.animate({ path: new jQuery.path.bezier(path) });
}
The calculation of endAngle is pretty simple for each individual case (the four diagonals, horizontal and vertical) but slightly tricky for a generalised solution. It took me a while to develop something that worked in all cases.
DEMO
If the "what you want" is really what you need, i.e. 90 degree departure and arrivals, then we can solve this problem pretty much instantly:
p_start = { X:..., Y:... }
p_end = { X:..., Y:... }
dx = p_end.X - p_start.X
dy = p_end.Y - p_start.Y
control_1 = { X: p_start.X, Y: p_start.Y + 0.55228 * dy }
control_2 = { X: p_end.X - 0.55228 * dx, Y: p_end.Y }
And done. What we've basically done is pretend that the start and end points lie on a circle, and computer the control points such that the resulting Bezier curve has minimal error wrt the quarter circular arc.
In terms of angles: The departure from start is always at angle π/2, and the arrival at the end points is always at angle 0.

Depending objects, consider rotation when translating an object

I built a simple 2D-car consisting of three elements, using Raphael JS. For that I have the body as an Image and two tires. I separated the elements because I want the tires to spin in an animation.
var curCar = raphael.image("car.png", /* x */ 0, /* y */ 0, /* width */ 120, /* height*/ 82)
var tires = raphael.set();
tires.push(
raphael.image("tire.png", 2, 50, 32, 32),
raphael.image("tire.png", 84, 50, 32, 32)
);
As you can see, the tires are positioned on the "bottom" of the car and are already aligned right.
Now, curCar follows a path using Raphael.animate, which works fine. For every update of the animation I translate the tires to curCar's position. It kinda looks like this:
function animationUpdate (pos) {
curCar.transform("t" + [pos.x, pos.y] +" r"+ pos.alpha);
tires.transform("t" + [pos.x, pos.y ]);
};
"t" stands for translate(x,y), r stands for rotation. These values are calculated automatically by Raphael.
When following a straight path this all works out, but as soon as curCar is rotating, the calculation for the tires fails.
An example:
curCar is at position x: 100 / y: 200 and is rotated by 10°.
The tires are translated to x:102 / y:150 and x: 184 / y: 250. Thus curCar is rotated by 10° the tires are out of place.
My main question is:
How do I calculate the correct X/Y-position considering the rotation?
There must be a common mathematical formula doing this.
The car wheels should be transformed w.r.t. center of rotation of car (body). It is to be noted that the center of rotation can be anywhere w.r.t. the car. For the sake of simplicity, the following code assumes the center of rotation to coincide with the car center.
Finally, Raphael can take care of the mathematical transformations, if translations and rotations are in right the order of execution.
var paper = Raphael.paper(0, 0, 500, 400);
// tire width
var tw = 10,
thw = tw * 0.5,
// car width
cw = 100,
chw = cw * 0.5,
// car height
ch = 50,
chh = ch * 0.5,
// x of tire 1 w.r.t. car
t1xc = -chw - thw,
// x of tire 2 w.r.t. car
t2xc = -chw - thw,
// y of tire 1 w.r.t. car
t1yc = -chh - thw,
// y of tire 2 w.r.t. car
t2yc = chh - thw;
var car = paper.rect(20, 20, cw, ch),
// car center
cx = car.attr('x') + chw,
cy = car.attr('y') + chh,
// tire 1
t1 = paper.rect(cx + t1xc, cy + t1yc, tw, tw),
// tire 2
t2 = paper.rect(cx + t2xc, cy + t2yc, tw, tw);
// Translate by 100, 100 and rotate by 30 deg
car.transform("...t100,100r30");
// car center post transformation
cx = car.attr('x') + chw;
cy = car.attr('y') + chh;
// Transform tires, with rotation w.r.t. car center
/* Edited - To add wheel rotation about axle (25 deg) */
t1.transform("...t100,100r30," + [cx, cy] + "r25");
t2.transform("...t100,100r30," + [cx, cy] + "r25");
Hope this helps.
Let's leave the translation of the whole car out for the moment. You want to rotate the car and its tires. For each tire, you can find its new center by applying the rotation matrix to its old center.
x' = x∙cos α − y∙sin α
y' = x∙sin α + y∙cos α
I assume that Raphaël uses the upper left corner as the position of an image. If so, then your original centers would be (2+32/2, 50+32/2)=(18,66) and (84+32/2, 50+32/2)=(100,66). So these would be (x, y) for the above formula. The resulting position would be again a center position, so you'd have to subtract 16 from all coordinates. The translation of the whole car could simply be added to that result.

Canvas ArcTo confusion

So I am once again dealing with annular sectors which is not my forte. I can use the .arc method on canvas just fine, the problem comes from needing my arc to be part of a path.
So for example:
ctx.save();
ctx.arc(centerX, centerY, radius, startAngle, endAngle, true);
ctx.stroke();
ctx.restore();
Works fine. But now I need it as part of a path, so I have something like this:
var pointArray = [...]; //this contains all four corner points of the annular sector
ctx.save();
ctx.moveTo(pointArray[0].x, pointArray[0].y);
ctx.lineTo(pointArray[1].x, pointArray[1].y); //so that draws one of the flat ends
ctx.arcTo(?, ?, pointArray[2].x pointArray[2].y, radius);
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern:
http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html
Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
Thanks for the help superior geometric minds of stackoverflow!
UPDATE
Ok, so I am having to do svg canvas inter-polarity here, and using coffee-script, actual production code follows!
annularSector : (startAngle,endAngle,innerRadius,outerRadius) ->
startAngle = degreesToRadians startAngle+180
endAngle = degreesToRadians endAngle+180
p = [
[ #centerX+innerRadius*Math.cos(startAngle), #centerY+innerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(startAngle), #centerY+outerRadius*Math.sin(startAngle) ]
[ #centerX+outerRadius*Math.cos(endAngle), #centerY+outerRadius*Math.sin(endAngle) ]
[ #centerX+innerRadius*Math.cos(endAngle), #centerY+innerRadius*Math.sin(endAngle) ]
]
angleDiff = endAngle - startAngle
largeArc = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)
if #isSVG
commands = []
commands.push "M" + p[0].join()
commands.push "L" + p[1].join()
commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
commands.push "L" + p[3].join()
commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
commands.push "z"
return commands.join(" ")
else
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
##gaugeCTX.arcTo
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
##gaugeCTX.moveTo p[2][0], p[2][1]
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, startAngle, endAngle, false
THE SOLUTION
#gaugeCTX.moveTo p[0][0], p[0][1]
#gaugeCTX.lineTo p[1][0], p[1][1]
#gaugeCTX.arc #centerX, #centerY, outerRadius, startAngle, endAngle, false
#gaugeCTX.lineTo p[3][0], p[3][1]
#gaugeCTX.arc #centerX, #centerY, innerRadius, endAngle, startAngle, true #note that this arc is set to true and endAngle and startAngle are reversed!
I recently found myself disappointed by the arcTo() method (which really should have been called roundedCorner() ). I decided to come up with a general-purpose work-around for people who want to use the cx,cy,r,theta1,theta2 expression as well:
http://www.purplefrog.com/~thoth/art/paisley/arcTo.html
With the important bit of code copied in here:
/**
if code is "move" then we will do a moveTo x0,y0
if code is "line" then we will do a lineTo x0,y0
if code is anything else, we'll assume the cursor is already at x0,y0
*/
function otherArcTo(ctx, cx, cy, r, theta1, theta2, code)
{
console.log([cx,cy,r,theta1, theta2, code])
var x0 = cx + r*Math.cos(theta1)
var y0 = cy + r*Math.sin(theta1)
if (code=="move") {
ctx.moveTo(x0,y0)
} else if (code=="line") {
ctx.lineTo(x0,y0)
}
var dTheta = theta2-theta1
var nChunks = Math.ceil( Math.abs(dTheta) / (0.67*Math.PI) )
if (nChunks <=1) {
var theta3 = theta1 + dTheta/2
var r3 = r/Math.cos(dTheta/2)
var x1 = cx + r3*Math.cos(theta3)
var y1 = cy + r3*Math.sin(theta3)
var x2 = cx + r*Math.cos(theta2)
var y2 = cy + r*Math.sin(theta2)
ctx.arcTo(x1,y1,x2,y2, r)
} else {
for (var i=0; i<nChunks; i++) {
var code2 = null
if (i==0)
code2 = code
otherArcTo(ctx, cx, cy, r,
theta1 + dTheta*i/nChunks,
theta1 + dTheta*(i+1)/nChunks, code2)
}
}
}
While your question/code is not 100% clear to me,
arcTo() still has browser/rendering problems, so use arc() for now.
(Please forgive me, I can't give detailed link right now since I to became a victim of new forced firefox 12 crap and lost years of notes in my ff3.6 powered system, which it simply deleted during it's unapproved update).
arc() works with radians, so do a quick read-up on wiki how Math.PI relates to radians, then whip up some formula to convert your degrees (or what ever you might wish) to radians.
You'll be doing something like: (((2 * Math.PI) / 360) * 270)    (=3/4 circle)
By the way: I did not ran into noticeable drawing problems with Radian/Unit conversions and ECMAscript's floating point behavior!
Also don't forget beginPath() and closePath() (and stroke() where needed): don't make the canvas guess!! This is usually key to drawing (closed) paths!!
You might also want to look at bezierCurveTo().
UPDATE (on TS' update):
Looking at your picture (which I guess is the rendering of your problem), I think I see what you want: pieces of pie-charts.
This is simple, they are 2 arcs and two lines between a beginPath() and closePath() (and a fill-color).
What you want to do is to center your origin (point 0,0) with translate(). Before you do this, read-up on getting crisp lines: the trick is to translate to half pixels: (x.5,y.5).
Then make one 'main-canvas' and one 'temp-canvas'. For each piece-of-pie, draw a on clean temp-canvas (just set it's width and height instead of some clear mumbo jumbo) and put this temp-canvas on your main-canvas. Lastly render/output your main-canvas. Done.
The 'magic' (plain math) in your script that does the translation between your existing svg-path I cannot help you with, since I('m ashamed to admit) don't recognize any javascript in your updated source.
Hope this helps!
Update 2: Actually.. if you would tell us the format of your points/coordinates array.. that would really help! Then we would know from where to where you are drawing.
The best solution MIGHT indeed be to whip-up a javascript function that accepts your points-array's..
This way your coffeescript could simply spit-out your known values (data-format) to the javascript that was needed anyways to render canvas (in html).
Which makes me think.. there must be existing svg-path to canvas translation-scripts .. right? Maybe some-one knows of a tried and tested one and can link/copy it here (for future reference)..
Update 3:
HINT: Don't forget: you can rotate the canvas in drawing-mode, but also when layering canvas'.
When you rotate (which works with the same radian-principle mentioned above) the canvas will rotate around it's origin-point (0,0) which is why translating (to the center of the canvas + 0.5px) is so useful for drawing these kind circle-based shapes!!!
I was having trouble with this myself. Once I drew it out on a piece of paper and used a little bit of geometry and trigonometry, it was pretty simple.
This function will help you calculate the points you need for the arcTo() function. You can move (translate) the arc by adding/subtracting from from the x and y at each point.
function calculateArcPoints(radius, rotation, sectionAngle) {
var halfSectionAngle = sectionAngle / 2;
return {
control: {
x: Math.cos(rotation) * radius / Math.cos(halfSectionAngle),
y: -1 * Math.sin(rotation) * radius / Math.cos(halfSectionAngle)
},
start: {
x: Math.cos(rotation - halfSectionAngle) * radius,
y: -1 * Math.sin(rotation - halfSectionAngle) * radius
},
end: {
x: Math.cos(rotation + halfSectionAngle) * radius,
y: -1 * Math.sin(rotation + halfSectionAngle) * radius
}
};
}
I used KineticJS and no SVG or coffee-script, so the rotation and translation was done outside the drawing function. Here's the full code on jsFiddle. I drew out multiple annular sections around a circle, but you can easily modify it to only draw one. Basically, you have an inner radius, an outer radius, and you connect each of them with straight lines at their start and end points.
That tangent of tangent coordinate is driving me mad. Plus I have a serious concern: http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html Makes it sound like an arc drawn with arcTo could never cover 180degrees or more of a circle and there will be times that my annular sector will be greater than 180degrees.
You are correct about the arcTo() function. It can only produce arcs that are less than 180 degrees. Tangent lines at >= 180° will never intersect, so there cannot be a control point for the arcTo() function. You could just draw two or (I would do three for an entire annulus) more adjacent to each other.

Calculate the bounding box's X, Y, Height and Width of a rotated element via JavaScript

Basically I'm asking this question for JavaScript: Calculate Bounding box coordinates from a rotated rectangle
In this case:
iX = Width of rotated (blue) HTML element
iY = Height of rotated (blue) HTML element
bx = Width of Bounding Box (red)
by = Height of Bounding Box (red)
x = X coord of Bounding Box (red)
y = Y coord of Bounding Box (red)
iAngle/t = Angle of rotation of HTML element (blue; not shown but
used in code below), FYI: It's 37 degrees in this example (not that it matters for the example)
How does one calculate the X, Y, Height and Width of a bounding box (all the red numbers) surrounding a rotated HTML element (given its width, height, and Angle of rotation) via JavaScript? A sticky bit to this will be getting the rotated HTML element (blue box)'s original X/Y coords to use as an offset somehow (this is not represented in the code below). This may well have to look at CSS3's transform-origin to determine the center point.
I've got a partial solution, but the calculation of the X/Y coords is not functioning properly...
var boundingBox = function (iX, iY, iAngle) {
var x, y, bx, by, t;
//# Allow for negetive iAngle's that rotate counter clockwise while always ensuring iAngle's < 360
t = ((iAngle < 0 ? 360 - iAngle : iAngle) % 360);
//# Calculate the width (bx) and height (by) of the .boundingBox
//# NOTE: See https://stackoverflow.com/questions/3231176/how-to-get-size-of-a-rotated-rectangle
bx = (iX * Math.sin(iAngle) + iY * Math.cos(iAngle));
by = (iX * Math.cos(iAngle) + iY * Math.sin(iAngle));
//# This part is wrong, as it's re-calculating the iX/iY of the rotated element (blue)
//# we want the x/y of the bounding box (red)
//# NOTE: See https://stackoverflow.com/questions/9971230/calculate-rotated-rectangle-size-from-known-bounding-box-coordinates
x = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (bx * Math.cos(t) - by * Math.sin(t));
y = (1 / (Math.pow(Math.cos(t), 2) - Math.pow(Math.sin(t), 2))) * (-bx * Math.sin(t) + by * Math.cos(t));
//# Return an object to the caller representing the x/y and width/height of the calculated .boundingBox
return {
x: parseInt(x), width: parseInt(bx),
y: parseInt(y), height: parseInt(by)
}
};
I feel like I am so close, and yet so far...
Many thanks for any help you can provide!
TO HELP THE NON-JAVASCRIPTERS...
Once the HTML element is rotated, the browser returns a "matrix transform" or "rotation matrix" which seems to be this: rotate(Xdeg) = matrix(cos(X), sin(X), -sin(X), cos(X), 0, 0); See this page for more info.
I have a feeling this will enlighten us on how to get the X,Y of the bounding box (red) based solely on the Width, Height and Angle of the rotated element (blue).
New Info
Humm... interesting...
Each browser seems to handle the rotation differently from an X/Y perspective! FF ignores it completely, IE & Opera draw the bounding box (but its properties are not exposed, ie: bx & by) and Chrome & Safari rotate the rectangle! All are properly reporting the X/Y except FF. So... the X/Y issue seems to exist for FF only! How very odd!
Also of note, it seems that $(document).ready(function () {...}); fires too early for the rotated X/Y to be recognized (which was part of my original problem!). I am rotating the elements directly before the X/Y interrogation calls in $(document).ready(function () {...}); but they don't seem to update until some time after(!?).
When I get a little more time, I will toss up a jFiddle with the example, but I'm using a modified form of "jquery-css-transform.js" so I have a tiny bit of tinkering before the jFiddle...
So... what's up, FireFox? That ain't cool, man!
The Plot Thickens...
Well, FF12 seems to fix the issue with FF11, and now acts like IE and Opera. But now I am back to square one with the X/Y, but at least I think I know why now...
It seems that even though the X/Y is being reported correctly by the browsers for the rotated object, a "ghost" X/Y still exists on the un-rotated version. It seems as though this is the order of operations:
Starting with an un-rotated element at an X,Y of 20,20
Rotate said element, resulting in the reporting of X,Y as 15,35
Move said element via JavaScript/CSS to X,Y 10,10
Browser logically un-rotates element back to 20,20, moves to 10,10 then re-rotates, resulting in an X,Y of 5,25
So... I want the element to end up at 10,10 post rotation, but thanks to the fact that the element is (seemingly) re-rotated post move, the resulting X,Y differs from the set X,Y.
This is my problem! So what I really need is a function to take the desired destination coords (10,10), and work backwards from there to get the starting X,Y coords that will result in the element being rotated into 10,10. At least I know what my problem is now, as thanks to the inner workings of the browsers, it seems with a rotated element 10=5!
I know this is a bit late, but I've written a fiddle for exactly this problem, on an HTML5 canvas:
http://jsfiddle.net/oscarpalacious/ZdQKg/
I hope somebody finds it useful!
I'm actually not calculating your x,y for the upper left corner of the container. It's calculated as a result of the offset (code from the fiddle example):
this.w = Math.sin(this.angulo) * rotador.h + Math.cos(this.angulo) * rotador.w;
this.h = Math.sin(this.angulo) * rotador.w + Math.cos(this.angulo) * rotador.h;
// The offset on a canvas for the upper left corner (x, y) is
// given by the first two parameters for the rect() method:
contexto.rect(-(this.w/2), -(this.h/2), this.w, this.h);
Cheers
Have you tried using getBoundingClientRect() ?
This method returns an object with current values of "bottom, height, left, right, top, width" considering rotations
Turn the four corners into vectors from the center, rotate them, and get the new min/max width/height from them.
EDIT:
I see where you're having problems now. You're doing the calculations using the entire side when you need to be doing them with the offsets from the center of rotation. Yes, this results in four rotated points (which, strangely enough, is exactly as many points as you started with). Between them there will be one minimum X, one maximum X, one minimum Y, and one maximum Y. Those are your bounds.
My gist can help you
Bounding box of a polygon (rectangle, triangle, etc.):
Live demo https://jsfiddle.net/Kolosovsky/tdqv6pk2/
let points = [
{ x: 125, y: 50 },
{ x: 250, y: 65 },
{ x: 300, y: 125 },
{ x: 175, y: 175 },
{ x: 100, y: 125 },
];
let minX = Math.min(...points.map(point => point.x));
let minY = Math.min(...points.map(point => point.y));
let maxX = Math.max(...points.map(point => point.x));
let maxY = Math.max(...points.map(point => point.y));
let pivot = {
x: maxX - ((maxX - minX) / 2),
y: maxY - ((maxY - minY) / 2)
};
let degrees = 90;
let radians = degrees * (Math.PI / 180);
let cos = Math.cos(radians);
let sin = Math.sin(radians);
function rotatePoint(pivot, point, cos, sin) {
return {
x: (cos * (point.x - pivot.x)) - (sin * (point.y - pivot.y)) + pivot.x,
y: (sin * (point.x - pivot.x)) + (cos * (point.y - pivot.y)) + pivot.y
};
}
let boundingBox = {
x1: Number.POSITIVE_INFINITY,
y1: Number.POSITIVE_INFINITY,
x2: Number.NEGATIVE_INFINITY,
y2: Number.NEGATIVE_INFINITY,
};
points.forEach((point) => {
let rotatedPoint = rotatePoint(pivot, point, cos, sin);
boundingBox.x1 = Math.min(boundingBox.x1, rotatedPoint.x);
boundingBox.y1 = Math.min(boundingBox.y1, rotatedPoint.y);
boundingBox.x2 = Math.max(boundingBox.x2, rotatedPoint.x);
boundingBox.y2 = Math.max(boundingBox.y2, rotatedPoint.y);
});
Bounding box of an ellipse:
Live demo https://jsfiddle.net/Kolosovsky/sLc7ynd1/
let centerX = 350;
let centerY = 100;
let radiusX = 100;
let radiusY = 50;
let degrees = 200;
let radians = degrees * (Math.PI / 180);
let radians90 = radians + Math.PI / 2;
let ux = radiusX * Math.cos(radians);
let uy = radiusX * Math.sin(radians);
let vx = radiusY * Math.cos(radians90);
let vy = radiusY * Math.sin(radians90);
let width = Math.sqrt(ux * ux + vx * vx) * 2;
let height = Math.sqrt(uy * uy + vy * vy) * 2;
let x = centerX - (width / 2);
let y = centerY - (height / 2);

Categories