Making a planar dynamical system with JSXgraph - javascript

I'm working on a predator-prey model for a dynamical systems book. I start by creating a dragable point for the initial condition. If I set up a second point whose coordinates are functions of the first point, I can drag one and it moves the other. I'm trying to get a 100 point orbit of the system, and I'm having difficulty. Here's a fiddle that works for a single point - https://jsfiddle.net/jford1906/gx86vbtc/21/
var board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-0.1, 3, 1, -0.1],
axis: true,
grid: true,
showFullscreen: true
});
var p1 = board.create('point', [0.5, 0.5], {
name: 'A'
});
var coords = board.create('text',
[0.3, 2.8, function() {
return "Initial Condition: (" + JXG.toFixed(p1.X(), 2) + "," + JXG.toFixed(p1.Y(), 2) + ")";
}]
);
var p2 = board.create('point', [function() {
return 2 * p1.X() * (1 - p1.X()) - 0.5 * p1.X() * p1.Y()
}, function() {
return 4 * p1.Y() / 5 + 1.5 * p1.X() * p1.Y()
}], {
withLabel: false,
color: "blue",
opacity: 1,
size: 3
});
What I've tried so far is to plug each point of the orbit into an array, and have the next point run the same functions for it's coordinates as I did in the working example. It initially shows the whole orbit, but when I move the initial condition, all points except the last one in the orbit vanish. This fiddle shows how I've tried to do it - https://jsfiddle.net/jford1906/jra9g2d3/3/
var i; //indexing variable
var pts = [p1] //Put the initial condition in an array
for (i = 1; i < 100; i++) {
var p2 = board.create('point', [function() {
return 2 * pts[i - 1].X() * (1 - pts[i - 1].X()) - 0.5 * pts[i - 1].X() * pts[i - 1].Y()
}, function() {
return 4 * pts[i - 1].Y() / 5 + 1.5 * pts[i - 1].X() * pts[i - 1].Y()
}], {
withLabel: false,
color: "blue",
opacity: 1,
size: 1
});
pts.push(p2);
}
Ideas on why this might happen, or thoughts on a different approach? I've also tried putting the whole loop in a function and having that trigger when the point is dragged, but the same issue occurs.

This is a problem with JavaScript closures. It has been answered in https://groups.google.com/g/jsxgraph/c/Y1y1Mbd23ZQ. A very quick fix would be to define the variable i with let instead of var:
var pts = [p1] //Put the initial condition in an array
for (let i = 1; i < 100; i++) {
var p2 = board.create('point', [function() {
return 2 * pts[i - 1].X() * (1 - pts[i - 1].X()) - 0.5 * pts[i - 1].X() * pts[i - 1].Y()
}, function() {
return 4 * pts[i - 1].Y() / 5 + 1.5 * pts[i - 1].X() * pts[i - 1].Y()
}], {
withLabel: false,
color: "blue",
opacity: 1,
size: 1
});
pts.push(p2);
}

Related

Javascript: Calculating from object properties

I have the following objects:
const circle = { center: { x: 0, y: 0 }, radius: 10 };
const point = { x: 0, y: 0 };
And I need to determine whether point lies in a circle. My code is:
function isInsideCircle(circle, point) {
if (((point.x - circle.center.x) * (point.x - circle.center.x) + (point.y - circle.center.y) * (point.y - circle.center.y)) <= circle.radius * circle.radius) return true;
return false;
}
But this does not work at all. The testing environment says, that it returns wrong boolean, so I suppose it doesn't calculate at all. Am I accessing objects properties wrongly?

how to make a custom easing function that eases between given breakpoints

I need a custom easing function that takes breakpoints as parameters and eases between those breakpoints similar to this but this doesn't work:
function makeEasing(breakpoints) {
return t => {
const iPoint = Math.floor(t / breakpoints.length),
iDest = iPoint + 1;
return (breakpoints[iPoint] - breakpoints[iDest]) * (t);
};
}
Usage:
const easing = makeEasing([0.5, 0.0, 0.5, 1.0, 0.5]);
easing(0) // 0.5
// easing(0 + 1/4 * 0.5) // 0.25
easing(1/4) // 0.0
easing(2/4) // 0.5
easing(3/4) // 1.0
// easing(3/4 + 1/4 * 0.5) // 0.75
easing(4/4) // 0.5
let's say we can use this elastic easing function
const easing = t => {
return .04 * t / (--t) * Math.sin(25 * t);
};
i have two variables in the range [0.0, 1.0]
impact and targetImpact
I want impact to reach targetImpact slowly so I do:
// delta is passed time
impact += (targetImpact - impact) * delta * 0.001;
This works for interpolating between impact and targetImpact.
But I want elastic effect when impact reaches targetImpact, that is
impact goes up to targetImpact + 0.2,
then goes down to targetImpact - 0.2,
then goes up to targetImpact + 0.1,
then goes down to targetImpact - 0.1,
finally reaches targetImpact.
I think the fastest way for you to have this effect is to create 4 impacts in a row with your different targetImpact. Use the previous targetImpact as impact for next one.
Edit:
var easing = 0;
function makeEasing(breakpoints) {
breakpoints.forEach(t => {
let iPoint = Math.floor(t / breakpoints.length),
iDest = iPoint + 1;
easing = (breakpoints[iPoint] - breakpoints[iDest]) * (t);
})
}
makeEasing([0.5, 0.0, 0.5, 1.0, 0.5]);
Thanks #AppyGG for giving the idea this now works:
function makeEasing(breakpoints) {
const spaces = breakpoints.length - 1;
return t => {
const iPoint = Math.floor(t / (1 / spaces)),
iDest = iPoint + 1;
return breakpoints[iPoint] + (breakpoints[iDest] - breakpoints[iPoint]) * smoothstep(0, 1/spaces, (t % (1/spaces)));
};
}
function smoothstep (min, max, value) {
var x = Math.max(0, Math.min(1, (value-min)/(max-min)));
return x*x*(3 - 2*x);
};
function round(v, d = 100) {
return Math.round(v * d) / d;
}
function testEasing(easing) {
for (let i = 0; i< 1; i+= 0.01) {
console.log(round(i), round(easing(i)));
}
}
const easing2 = makeEasing([0.5, 0.0, 0.5, 1.0, 0.5]);
testEasing(easing2);

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).

D3.js: Getting my own data for the layout

I have experimenting around with the D3 cluster force layout.
However I am not sure as to how to bind the data and hence, I am not able to have my own data within.
This is what I have tried, without any binding, it's bound not to work.
d3.tsv("data.tsv", function(data){
nodes = d3.range(n).map(function(data) {
var i = Math.floor(Math.random() * m),
r = Math.sqrt((i + 1) / m * -Math.log(data.diskSpace)) * maxRadius,
d = {
cluster: i,
radius: data.radius,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) clusters[i] = d;
return d;
});
This does not seem to work as expected.
What exactly is happening and how do I fix it?
EDIT:
The TSV file
model diskSpace radius
DE431 8 8
DD342 16 18
...
First, make sure the file is actually separated with tabs and not spaces. Loading a tsv in d3 looks like this:
d3.tsv('data.tsv', function (rows) {
console.log(rows);
});
an array of rows will be logged to the console. Note that this call is made asynchronously, the d3.tsv()-function does not return the rows but instead calls a function with the rows as first argument.
Now we need to transform this data into something the d3 force layout understands:
d3.tsv('data.tsv', function (rows) {
var nodes = rows.map(function (item) {
var i = item['diskSpace'],
r = +item['radius'], // note the + to convert to number here.
d = {
cluster: i,
radius: r,
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
};
if (!clusters[i] || (r > clusters[i].radius)) {
clusters[i] = d;
}
return d;
});
// insert code generating the force layout here (including the cluster and collide functions)
});
This will cluster the rows by diskSpace. Please note the comments I added.
Working Demo here

Correctly calculating point on a bicubic Bézier surface

I wrote a function that should return a point on a bicubic Bézier surface from a 4x4 matrix of control points for parameters u and v, which are element of [0, 1], using Bernstein polynomials. But either my function doesn't work like it should, or my understanding of the matter is even worse than I thought.
The function to calculate the point looks like this:
var bezierSurface = function (u, v, p) {
var result = [];
var p00 = p[0], p01 = p[1], p02 = p[2], p03 = p[3],
p10 = p[4], p11 = p[5], p12 = p[6], p13 = p[7],
p20 = p[8], p21 = p[9], p22 = p[10], p23 = p[11],
p30 = p[12], p31 = p[13], p32 = p[14], p33 = p[15];
var uin = (1 - u),
vin = (1 - v);
var bu0 = Math.pow(uin, 3),
bu1 = 3 * u * Math.pow(uin, 2),
bu2 = 3 * Math.pow(u, 2) * uin,
bu3 = Math.pow(u, 3);
var bv0 = Math.pow(vin, 3),
bv1 = 3 * v * Math.pow(vin, 2),
bv2 = 3 * Math.pow(v, 2) * vin,
bv3 = Math.pow(v, 3);
for (var i = 0; i < 3; i++) {
result.push(
p00[i] * bu0 * bv0 +
p01[i] * bu0 * bv1 +
p02[i] * bu0 * bv2 +
p03[i] * bu0 * bv3 +
p10[i] * bu1 * bv0 +
p11[i] * bu1 * bv1 +
p12[i] * bu1 * bv2 +
p13[i] * bu1 * bv3 +
p20[i] * bu2 * bv0 +
p21[i] * bu2 * bv1 +
p22[i] * bu2 * bv2 +
p23[i] * bu2 * bv3 +
p30[i] * bu3 * bv0 +
p31[i] * bu3 * bv1 +
p32[i] * bu3 * bv2 +
p33[i] * bu3 * bv3
);
}
return result;
};
Most probably this is not the most efficient way to get the job done, but since I'm just getting started with parametric surfaces, I'm trying to keep things as simple as possible, yet not even thinking about tesselating the surface to get vertices for triangles or something like that.
Now, the problem appeared when I called the function with the following arguments:
var getSurfacePoint = function () {
var u = 0.5,
v = 0.25;
var cp = [
[-1.0, 0.0, -1.0],
[-0.5, 0.3, -0.8],
[ 0.5, 0.3, -0.8],
[ 1.0, 0.0, -1.0],
[-0.8, 0.3, -0.5],
[-0.3, 1.0, -0.4],
[ 0.3, 1.0, -0.4],
[ 0.8, 0.3, -0.5],
[-0.8, 0.3, 0.5],
[-0.3, 1.0, 0.4],
[ 0.3, 1.0, 0.4],
[ 0.8, 0.3, 0.5],
[-1.0, 0.0, 1.0],
[-0.5, 0.3, 0.8],
[ 0.5, 0.3, 0.8],
[ 1.0, 0.0, 1.0]
];
return bezierSurface(u, v, cp);
};
The result of calling bezierSurface via getSurfacePoint is -0.4437500000000001 for x, 0.5625 for y and -4.683753385137379e-17 for z, and that is not what I expected. I mean, at first sight, the return values for x and y seem plausible, but considering the values provided by the matrix of control points, the return value for z just looks completely wrong.
As far as I understand it, the points of a Bézier curve as well as the points of a Bézier surface are always enclosed within the convex hull of the control polygon, that is here represented by the points of the 4x4 matrix. So, when the range of z-values of the control points only goes from -1.0 to 1.0, how can the calculated point of the surface have a z-value < -4.0?
If we suppose the result is wrong, there must be something wrong with my function to calculate the point on the surface, but though alternately staring at bezierSurface and the mathematical definition of the Bézier surface for some time, I wasn't able to spot the error yet. I hope someone else can.
the return value for z just looks completely wrong
-4.683753385137379e-17, the value is (almost) 0. The result looks pretty right.

Categories

Resources