Using Highcharts, I'm trying to position rectangular elements above the "center-of-mass" of a pie chart slice.
I have the formula/algorithm for finding the center of the slice:
shapeArgs = Chart.series[0].points[sliceIndex].shapeArgs,
ang = (shapeArgs.end - shapeArgs.start) / 2 + shapeArgs.start,
posX = cX + shapeArgs.r/2 * Math.cos(ang),
posY = cY + shapeArgs.r/2 * Math.sin(ang)
This finds the center of the slice based on the start & end angles, and the radius of the chart - represented by the black circle in the image below. What I'm wanting is some way to find the center of the biggest rectangle that'll fit in the slice - represented by the green circle below (which I just eye-balled). My reasoning is that an element positioned over the center-of-mass of the slice will fit better in the slice than something positioned at the exact center.
[![enter image description here][1]][1]
My rudimentary fix has been to reduce the factor by which the radius is reduced:
posX = cX + shapeArgs.r/1.3 * Math.cos(ang)
Which seems to work fairly well, but I'm wondering if there's a better way. Does anyone know the math for this?
Edit:
Screenshot of the result of tweaking the factor: https://i.stack.imgur.com/FBH5p.png
Screenshot of the result of doing the Wikipedia math:
https://i.stack.imgur.com/DlGcS.png
According to Wikipedia, the distance of the center of mass of a circular sector from the center is (2r * sin(a)) / (3 * a). I would find the posX and posY like this:
var dist = (2 * shapeArgs.r * Math.sin(ang)) / (3 * ang);
posX = cX + dist * Math.cos(ang);
posY = cY + dist * Math.sin(ang);
Related
I have a grid for a flat, 2D map which is a bit different from usual. The top left point is 0,0 and bottom right is 1000,1000. I have 2 points on this map, an origin/anchor point and a destination.
I am looking to figure out the degrees (in javascript) from the origin to the destination. I have looked through many answers and they all don't produce the correct result.
function getAngle(origin_x, origin_y, destination_x, destination_y) {
var newx = destination_x - origin_x;
var newy = destination_y - origin_y;
var theta = Math.atan2(-newy, newx);
if (theta < 0) {
theta += 2 * Math.PI;
}
theta *= 180 / Math.PI;
return theta;
}
This is what I have so far but it doesn't produce the right angle.
Thankyou very much in advance!
image from mdn Math.atan2 doc
It will give the angle relative to the x-axis, not the y-axis. To convert all you would do is
var newAngle = 90 - theta;
I want to overlay historic aerial photographs onto a map, using the image rotation available in IIIF. The problem that I am facing is that using code such as in http://jsfiddle.net/jamesinealing/tr7obasm/ I need to manually calculate the NW and SE points, which alter based on the rotation, in advance of placing the image on the map.
What I am looking to do is to calculate the pixel dimensions of the new rotated image (the bounding box, if you like) and then use those to at least get something approximate for the lat lng coordinates, which I can then tweak.
My test code for the calculation is at http://jsfiddle.net/jamesinealing/ehp65gy1/ (based on https://stackoverflow.com/a/17453766)
var rotation=30;
var angle=rotation;
// var angle=((360-rotation) % 180);
var imgWidth=300;
var imgHeight=224;
w = Math.sin(angle * Math.PI/180) * imgHeight + Math.cos(angle * Math.PI/180) * imgWidth;
h = Math.sin(angle * Math.PI/180) * imgWidth + Math.cos(angle * Math.PI/180) * imgHeight;
The problem is that it only works up to a 90 degree angle, whilst the IIIF rotation is any value 0-359. I appreciate that any value 180 or above will yield the same results, so some sort of modulus is needed anyway, but can't get my head around why it isn't working over 90 degrees!
The dimensions of the new box only vary according to angle % 90, i.e. between 300*224 and 224*300 on an ellipse, so you could just use Math.abs with the adjusted angle and some if statements to work out which way round it should be.
I'm building an app in which I present some planes with textures. However, I would like to calculate the radius of the helix (which I use in my calculations to create a helix), dynamically based on the frustum width and the camera position.
The helix is positioned at the center of the screen x=0, y=0, z=0.
I would like this to take under consideration the screen orientation (landscape/ portrait).So far this is the code I have but it seems that I'm missing something because the planes at the left and the right are not inside the viewport.
App.prototype.calculateHelixRadius = function(){
// plane width = height = 512;
var friend = this.getFriend();
var vFOV = friend.camera.fov * Math.PI / 180;
var dist = utils.getAbsPointsDistance3D(friend.camera.position, friend.scene.position);
var aspect = friend.settings.container.clientWidth / friend.settings.container.clientHeight;
var frustumHeight = 2.0 * dist * Math.tan(0.5 * vFOV);
var frustumWidth = frustumHeight * aspect;
return utils.isLandscape() ? frustumHeight / 2 : frustumWidth / 2 ;
};
What am I doing wrong and why are the planes at the edges of the screen not inside?
Also for reference here is the code of getAbsPointsDistance3D
var utils = {
// other helpers...
getAbsPointsDistance3D: function(p1, p2) {
var xd = p2.x - p1.x;
var yd = p2.y - p1.y;
var zd = p2.z - p1.z;
return Math.sqrt(xd * xd + yd * yd + zd * zd);
}
};
update
I tried decreasing the dist parameter but the results are not consistent...
I wonder if the following explains your clipping.
You calculate your frustum characteristics, then calculate the helix radius using, say, the frustum width (width or height depending on the screen aspect...I may be getting some of the particulars wrong here because your question does not completely explain the details, but the general concepts still hold). The image below is a top view of the scenario which shows a circle representing the cylinder that encloses the helix. I believe you have calculated radius1. If so, note that there will be clipping of the cylinder (the shaded area), and thus the helix, in "front" of the cylinder centre.
Instead you need to calculate the cylinder/helix radius as shown in the second image, i.e. you need radius2. If the large angle at the image left is fov (again, vFOV? or hFOV?, etc., depending on whether your helix is going up-down or side-to-side, etc.), then its half angle is fov/2. This is the same angle shown in the centre of the cylinder. Thus, you need to decrease your helix radius as follows: radius2 = radius1 * cos(fov/2).
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);
I have Google Maps icons which I need to rotate by certain angles before drawing on the map using MarkerImage. I do the rotation on-the-fly in Python using PIL, and the resulting image is of the same size as the original - 32x32. For example, with the following default Google Maps marker:
, a 30 degrees conter-clockwise rotation is achieved using the following python code:
# full_src is a variable holding the full path to image
# rotated is a variable holding the full path to where the rotated image is saved
image = Image.open(full_src)
png_info = image.info
image = image.copy()
image = image.rotate(30, resample=Image.BICUBIC)
image.save(rotated, **png_info)
The resulting image is
The tricky bit is getting the new anchor point to use when creating the MarkerImage using the new rotated image. This needs to be the pointy end of the icon. By default, the anchor point is the bottom middle [defined as (16,32) in x,y coordinates where (0,0) is the top left corner]. Can someone please explain to me how I can easily go about this in JavaScript?
Thanks.
Update 22 Jun 2011:
Had posted the wrong rotated image (original one was for 330 degrees counter-clockwise). I've corrected that. Also added resampling (Image.BICUBIC) which makes the rotated icon clearer.
To calculate the position of a rotated point you can use a rotation matrix.
Converted into JavaScript, this calculates the rotated point:
function rotate(x, y, xm, ym, a) {
var cos = Math.cos,
sin = Math.sin,
a = a * Math.PI / 180, // Convert to radians because that is what
// JavaScript likes
// Subtract midpoints, so that midpoint is translated to origin
// and add it in the end again
xr = (x - xm) * cos(a) - (y - ym) * sin(a) + xm,
yr = (x - xm) * sin(a) + (y - ym) * cos(a) + ym;
return [xr, yr];
}
rotate(16, 32, 16, 16, 30); // [8, 29.856...]
The formula for rotations about 0,0 is:
x1 = cos(theta) x0 - sin(theta) y0
y1 = sin(theta) x0 + cos(theta) y0
But that's for regular axes, and rotation about 0,0. The PIL rotation is clockwise with "graphics" axes. Plus, it's around the center of the image. The final confusing thing is that the size of the image can change, which needs to be accounted for in the final result.
Procedure: take original point, subtract off center of image, apply "graphics axes" corrected rotation, find new size of image, add back center position of new image.
Rotation using graphics axes is:
x1 = cos(theta) x0 + sin(theta) y0
y1 = -sin(theta) x0 + cos(theta) y0
16,32 - 16,16 is 0, 16. Rotate 30 degrees clockwise rotation (based on your images) gives a point cos(-30)*0+sin(-30)*16, -sin(-30)*0+cos(-30)*16 = -8, 13.86. The final step is adding back the center position of the rotated position.
In an image, downwards is positive Y and rightwards is positive X. However, to apply the rotation formula, we need upwards as positive Y. Therefore, step 1 would be to apply f(x,y) = f(x,h-y), where 'h' is the height of the image.
Let's say the image is rotated with respect to x0,y0. You'd then need to transform your origin to this point. Therefore, step 2 would be f(x,y) = f(x-x0,y-y0). At this stage (i.e. after the two steps), your new co-ordinates would be x-x0, h-y-y0. You're now ready to apply the rotation formula
x1 = x*cos(theta) - y*sin(theta)
y1 = xsin(theta) + ycos(theta)
Use the values of x and y obtained after step two.
You'd get
x1 = (x-x0)*cos(theta) - (h-y-y0)*sin(theta)
y1 = (x-x0)*sin(theta) + (h-y-y0)*cos(theta)
Now, undo transformations done in step 2 and step 1 (in that order).
After undoing step2: xNew = x1 + x0 and yNew = y1 + y0
After undoing step1: xNew = x1 + x0 and yNew = h - (y1 + y0)
This gives you:
xNew = (x-x0)*cos(theta) - (h-y-y0)*sin(theta) + x0
yNew = -(x-x0)*sin(theta) - (h-y-y0)*cos(theta) + (h-y0)