Javascript map() forEach() method not working in succession - javascript

I know its a bit convoluted, but consider this hypothetical:
class test {
points = [[10,15]];
rotate (angle){
let deg = angle *= Math.PI/180; //degrees to radians
this.points.map((point, pointIndex) => point.map((value, axis) => this.points[pointIndex][axis] = axis === 0 ? (this.points[pointIndex][0]*Math.cos(deg)-this.points[pointIndex][1]*Math.sin(deg)):(this.points[pointIndex][1]*Math.cos(deg)+this.points[pointIndex][0]*Math.sin(deg))));
//This essentially maps the current points to their projections after rotating them by some angle
//It performs two different operations respective to the two different values in the nested array
}
}
let foo = new test;
foo.rotate(90);
console.log(foo.points);
Running this will return:
Array [ -15, -14.999999999999998 ]
Which, for arguments sake, is not the intended result.
At first I assumed it had something to do with the use of the "?" operator, but eliminating this and separating the outcomes into their own mapping:
class test {
points = [[10,15]];
rotate (angle){
let deg = angle *= Math.PI/180;
this.points.map((point, pointIndex) => this.points[pointIndex][0]=(this.points[pointIndex][0]*Math.cos(deg)-this.points[pointIndex][1]*Math.sin(deg)));
this.points.map((point, pointIndex) => this.points[pointIndex][1]=(this.points[pointIndex][1]*Math.cos(deg)+this.points[pointIndex][0]*Math.sin(deg)));
}
}
let foo = new test;
foo.rotate(90);
console.log(foo.points);
But this results in the same outcome. However, when running either of line by itself, which, because they are split, affects only the first or second element depending on which is eliminated, the accurate result is returned:
Array [ -15, 15 ] (if the second line is removed)
Array [ 10, 10.000000000000002 ] (if the first line is removed)
Both of which return the accurate value of their respective index. ([ -15, 10.000000000000002 ] is correct, taking the first element of the first array and the second of the second.)
For some reason, by running them in succession, something fails.
Thank you in advance.
Edit: The same issue occurs when using forEach().

You shouldn't access the current object inside an transformation.
class test {
points = [[10,15]];
rotate (angle){
let deg = angle * Math.PI/180; //degrees to radians
this.points = this.points.map((point) => {
let newPoint = [0,0];
newPoint[0] = point[0]*Math.cos(deg)-point[1]*Math.sin(deg);
newPoint[1] = point[1]*Math.cos(deg)+point[0]*Math.sin(deg);
return newPoint
})
//This essentially maps the current points to their projections after rotating them by some angle
//It performs two different operations respective to the two different values in the nested array
}
}
let foo = new test;
foo.rotate(90);
console.log(foo.points);

You are transforming one coordinate and then doing to operation for the other one with the new value (not the old one as you should). I think that's the problem.
class test {
points = [[10, 15]];
rotate (angle){
let radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians);
this.points = this.points.map((p) => {
return [cos * p[0] + sin * p[1], cos * p[1] - sin * p[0]];
})
}
}
let foo = new test;
foo.rotate(90);
console.log(foo.points);
Link to jsFiddle

Related

Figuring out the value for PI

Let's say I have a function called bars()
bars () {
const bars = []
for (let i = 0; i < this.numberOfBars; i++) {
bars.push(Math.sqrt(this.numberOfBars * this.numberOfBars - i * i))
}
return bars
}
If I'm reducing the bars array to approximate PI, what should be on the right side of the arrow function?
PI = bars().reduce((a, b) =>
I tried adding the values and dividing by the number of bars, but I'm not getting anywhere near the approximation of Pi. I feel like there's a simple trick that I'm missing.
Your funcion seems to list lengths of "bars" in a quarter of a circle, so we have to add them all up (to have the area of the quarter of a circle), then multiply by 4 (because there is 4 quarter) and the divide by this.numberOfBars ^ 2 because area = π * r^2, but like we have to know the radius, it is better using a pure function :
// Your function rewritten as a pure one
const bars = numberOfBars => {
const bars = []
for (let i = 0; i < numberOfBars; i++) {
bars.push(Math.sqrt(numberOfBars * numberOfBars - i * i))
}
return bars
}
// Here we take 1000 bars as an example but in your case you replace it by this.numberOfBars
// Sum them all up, multiply by 4, divide by the square of the radius
const PI = bars(1000).reduce((g, c) => g + c) * 4 / Math.pow(1000, 2)
console.log(PI)
/** Approximates PI using geometry
* You get a better approximation using more bars and a smaller step size
*/
function approximatePI(numberOfBars, stepSize) {
const radius = numberOfBars * stepSize;
// Generate bars (areas of points on quarter circle)
let bars = [];
// You can think of i as some point along the x-axis
for (let i = 0; i < radius; i += stepSize) {
let height = Math.sqrt(radius*radius - i*i)
bars.push(height * stepSize);
}
// Add up all the areas of the bars
// (This is approximately the area of a quarter circle if stepSize is small enough)
const quarterArea = bars.reduce((a, b) => a + b);
// Calculate PI using area of circle formula
const PI = 4 * quarterArea / (radius*radius)
return PI;
}
console.log(`PI is approximately ${approximatePI(100_000, 0.001)}`);
There is no reason to push all terms to an array, then to reduce the array by addition. Just use an accumulator variable and add all terms to it.
Notice that the computation becomes less and less accurate the closer you get to the end of the radius. If you sum to half of the radius, you obtain r²(3√3+π)/24, from which you can draw π.
(Though in any case, this is one of the worst methods to evaluate π.)

Get angle Two lines svg react js

Hellow it possible get angle two lines in react js, using svg, without using the svg set attribute?
already tried several tutorials of the stack but none really returned the angle between the two lines and yes only the angle in which the line is, I tried this.
findAngle(p0,p1,p2) {
var a = Math.pow(10,2) + Math.pow(100,2),
b = Math.pow(10,2) + Math.pow(10,2),
c = Math.pow(10,2) + Math.pow(70,2);
var aa = Math.acos( (a+b-c) / Math.sqrt(4*a*b) );
console.log(aa);
}
obs: these values are in my two lines.
Based on this answer, you want something like this:
// helper function: make a point
function point(x,y){
return {'x':x,'y':y};
}
// helper function: get distance between points a and b (by Pythagoras)
function d(a,b) {
return Math.sqrt(d2(a,b));
}
// helper function: get square of distance between points a and b
function d2(a,b) {
return (a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y);
}
// helper function: convert radians to degrees
function rad2deg(angleInRadians) {
return angleInRadians/(Math.PI/180);
}
// get the angle in degrees between ab and ac, using the cosine rule:
function angle_deg(a,b,c) {
return rad2deg(Math.acos((d2(a,b) + d2(a,c) - d2(c,b)) / (2 * d(a,b) * d(a,c))));
}
p0 = point(0,0);
p1 = point(1,0);
p2 = point(0,1);
console.log(angle_deg(p0,p1,p2)); // 90

How to find angle between two straight lines (paths) on a SVG in Javascript?

I have two straight lines as <path> in SVG canvas. Using pixel coordinates of LineA (A1x, A1y) (A2x, A2y) and LineB (B1x, B1y) (B2x, B2y) how can I calculate the angle between these lines.
I have below code which works for THREE points (it works for green cases in below image). It does not work when (A2x, A2y) != (B1x, B1y).
How can I modify this formula to work even when lines are not joined.
function find_angle(p0,p1,c) {
var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
Math.pow(c.y-p0.y,2));
var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
Math.pow(c.y-p1.y,2));
var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
Math.pow(p1.y-p0.y,2));
var angle = Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
return angle * (180 / Math.PI);
}
You can exploit Math.atan2 function with cross product and dot product of direction vectors for these segments. Note the atan2 returns signed angle in range -Pi...Pi
//find vector components
var dAx = A2x - A1x;
var dAy = A2y - A1y;
var dBx = B2x - B1x;
var dBy = B2y - B1y;
var angle = Math.atan2(dAx * dBy - dAy * dBx, dAx * dBx + dAy * dBy);
if(angle < 0) {angle = angle * -1;}
var degree_angle = angle * (180 / Math.PI);
You want P0-C, P1-D instead of P0-C, P1-C: just translate one of the segments to let D and C coincide: P1' = P1 - D + C (then D' = C).
After submitting my answer, I realize this is the same solution as the one provided by #YvesDaoust. That answer is a more concise conceptual summary of the same approach I have fleshed out here with a JavaScript example.
The answer is fairly simple:
function find_disconnected_angle(p0,c0, p1,c1) {
return find_angle({x:p0.x-c0.x+c1.x,y:p0.y-c0.y+c1.y},p1,c1);
}
You can calculate the angle from scratch using trigonometry fundamentals. However, to make your life easier you can also just use the function you already have. First, just mathematically translate one line so that one of its end points coincides with one of the end points of the other line. There are four different ways of matching up one end point from each line, and each way will produce a potentially different angle measure. However, this is no bigger of a dilemma than you having to figure out which angle you want of the four angles you get when you take each of the original untranslated line segments, extend each into an infinite line, and examine the four angles where those two lines intersect.
You need a function that takes 4 points as inputs, i.e. p0, p1, p2 and p3. However, just to make clear which points are being made coincidental, I've instead labeled them as p0, c0, p1 and c1, such that p0 and c0 are both being moved in such a way as to make c0 and c1 coincide, resulting in three points: p0new, p1 and c, the latter of which equals both c1 and c0new.
Update: Upon examining your original function more closely, I realize my discussion above of the choice of four possible angles may not be relevant with the exact function implementation you have written, as the order of points p0 and p1 does not matter for your function. You could rewrite your original function, perhaps using some of the concepts from the other answers, to be able to more fully control which angle you get as a result, if that's really what you want. In any case, the general concept behind my answer holds: if you already have a function that calculates an angle between 3 points (with whatever limitations the algorithm has), you can use the same function on two disconnected line segments by simply translating one so that two end points coincide and then using the same function (again, with whatever limitations the algorithm still has).
function find_angle(p0,p1,c) {
var p0c = Math.sqrt(Math.pow(c.x-p0.x,2)+
Math.pow(c.y-p0.y,2));
var p1c = Math.sqrt(Math.pow(c.x-p1.x,2)+
Math.pow(c.y-p1.y,2));
var p0p1 = Math.sqrt(Math.pow(p1.x-p0.x,2)+
Math.pow(p1.y-p0.y,2));
var angle = Math.acos((p1c*p1c+p0c*p0c-p0p1*p0p1)/(2*p1c*p0c));
return angle * (180 / Math.PI);
}
function find_disconnected_angle(p0,c0, p1,c1) {
return find_angle({x:p0.x-c0.x+c1.x,y:p0.y-c0.y+c1.y},p1,c1);
}
console.log(
find_angle(
{x: 7, y: 2},
{x: 7, y: 7},
{x: 2, y: 2}
)
);
console.log(
find_disconnected_angle(
{x: 27, y: 42},
{x: 22, y: 42},
{x: 7, y: 7},
{x: 2, y: 2}
)
);
finding the angle (#) between two lines:
tan# = (m1-m2)/(1+m1.m2)
where m1 and m2 are the gradient of the lines respectively. In terms of JS:
var m1 = (A1y-A2y)/(A1x-A2x)
var m2 = (B1y-B2y)/(B1x-B2x)
var angle
if(m1*m2==-1){
angle = Math.PI/2
}else{
angle = Math.atan((m1-m2)/(1+m1*m2))
}

Calculate minimum value not between set of ranges

Given an array of circles (x,y,r values), I want to place a new point, such that it has a fixed/known Y-coordinate (shown as the horizontal line), and is as close as possible to the center BUT not within any of the existing circles. In the example images, the point in red would be the result.
Circles have a known radius and Y-axis attribute, so easy to calculate the points where they intersect the horizontal line at the known Y value. Efficiency is important, I don't want to have to try a bunch of X coords and test them all against each item in the circles array. Is there a way to work out this optimal X coordinate mathematically? Any help greatly appreciated. By the way, I'm writing it in javascript using the Raphael.js library (because its the only one that supports IE8) - but this is more of a logic problem so the language doesn't really matter.
I'd approach your problem as follows:
Initialize a set of intervals S, sorted by the X coordinate of the interval, to the empty set
For each circle c, calculate the interval of intersection Ic of c with with the X axis. If c does not intersect, go on to the next circle. Otherwise, test whether Ic overlaps with any interval(s) in S (this is quick because S is sorted); if so, remove all intersecting intervals from S, collapse Ic and all removed intervals into a new interval I'c and add I'c to S. If there are no intersections, add Ic to S.
Check whether any interval in S includes the center (again, fast because S is sorted). If so, select the interval endpoint closest to the center; if not, select the center as the closest point.
Basically the equation of a circle is (x - cx)2 + (y - cy)2 = r2. Therefore you can easily find the intersection points between the circle and X axis by substituting y with 0. After that you just have a simple quadratic equation to solve: x2 - 2cxx + cx2 + cy2 - r2 = 0 . For it you have 3 possible outcomes:
No intersection - the determinant will be irrational number (NaN in JavaScript), ignore this result;
One intersection - both solutions match, use [value, value];
Two intersections - both solutions are different, use [value1, value2].
Sort the newly calculated intersection intervals, than try merge them where it is possible. However take in mind that in every program language there approximation, therefore you need to define delta value for your dot approximation and take it into consideration when merging the intervals.
When the intervals are merged you can generate your x coordinates by subtracting/adding the same delta value to the beginning/end of every interval. And lastly from all points, the one closest to zero is your answer.
Here is an example with O(n log n) complexity that is oriented rather towards readability. I've used 1*10-10 for delta :
var circles = [
{x:0, y:0, r:1},
{x:2.5, y:0, r:1},
{x:-1, y:0.5, r:1},
{x:2, y:-0.5, r:1},
{x:-2, y:0, r:1},
{x:10, y:10, r:1}
];
console.log(getClosestPoint(circles, 1e-10));
function getClosestPoint(circles, delta)
{
var intervals = [],
len = circles.length,
i, result;
for (i = 0; i < len; i++)
{
result = getXIntersection(circles[i])
if (result)
{
intervals.push(result);
}
}
intervals = intervals.sort(function(a, b){
return a.from - b.from;
});
if (intervals.length <= 0) return 0;
intervals = mergeIntervals(intervals, delta);
var points = getClosestPoints(intervals, delta);
points = points.sort(function(a, b){
return Math.abs(a) - Math.abs(b);
});
return points[0];
}
function getXIntersection(circle)
{
var d = Math.sqrt(circle.r * circle.r - circle.y * circle.y);
return isNaN(d) ? null : {from: circle.x - d, to: circle.x + d};
}
function mergeIntervals(intervals, delta)
{
var curr = intervals[0],
result = [],
len = intervals.length, i;
for (i = 1 ; i < len ; i++)
{
if (intervals[i].from <= curr.to + delta)
{
curr.to = Math.max(curr.to, intervals[i].to);
} else {
result.push(curr);
curr = intervals[i];
}
}
result.push(curr);
return result;
}
function getClosestPoints(intervals, delta)
{
var result = [],
len = intervals.length, i;
for (i = 0 ; i < len ; i++)
{
result.push( intervals[i].from - delta );
result.push( intervals[i].to + delta );
}
return result;
}
create the intersect_segments array (normalizing at x=0 y=0)
sort intersectsegments by upperlimit and remove those with upperlimit<0
initialize point1 = 0 and segment = 0
loop while point1 is inside intersectsegment[segment]
4.1. increment point1 by uppper limit of intersectsegment[segment]
4.2. increment segment
sort intersectsegments by lowerlimit and remove those with loerlimit>0
initialize point2 = 0 and segment = 0
loop while point2 is inside intersectsegments[segment]
7.1. decrement point2 by lower limit of segment
7.2. decrement segment
the point is minimum absolute value of p1 and p2

Rotating 2D Vector by unknown angle such that its direction vector is [1,0]

I am trying to rotate a vector [x,y] around the origin such that when the rotation is completed it lies on the X axis. In order to do this, I'm first computing the angle between [x,y] and [1,0], then applying a simple 2D rotation matrix to it. I'm using numericjs to work with the vectors.
math.angleBetween = function(A, B) {
var x = numeric.dot(A, B) / (numeric.norm2(A) * numeric.norm2(B));
if(Math.abs(x) <= 1) {
return Math.acos(x);
} else {
throw "Bad input to angleBetween";
}
};
math.alignToX = function(V) {
var theta = -math.angleBetween([1,0], V);
var R = [[Math.cos(theta), -Math.sin(theta)],
[Math.sin(theta), Math.cos(theta)]];
return numeric.dot(R, V);
};
(Note: math is a namespace object within my project. Math is ye olde math object.)
This code works sometimes, however there are occasions where no matter how many times I run math.alignToX the vector never even gets close to aligning with the X axis. I'm testing this by checking if the y coordinate is less than 1e-10.
I've also tried using Math.atan2 with an implicit z coordinate of 0, but the results have been the same. Errors are not being thrown. Some example results:
math.alignToX([152.44444444444434, -55.1111111111111])
// result: [124.62691466033475, -103.65652585400568]
// expected: [?, 0]
math.alignToX([372, 40])
// result: [374.14435716712336, -2.0605739337042905e-13]
// expected: [?, 0]
// this value has abs(y coordinate) < 1e-10, so its considered aligned
What am I doing wrong?
If you're rotating something other than your vector, then you'll need to use your R matrix. But if you just need to rotate your vector, the result will be [Math.sqrt(x*x+y*y),0].
Actually, the task of building a rotation matrix that aligns a known 2d vector with [1, 0] doesn't require any trigonometric functions at all.
In fact, if [x y] is your vector and s is its length (s = Sqrt(x*x + y*y)), then the transformation that maps [x y] to align with [1 0] (pure rotation, no scaling) is just:
[ x y]
T = 1/s^2 [-y x]
For example, suppose your vector is [Sqrt(3)/2, 1/2]. This is a unit vector as you can easily check so s = 1.
[Sqrt(3)/2 1/2 ]
T = [ -1/2 Sqrt(3)/2]
Multiplying T by our vector we get:
[Sqrt(3)/2 1/2 ][Sqrt(3)/2] [1]
T = [ -1/2 Sqrt(3)/2][ 1/2 ] = [0]
So in finding the rotation angle (which in this case is Pi/6) and then creating the rotation matrix, you've just come full circle back to what you started with. The rotation angle for [Sqrt(3)/2, 1/2] is Pi/2, and cos(Pi/2) is Sqrt(3)/2 = x, sin(pi/2) is 1/2 = y.
Put another way, if you know the vector, you ALREADY know the sine and cosine of it's angle with the x axis from the definition of sine and cosine:
cos a = x/s
sin a = y/s where s = || [x, y] ||, is the length of the vector.
My problem is so mind-bendingly obvious that I cannot believe I didn't see it. While I'm checking the domain of Math.acos, I'm not checking the range at all! The problem occurs when the vector lies outside of the range (which is [0,PI]). Here is what I did to fix it:
math.alignToX = function(V) {
var theta = -math.angleBetween([1,0], V);
var R = [[Math.cos(theta), -Math.sin(theta)],
[Math.sin(theta), Math.cos(theta)]];
var result = numeric.dot(R, V);
if(Math.abs(result[1]) < ZERO_THRESHOLD) {
return result;
} else {
V = numeric.dot([[-1, 0], [0, -1]], V); // rotate by PI
theta = -math.angleBetween([1,0], V);
R = [[Math.cos(theta), -Math.sin(theta)],
[Math.sin(theta), Math.cos(theta)]];
result = numeric.dot(R, V);
if(Math.abs(result[1]) < ZERO_THRESHOLD) {
return result;
} else {
throw "Unable to align " + V; // still don't trust it 100%
}
}
};
For the broken example I gave above, this produces:
[162.10041088743887, 2.842170943040401e-14]
The Y coordinate on this result is significantly less than my ZERO_THRESHOLD (1e-10). I almost feel bad that I solved it myself, but I don't think I would have done so nearly as quickly had I not posted here. I saw the problem when I was checking over my post for typos.

Categories