Create JavaScript Array within For Loop - javascript

I want to create the array below with a for loop as its large
var centres = {
1979: { x: width * 1 / 41, y: height / 2 },
1980: { x: width * 2 / 41, y: height / 2 },
1981: { x: width * 3 / 41, y: height / 2 },
...
}
and then access it as follows:
function nodeYearPos(d) {
return yearCenters[d.year].x;
}
I have the following code, but its only setting the year...
var yearCenters = Array.from(new Array(2020-1919+1), (x, i) => i + 1919);
for (year = 1919; year <= 2020; year++) {
coords = getCentres(year); // this returns an object in the form {x : x, y : y}
yearCenters[year] = coords;
}

you can do as gorak commented but with the getCenters function
var yearCenters = Object.fromEntries(Array.from(new Array(2020-1919+1), (x, i) => [i + 1919, getCenters(i + 1919)]));
or you can also try
var yearCenters = {};
for (year = 1919; year <= 2020; year++) {
coords = getCenters(year);
yearCenters[year] = coords;
}

When you try to fetch by year in yearCenters array (e.g. yearCenters[year]) this won't work since the year is not the index in the array.
I would suggest you first convert the array into a JS object so that indexing on it works with years.
See below snippet -
// Create obejct from array
var yearCenters = Object.fromEntries(Array.from(new Array(2020-1919+1), (x, i) => [i + 1919, null]))
// This loop remains same
for (year = 1919; year <= 2020; year++) {
coords = getCentres(year); // this returns an object in the form {x : x, y : y}
yearCenters[year] = coords;
}
// Mock function
function getCentres(year) {
return {
x: Math.random() * 100,
y: Math.random() * 100
}
}
console.log(yearCenters)

Related

Get rid of duplicates in an array of objects

This part is responsible for processing the Data in the val array so i everything is an integer and saves it in the output array. (The val array sometimes contains floats and i cant work with those)
Lets say the next element that gets processed has a x and y pair that is already in the output array with any other color.
How do i replace the old object with the new.
val.forEach(element => {
output.push({
x: Math.round(element.x / element.width),
y: Math.round(element.y / element.height),
color: mapColorToBlock(element.color)
})
});
/* val array...
[
{"x":0,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"lime","width":256,"height":256}
]
*/
/*output array after the processing(notice how there are 2 objects with x = 0 and y = 1 (the second and last entry in output))
[
{ x: 0, y: 0, color: 12 },
{ x: 0, y: 1, color: 12 },
{ x: 1, y: 1, color: 12 },
{ x: 1, y: 0, color: 12 },
{ x: 0, y: 1, color: 6 }
]
*/
It is important that the new one replaces the old object.(The new entry doesnt have to be in the same position in the array as the old one)
In this scenario the output array would look like this.
[
{ x: 0, y: 0, color: 12 },
{ x: 1, y: 1, color: 12 },
{ x: 1, y: 0, color: 12 },
{ x: 0, y: 1, color: 6 }
]
Note:
It is important that the new one replaces the old object
val.forEach(element => {
const x = Math.round(element.x / element.width);
const y = Math.round(element.y / element.height);
const isDuplicate = output.some(item => item.x === x && item.y === y);
if (!isDuplicate) {
output.push({
x: x,
y: y,
color: mapColorToBlock(element.color)
})
}
});
reduce over the array to create a new object with keys based on the x and y coordinates, and then use Object.values to create a new array of objects from that object's values.
const data=[{x:0,y:0,color:"blue",width:256,height:256},{x:0,y:256,color:"blue",width:256,height:256},{x:256,y:256,color:"blue",width:256,height:256},{x:256,y:0,color:"blue",width:256,height:256},{x:0,y:256,color:"lime",width:256,height:256}];
const out = data.reduce((acc, c) => {
// Extract the values and create a key
// from the x and y values
const { x, y, color, width, height } = c;
const key = `${x}|${y}`;
// Do your x/y calculations
const newX = Math.round(x / width);
const newY = Math.round(y / height);
// Add the current object to the property
// defined by the key *replacing the data
// if it already exists*
acc[key] = { x: newX, y: newY, color };
// Return the accumulator for the next iteration
return acc;
}, {});
// Now extract the values from the new object
console.log(Object.values(out));
A modified version of Shuvo's answer.
This one is only sensible if mapColorToBlock can return different results in successive calls for the same value passed, otherwise Shuvo's answer which skips duplicates is better.
"It is important that the new one replaces the old object"...
let val = [
{"x":0,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":256,"color":"blue","width":256,"height":256},
{"x":256,"y":0,"color":"blue","width":256,"height":256},
{"x":0,"y":256,"color":"lime","width":256,"height":256}
];
let output = [];
val.forEach(element => {
const x = Math.round(element.x / element.width);
const y = Math.round(element.y / element.height);
const found = output.find(item => item.x === x && item.y === y);
if (found) {
found.color = mapColorToBlock(element.color);
} else {
output.push({
x: x,
y: y,
color: mapColorToBlock(element.color)
})
}
});
Here you have to replace the previous element with same x and y
val.forEach(element => {
output = [...output.filter(out => !( out.x === Math.round(element.x / element.width) && out.y === Math.round(element.y / element.height)), {
x: Math.round(element.x / element.width),
y: Math.round(element.y / element.height),
color: mapColorToBlock(element.color)
}]
});

Calculate which of coords in object is closest to choosen coords

I have some objects in array with coordinates.
var players = [
{x:100, y:100, pid:1},
{x:-100, y:200, pid:2},
{x:600, y:1200, pid:3}
]
var mousepos = {x:50, y:0}
var selected = null
And I want to calculate which of these objects have closest coordinates to mousepos to select it pid
selected = pid of closest coords to mousepos
if anyone could give me easy done code?
Mate here is the answer. Use the distance formula to calculate the distance between points. One with the smallest distance is the closest point.
Fomula :- Sqrt((x2-x1)2 + (y2-y1)2)
It seems you are trying to calculate Euclidean Distance. If so then you can use Math build in object. Once the distance is calculated then you can return the index. Using this index retrieve the he object from the players
var players = [{
x: 100,
y: 100,
pid: 1
},
{
x: -100,
y: 200,
pid: 2
},
{
x: 600,
y: 1200,
pid: 3
}
]
var mousepos = {
x: 50,
y: 0
}
function calculateEuclidean() {
let selectedPid = 0;
players.forEach(function(item, index) {
let distance = Math.sqrt(Math.pow((item.x - mousepos.x), 2) + Math.pow((item.y - mousepos.y), 2))
if (index === 0) {
selectedPid = index
} else if (distance < selectedPid) {
selectedPid = index;
}
})
return players[selectedPid];
}
console.log(calculateEuclidean())
Working example given by random from discord
let closestPlayer = undefined;
let closestDist = undefined;
for (let i = 0; i < players.length; i++) {
let player = players[i];
let distance = Math.hypot(player.x - mousepos.x, player.y - mousepos.y);
if (closestPlayer == undefined || distance <= closestDist) {
closestPlayer = player;
closestDist = distance;
}
}
console.log(closestPlayer)

How to calculate the rotation angle of two constrained segments?

I have two vectors, the Y-aligned is fixed whereby the X-aligned is allowed to rotate. These vectors are connected together through two fixed-length segments. Given the angle between the two vectors (82.74) and the length of all segments, how can I get the angle of the two jointed segments (24.62 and 22.61)?
What is given: the magnitude of the vectors, and the angle between the X-axis and OG:
var magOG = 3,
magOE = 4,
magGH = 3,
magEH = 2,
angleGamma = 90;
This is my starting point: angleGamma = 90 - then, I will have following vectors:
var vOG = new vec2(-3,0),
vOE = new vec2(0,-4);
From here on, I am trying to get angleAlphaand angleBeta for values of angleGamma less than 90 degrees.
MAGNITUDE OF THE CONSTRAINED SEGMENTS:
Segments HG and HE must meet following conditions:
/
| OG*OG+ OE*OE = (HG + HE)*(HG + HE)
>
| OG - HG = OE - HE
\
which will lead to following two solutions (as pointed out in the accepted answer - bilateration):
Solution 1:
========================================================
HG = 0.5*(-Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(-Math.sqrt(OG*OG + OE*OE) - OG + OE)
Solution 2:
========================================================
HG = 0.5*(Math.sqrt(OG*OG + OE*OE) + OG - OE)
HE = 0.5*(Math.sqrt(OG*OG + OE*OE) - OG + OE)
SCRATCHPAD:
Here is a playground with the complete solution. The visualization library used here is the great JSXGraph. Thanks to the Center for Mobile Learning with Digital Technology of the Bayreuth University.
Credits for the circle intersection function: 01AutoMonkey in the accepted answer to this question: A JavaScript function that returns the x,y points of intersection between two circles?
function deg2rad(deg) {
return deg * Math.PI / 180;
}
function rad2deg(rad) {
return rad * 180 / Math.PI;
}
function lessThanEpsilon(x) {
return (Math.abs(x) < 0.00000000001);
}
function angleBetween(point1, point2) {
var x1 = point1.X(), y1 = point1.Y(), x2 = point2.X(), y2 = point2.Y();
var dy = y2 - y1, dx = x2 - x1;
var t = -Math.atan2(dx, dy); /* range (PI, -PI] */
return rad2deg(t); /* range (180, -180] */
}
function circleIntersection(circle1, circle2) {
var r1 = circle1.radius, cx1 = circle1.center.X(), cy1 = circle1.center.Y();
var r2 = circle2.radius, cx2 = circle2.center.X(), cy2 = circle2.center.Y();
var a, dx, dy, d, h, h2, rx, ry, x2, y2;
/* dx and dy are the vertical and horizontal distances between the circle centers. */
dx = cx2 - cx1;
dy = cy2 - cy1;
/* angle between circle centers */
var theta = Math.atan2(dy,dx);
/* vertical and horizontal components of the line connecting the circle centers */
var xs1 = r1*Math.cos(theta), ys1 = r1*Math.sin(theta), xs2 = r2*Math.cos(theta), ys2 = r2*Math.sin(theta);
/* intersection points of the line connecting the circle centers */
var sxA = cx1 + xs1, syA = cy1 + ys1, sxL = cx2 - xs2, syL = cy2 - ys2;
/* Determine the straight-line distance between the centers. */
d = Math.sqrt((dy*dy) + (dx*dx));
/* Check for solvability. */
if (d > (r1 + r2)) {
/* no solution. circles do not intersect. */
return [[sxA,syA], [sxL,syL]];
}
thetaA = -Math.PI - Math.atan2(cx1,cy1); /* Swap X-Y and re-orient to -Y */
xA = +r1*Math.sin(thetaA);
yA = -r1*Math.cos(thetaA);
ixA = cx1 - xA;
iyA = cy1 - yA;
thetaL = Math.atan(cx2/cy2);
xL = -r2*Math.sin(thetaL);
yL = -r2*Math.cos(thetaL);
ixL = cx2 - xL;
iyL = cy2 - yL;
if(d === 0 && r1 === r2) {
/* infinite solutions. circles are overlapping */
return [[ixA,iyA], [ixL,iyL]];
}
if (d < Math.abs(r1 - r2)) {
/* no solution. one circle is contained in the other */
return [[ixA,iyA], [ixL,iyL]];
}
/* 'point 2' is the point where the line through the circle intersection points crosses the line between the circle centers. */
/* Determine the distance from point 0 to point 2. */
a = ((r1*r1) - (r2*r2) + (d*d)) / (2.0 * d);
/* Determine the coordinates of point 2. */
x2 = cx1 + (dx * a/d);
y2 = cy1 + (dy * a/d);
/* Determine the distance from point 2 to either of the intersection points. */
h2 = r1*r1 - a*a;
h = lessThanEpsilon(h2) ? 0 : Math.sqrt(h2);
/* Now determine the offsets of the intersection points from point 2. */
rx = -dy * (h/d);
ry = +dx * (h/d);
/* Determine the absolute intersection points. */
var xi = x2 + rx, yi = y2 + ry;
var xi_prime = x2 - rx, yi_prime = y2 - ry;
return [[xi, yi], [xi_prime, yi_prime]];
}
function plot() {
var cases = [
{a: 1.1, l: 1.9, f: 0.3073},
{a: 1.0, l: 1.7, f: 0.3229}
];
var testCase = 1;
var magA = cases[testCase].a, magL = cases[testCase].l;
var maxS = Math.sqrt(magA*magA+magL*magL), magS1 = maxS * cases[testCase].f, magS2 = maxS - magS1;
var origin = [0,0], board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5.0, 5.0, 5.0, -5.0], axis: true});
var drawAs = {dashed: {dash: 3, strokeWidth: 0.5, strokeColor: '#888888'} };
board.suspendUpdate();
var leftArm = board.create('slider', [[-4.5, 3], [-1.5, 3], [0, -64, -180]]);
var leftLeg = board.create('slider', [[-4.5, 2], [-1.5, 2], [0, -12, -30]]);
var rightArm = board.create('slider', [[0.5, 3], [3.5, 3], [0, 64, 180]]);
var rightLeg = board.create('slider', [[0.5, 2], [3.5, 2], [0, 12, 30]]);
var lh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(leftArm.Value())); },
function() { return -magA * Math.cos(deg2rad(leftArm.Value())); }
], {size: 3, name: 'lh'});
var LA = board.create('line', [origin, lh], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS1 = board.create('circle', [function() { return [lh.X(), lh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var lf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(leftLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(leftLeg.Value())); }
], {size: 3, name: 'lf'});
var LL = board.create('line', [origin, lf], {straightFirst: false, straightLast: false, lastArrow: true});
var cLS2 = board.create('circle', [function() { return [lf.X(), lf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var lx1 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[0][0]; },
function() { return circleIntersection(cLS1, cLS2)[0][1]; }
], {size: 3, face:'x', name: 'lx1'});
var lx2 = board.create('point', [
function() { return circleIntersection(cLS1, cLS2)[1][0]; },
function() { return circleIntersection(cLS1, cLS2)[1][1]; }
], {size: 3, face:'x', name: 'lx2'});
/* Angle between lh, lx1 shall be between 0 and -180 */
var angleLAJ = board.create('text', [-3.7, 0.5, function(){ return angleBetween(lh, lx1).toFixed(2); }]);
/* Angle between lf, lx1 shall be between 0 and 180 */
var angleLLJ = board.create('text', [-2.7, 0.5, function(){ return angleBetween(lf, lx1).toFixed(2); }]);
var rh = board.create('point', [
function() { return +magA * Math.sin(deg2rad(rightArm.Value())); },
function() { return -magA * Math.cos(deg2rad(rightArm.Value())); }
], {size: 3, name: 'rh'});
var RA = board.create('line', [origin, rh], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS1 = board.create('circle', [function() { return [rh.X(), rh.Y()]; }, function() { return magS1; }], drawAs.dashed);
var rf = board.create('point', [
function() { return +magL * Math.sin(deg2rad(rightLeg.Value())); },
function() { return -magL * Math.cos(deg2rad(rightLeg.Value())); }
], {size: 3, name: 'rf'});
var RL = board.create('line', [origin, rf], {straightFirst: false, straightLast: false, lastArrow: true});
var cRS2 = board.create('circle', [function() { return [rf.X(), rf.Y()]; }, function() { return magS2; }], drawAs.dashed);
var rx1 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[1][0]; },
function() { return circleIntersection(cRS1, cRS2)[1][1]; }
], {size: 3, face:'x', name: 'rx1'});
var rx2 = board.create('point', [
function() { return circleIntersection(cRS1, cRS2)[0][0]; },
function() { return circleIntersection(cRS1, cRS2)[0][1]; }
], {size: 3, face:'x', name: 'rx2'});
var angleRAJ = board.create('text', [+1.3, 0.5, function(){ return angleBetween(rh, rx1).toFixed(2); }]);
var angleRLJ = board.create('text', [+2.3, 0.5, function(){ return angleBetween(rf, rx1).toFixed(2); }]);
board.unsuspendUpdate();
}
plot();
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraph.css" />
<link rel="stylesheet" href="style.css">
<script type="text/javascript" charset="UTF-8" src="//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.99.7/jsxgraphcore.js"></script>
</head>
<body>
<div id="jxgbox" class="jxgbox" style="width:580px; height:580px;"></div>
</body>
</html>
According to your sketch, the coordinates of E and G are:
E = (0, -magOE)
G = magOG * ( -sin(gamma), -cos(gamma) )
Then, calculating the position of H is a trilateration problem. Actually, it is just bilateration because you are missing a third distance. Hence, you will get two possible positions for H.
First, let us define a new coordinate system, where E lies at the origin and G lies on the x-axis. The x-axis direction in our original coordinate system is then:
x = (G - E) / ||G - E||
The y-axis is:
y = ( x.y, -x.x )
The coordinates of E and G in this new coordinate system are:
E* = (0, 0)
G* = (0, ||G - E||)
Now, we can easily find the coordinates of H in this coordinate system, up to the ambiguity mentioned earlier. I will abbreviate ||G - E|| = d like in the notation used in the Wikipedia article:
H.x* = (magGH * magGH - magEH * magEH + d * d) / (2 * d)
H.y* = +- sqrt(magGH * magGH - H.x* * H.x*)
Hence, we have two solutions for H.y, one positive and one negative.
Finally, we just need to transform H back into our original coordinate system:
H = x * H.x* + y * H.y* - (0, magOE)
Given the coordinates of H, calculating the angles is pretty straightforward:
alpha = arccos((H.x - G.x) / ||H - G||)
beta = arccos((H.y - E.y) / ||H - E||)
Example
Taking the values from your example
magOG = 3
magOE = 4
magGH = 3
magEH = 2
angleGamma = 82.74°
we first get:
E = (0, -4)
G = 3 * ( -sin(82.74°), -cos(82.74°) )
= (-2.976, -0.379)
Our coordinate system:
x = (-0.635, 0.773)
y = ( 0.773, 0.635)
In this coordinate system:
E* = (0, 0)
G* = (0, 4.687)
Then, the coordinates of H in our auxiliary coordinate system are:
H* = (2.877, +- 0.851)
I will only focus on the positive value for H*.y because this is the point that you marked in your sketch.
Transform back to original coordinate system:
H = (-1.169, -1.237)
And finally calculate the angles:
alpha = 25.41°
beta = 22.94°
The slight differences to your values are probably caused by rounding errors (either in my calculations or in yours).

How to calculate weighted center point of 4 points?

If I have 4 points
var x1;
var y1;
var x2;
var y2;
var x3;
var y3;
var x4;
var y4;
that make up a box. So
(x1,y1) is top left
(x2,y2) is top right
(x3,y3) is bottom left
(x4,y4) is bottom right
And then each point has a weight ranging from 0-522. How can I calculate a coordinate (tx,ty) that lies inside the box, where the point is closer to the the place that has the least weight (but taking all weights into account). So for example. if (x3,y3) has weight 0, and the others have weight 522, the (tx,ty) should be (x3,y3). If then (x2,y2) had weight like 400, then (tx,ty) should be move a little closer towards (x2,y2) from (x3,y3).
Does anyone know if there is a formula for this?
Thanks
Creating a minimum, complete, verifiable exmample
You have a little bit of a tricky problem here, but it's really quite fun. There might be better ways to solve it, but I found it most reliable to use Point and Vector data abstractions to model the problem better
I'll start with a really simple data set – the data below can be read (eg) Point D is at cartesian coordinates (1,1) with a weight of 100.
|
|
| B(0,1) #10 D(1,1) #100
|
|
| ? solve weighted average
|
|
| A(0,0) #20 C(1,0) #40
+----------------------------------
Here's how we'll do it
find the unweighted midpoint, m
convert each Point to a Vector of Vector(degrees, magnitude) using m as the origin
add all the Vectors together, vectorSum
divide vectorSum's magnitude by the total magnitude
convert the vector to a point, p
offset p by unweighted midpoint m
Possible JavaScript implementation
I'll go thru the pieces one at a time then there will be a complete runnable example at the bottom.
The Math.atan2, Math.cos, and Math.sin functions we'll be using return answers in radians. That's kind of a bother, so there's a couple helpers in place to work in degrees.
// math
const pythag = (a,b) => Math.sqrt(a * a + b * b)
const rad2deg = rad => rad * 180 / Math.PI
const deg2rad = deg => deg * Math.PI / 180
const atan2 = (y,x) => rad2deg(Math.atan2(y,x))
const cos = x => Math.cos(deg2rad(x))
const sin = x => Math.sin(deg2rad(x))
Now we'll need a way to represent our Point and Point-related functions
// Point
const Point = (x,y) => ({
x,
y,
add: ({x: x2, y: y2}) =>
Point(x + x2, y + y2),
sub: ({x: x2, y: y2}) =>
Point(x - x2, y - y2),
bind: f =>
f(x,y),
inspect: () =>
`Point(${x}, ${y})`
})
Point.origin = Point(0,0)
Point.fromVector = ({a,m}) => Point(m * cos(a), m * sin(a))
And of course the same goes for Vector – strangely enough adding Vectors together is actually easier when you convert them back to their x and y cartesian coordinates. other than that, this code is pretty straightforward
// Vector
const Vector = (a,m) => ({
a,
m,
scale: x =>
Vector(a, m*x),
add: v =>
Vector.fromPoint(Point.fromVector(Vector(a,m)).add(Point.fromVector(v))),
inspect: () =>
`Vector(${a}, ${m})`
})
Vector.zero = Vector(0,0)
Vector.fromPoint = ({x,y}) => Vector(atan2(y,x), pythag(x,y))
Lastly we'll need to represent our data above in JavaScript and create a function which calculates the weighted point. With Point and Vector by our side, this will be a piece of cake
// data
const data = [
[Point(0,0), 20],
[Point(0,1), 10],
[Point(1,1), 100],
[Point(1,0), 40],
]
// calc weighted point
const calcWeightedMidpoint = points => {
let midpoint = calcMidpoint(points)
let totalWeight = points.reduce((acc, [_, weight]) => acc + weight, 0)
let vectorSum = points.reduce((acc, [point, weight]) =>
acc.add(Vector.fromPoint(point.sub(midpoint)).scale(weight/totalWeight)), Vector.zero)
return Point.fromVector(vectorSum).add(midpoint)
}
console.log(calcWeightedMidpoint(data))
// Point(0.9575396819442366, 0.7079725827019256)
Runnable script
// math
const pythag = (a,b) => Math.sqrt(a * a + b * b)
const rad2deg = rad => rad * 180 / Math.PI
const deg2rad = deg => deg * Math.PI / 180
const atan2 = (y,x) => rad2deg(Math.atan2(y,x))
const cos = x => Math.cos(deg2rad(x))
const sin = x => Math.sin(deg2rad(x))
// Point
const Point = (x,y) => ({
x,
y,
add: ({x: x2, y: y2}) =>
Point(x + x2, y + y2),
sub: ({x: x2, y: y2}) =>
Point(x - x2, y - y2),
bind: f =>
f(x,y),
inspect: () =>
`Point(${x}, ${y})`
})
Point.origin = Point(0,0)
Point.fromVector = ({a,m}) => Point(m * cos(a), m * sin(a))
// Vector
const Vector = (a,m) => ({
a,
m,
scale: x =>
Vector(a, m*x),
add: v =>
Vector.fromPoint(Point.fromVector(Vector(a,m)).add(Point.fromVector(v))),
inspect: () =>
`Vector(${a}, ${m})`
})
Vector.zero = Vector(0,0)
Vector.unitFromPoint = ({x,y}) => Vector(atan2(y,x), 1)
Vector.fromPoint = ({x,y}) => Vector(atan2(y,x), pythag(x,y))
// data
const data = [
[Point(0,0), 20],
[Point(0,1), 10],
[Point(1,1), 100],
[Point(1,0), 40],
]
// calc unweighted midpoint
const calcMidpoint = points => {
let count = points.length;
let midpoint = points.reduce((acc, [point, _]) => acc.add(point), Point.origin)
return midpoint.bind((x,y) => Point(x/count, y/count))
}
// calc weighted point
const calcWeightedMidpoint = points => {
let midpoint = calcMidpoint(points)
let totalWeight = points.reduce((acc, [_, weight]) => acc + weight, 0)
let vectorSum = points.reduce((acc, [point, weight]) =>
acc.add(Vector.fromPoint(point.sub(midpoint)).scale(weight/totalWeight)), Vector.zero)
return Point.fromVector(vectorSum).add(midpoint)
}
console.log(calcWeightedMidpoint(data))
// Point(0.9575396819442366, 0.7079725827019256)
Going back to our original visualization, everything looks right!
|
|
| B(0,1) #10 D(1,1) #100
|
|
| * <-- about right here
|
|
|
| A(0,0) #20 C(1,0) #40
+----------------------------------
Checking our work
Using a set of points with equal weighting, we know what the weighted midpoint should be. Let's verify that our two primary functions calcMidpoint and calcWeightedMidpoint are working correctly
const data = [
[Point(0,0), 5],
[Point(0,1), 5],
[Point(1,1), 5],
[Point(1,0), 5],
]
calcMidpoint(data)
// => Point(0.5, 0.5)
calcWeightedMidpoint(data)
// => Point(0.5, 0.5)
Great! Now we'll test to see how some other weights work too. First let's just try all the points but one with a zero weight
const data = [
[Point(0,0), 0],
[Point(0,1), 0],
[Point(1,1), 0],
[Point(1,0), 1],
]
calcWeightedMidpoint(data)
// => Point(1, 0)
Notice if we change that weight to some ridiculous number, it won't matter. Scaling of the vector is based on the point's percentage of weight. If it gets 100% of the weight, it (the point) will not pull the weighted midpoint past (the point) itself
const data = [
[Point(0,0), 0],
[Point(0,1), 0],
[Point(1,1), 0],
[Point(1,0), 1000],
]
calcWeightedMidpoint(data)
// => Point(1, 0)
Lastly, we'll verify one more set to ensure weighting is working correctly – this time we'll have two pairs of points that are equally weighted. The output is exactly what we're expecting
const data = [
[Point(0,0), 0],
[Point(0,1), 0],
[Point(1,1), 500],
[Point(1,0), 500],
]
calcWeightedMidpoint(data)
// => Point(1, 0.5)
Millions of points
Here we will create a huge point cloud of random coordinates with random weights. If points are random and things are working correctly with our function, the answer should be pretty close to Point(0,0)
const RandomWeightedPoint = () => [
Point(Math.random() * 1000 - 500, Math.random() * 1000 - 500),
Math.random() * 1000
]
let data = []
for (let i = 0; i < 1e6; i++)
data[i] = RandomWeightedPoint()
calcWeightedMidpoint(data)
// => Point(0.008690554978970092, -0.08307212085822799)
A++
Assume w1, w2, w3, w4 are the weights.
You can start with this (pseudocode):
M = 522
a = 1
b = 1 / ( (1 - w1/M)^a + (1 - w2/M)^a + (1 - w3/M)^a + (1 - w4/M)^a )
tx = b * (x1*(1-w1/M)^a + x2*(1-w2/M)^a + x3*(1-w3/M)^a + x4*(1-w4/M)^a)
ty = b * (y1*(1-w1/M)^a + y2*(1-w2/M)^a + y3*(1-w3/M)^a + y4*(1-w4/M)^a)
This should approximate the behavior you want to accomplish. For the simplest case set a=1 and your formula will be simpler. You can adjust behavior by changing a.
Make sure you use Math.pow instead of ^ if you use Javascript.
A very simple approach is this:
Convert each point's weight to 522 minus the actual weight.
Multiply each x/y co-ordinate by its adjusted weight.
Sum all multiplied x/y co-ordinates together, and --
Divide by the total adjusted weight of all points to get your adjusted average position.
That should produce a point with a position that is biased proportionally towards the "lightest" points, as described. Assuming that weights are prefixed w, a quick snippet (followed by JSFiddle example) is:
var tx = ((522-w1)*x1 + (522-w2)*x2 + (522-w3)*x3 + (522-w4)*x4) / (2088-(w1+w2+w3+w4));
var ty = ((522-w1)*y1 + (522-w2)*y2 + (522-w3)*y3 + (522-w4)*y4) / (2088-(w1+w2+w3+w4));
JSFiddle example of this
Even though this has already been answered, I feel the one, short code snippet that shows the simplicity of calculating a weighted-average is missing:
function weightedAverage(v1, w1, v2, w2) {
if (w1 === 0) return v2;
if (w2 === 0) return v1;
return ((v1 * w1) + (v2 * w2)) / (w1 + w2);
}
Now, to make this specific to your problem, you have to apply this to your points via a reducer. The reducer makes it a moving average: the value it returns represents the weights of the points it merged.
// point: { x: xCoordinate, y: yCoordinate, w: weight }
function avgPoint(p1, p2) {
return {
x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
w: p1.w + pw.2,
}
}
Now, you can reduce any list of points to get an average coordinate and the weight it represents:
[ /* points */ ].reduce(avgPoint, { x: 0, y: 0, w: 0 })
I hope user naomik doesn't mind, but I used some of their test cases in this runnable example:
function weightedAverage(v1, w1, v2, w2) {
if (w1 === 0) return v2;
if (w2 === 0) return v1;
return ((v1 * w1) + (v2 * w2)) / (w1 + w2);
}
function avgPoint(p1, p2) {
return {
x: weightedAverage(p1.x, p1.w, p2.x, p2.w),
y: weightedAverage(p1.y, p1.w, p2.y, p2.w),
w: p1.w + p2.w,
}
}
function getAvgPoint(arr) {
return arr.reduce(avgPoint, {
x: 0,
y: 0,
w: 0
});
}
const testCases = [
{
data: [
{ x: 0, y: 0, w: 1 },
{ x: 0, y: 1, w: 1 },
{ x: 1, y: 1, w: 1 },
{ x: 1, y: 0, w: 1 },
],
result: { x: 0.5, y: 0.5 }
},
{
data: [
{ x: 0, y: 0, w: 0 },
{ x: 0, y: 1, w: 0 },
{ x: 1, y: 1, w: 500 },
{ x: 1, y: 0, w: 500 },
],
result: { x: 1, y: 0.5 }
}
];
testCases.forEach(c => {
var expected = c.result;
var outcome = getAvgPoint(c.data);
console.log("Expected:", expected.x, ",", expected.y);
console.log("Returned:", outcome.x, ",", outcome.y);
console.log("----");
});
const rndTest = (function() {
const randomWeightedPoint = function() {
return {
x: Math.random() * 1000 - 500,
y: Math.random() * 1000 - 500,
w: Math.random() * 1000
};
};
let data = []
for (let i = 0; i < 1e6; i++)
data[i] = randomWeightedPoint()
return getAvgPoint(data);
}());
console.log("Expected: ~0 , ~0, 500000000")
console.log("Returned:", rndTest.x, ",", rndTest.y, ",", rndTest.w);
.as-console-wrapper {
min-height: 100%;
}

Converting CSS Matrix3d Transform into Euler Angles

I'm unsure if this is possible, given that the order rotations are applied can affect the form of the rotational matrix; but I'd like get the Euler angles from a CSS matrix3d Transform. I'm finding a dearth of documentation on the format of the matrix3d and how transformations are applied. Here's my code so far:
getRotation: function (el) {
var matrix = Esprit.getTransform(el);
// 2d matrix
if (matrix.length === 6) {
return {
x: 0,
y: 0,
z: Math.round(Math.atan2(matrix[1], matrix[0]) * (180 / Math.PI))
};
}
// 3d matrix
else {
// incorrect calculations
// only work for a single rotation
// return {
// x: Math.round(Math.atan2(matrix[6], matrix[5]) * (180/Math.PI)),
// y: Math.round(Math.atan2(-matrix[2], matrix[0]) * (180/Math.PI)),
// z: Math.round(Math.atan2(matrix[1], matrix[0]) * (180/Math.PI))
// };
// convert from string to number
// for (var i = 0, len = matrix.length; i < len; i++) {
// matrix[i] = Number(matrix[i]);
// }
// gimball lock for positive 90 degrees
if (matrix[2] === 1) {
return {
x: Esprit.toDegrees(Math.atan2(matrix[0], matrix[1])),
y: Esprit.toDegrees(Math.PI / 2),
z: 0
}
}
// gimball lock for negative 90 degrees
else if (matrix[2] === -1) {
return {
x: Esprit.toDegrees(-Math.atan2(matrix[0], matrix[1])),
y: Esprit.toDegrees(-Math.PI / 2),
z: 0
}
}
// no gimball lock
else {
return {
x: Esprit.toDegrees(Math.atan2(matrix[6], matrix[10])),
y: Esprit.toDegrees(Math.asin(matrix[2])),
z: Esprit.toDegrees(Math.atan2(-matrix[1], matrix[0]))
}
}
}
},
getTransform: function (el) {
var transform = getComputedStyle(el).webkitTransform;
return transform !== 'none' ? transform.split('(')[1].split(')')[0].split(',') : Esprit.create3dMatrix();
},
toDegrees: function (radians) {
return Math.round(radians * 180 / Math.PI);
}
Any help or ideas would be greatly appreciated. Thanks!
John Schulz (#jfsiii) posted this response:
https://gist.github.com/4119165

Categories

Resources