UPDATE from 22.5.2019
I did a simpler example of the "not working" code and also imitated the "working code" by defining K1 and KK locally when drawing the points, but doing this inside a method to have them defined only once and have the same definition for all points. Since I want the points to be drawn on a parabola, I now create points that have a fixed radius from the axis of revolution and a sign, so that I can create two points 180 degrees apart by just switching the sign from +1 to -1 when drawing the parametrized points in the xz plane. Still, nothing gets drawn. Here is a link to the thing I want to see (but the code is ugly).
Below the newest try (with less points being drawn, just to see if it works at all).
const board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-10, 10, 10, -10],
axis: true,
showCopyright: true,
showNavigation: true,
pan: false,
grid: false,
zoom: {
factorX: 1.25,
factorY: 1.25,
wheel: false
}
});
//create z axis
var zAxis = board.create('axis', [
[0, 0],
[-1, -1]
], {
ticks: {
majorHeight: 10,
drawLabels: false
}
});
//create direction of view for projections
var cam = [4, 4, 30]; // [x,y,z]
var r = 6.0;
var origin = [0, 0, 0];
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//create slider for rotating the parabola
var sRadius = board.create('slider', [
[1, -8.5],
[6, -8.5],
[-10, 0, 10]
], {
name: 'angle',
needsRegularUpdate: true
//snapWidth: 1
});
//create slider for adjusting the angular speed
var sOmega = board.create('slider', [
[1, -7.5],
[6, -7.5],
[0, 2, 10]
], {
name: 'Omega',
needsRegularUpdate: true
//snapWidth: 1,
});
//fix parameters
const g = 9.81 //gravitational acceleration
const h0 = 5 //initial height of the water surface
//define radius from the y-axis for I3 and I4
const R34 = Math.sqrt(2);
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//function creates points for drawing conic sections
function PPoint2(radius,sign,namep,fixval) {
this.R=radius;
this.S=sign;
this.Namep=namep;
this.Fixval=fixval
}
//method for drawing each Point
PPoint2.prototype.draw = function(pp) {
board.create('point', [function() {
var K1 = sOmega.Value()*sOmega.Value()/g,
KK = 1/4*sOmega.Value()*sOmega.Value()/g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.sign*pp.R*Math.sin(v),K1/2*pp.R*pp.R-KK+h0,pp.sign*pp.R*Math.cos(v)];
//debugger
return project(c, cam);
}
], {
fixed: this.Fixval,
name: this.Namep,
visible: true
})
}
//create and draw points
var p3 = new PPoint2(0,-1,'p_3','false');
var I_1 = new PPoint2(r,1,'I_1','false');
//debugger
p3.draw(p3)
I_1.draw(I_1)
Original question below:
I am doing an illustration of the "bucket argument" (how water takes the shape of a paraboloid in a spinning bucket) using JSXGraph. I would like to
A) Have the shape of the parabola be dependent on the angular velocity "Omega" of the bucket.
B) Have the parabola be projected from 3D into a 2D image and the user being able to turn the parabola using a slider.
For A) my code uses the slider "Omega" and for B) the slider "angle".
The slider values are read into global variables K1 (coeffiecient of the second order term of the parabola) and KK (constant term of the parabola). Then five points (p3 and I_1-I_4) are drawn and the parabola should be drawn through these points. The points are drawn with the initial slider values, but updating (i.e. sliding) the sliders doesn't make the points move. Also, the parabola is not drawn at all.
How to make the points adjust their positions according to the current slider values?
The functionality I want is implemented in this fiddle https://jsfiddle.net/ync3pkx5/1/ (but the code is ugly and KK and K1 are defined locally for each point, but I want them to be global).
HTML
<div id="jxgbox" class="jxgbox" style="width:500px; height:500px">
</div>
JS
//create drawing board
const board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-10, 10, 10, -10],
axis: true,
showCopyright: true,
showNavigation: true,
pan: false,
grid: false,
zoom: {
factorX: 1.25,
factorY: 1.25,
wheel: false
}
});
//create z axis
var zAxis = board.create('axis', [
[0, 0],
[-1, -1]
], {
ticks: {
majorHeight: 10,
drawLabels: false
}
});
//create direction of view for projections
var cam = [4, 4, 30]; // [x,y,z]
var r = 6.0;
var origin = [0, 0, 0];
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//create slider for rotating the parabola
var sRadius = board.create('slider', [
[1, -8.5],
[6, -8.5],
[-10, 0, 10]
], {
name: 'angle',
//snapWidth: 1
});
//create slider for adjusting the angular speed (inactive)
var sOmega = board.create('slider', [
[1, -7.5],
[6, -7.5],
[0, 0, 10]
], {
name: 'Omega',
//snapWidth: 1,
});
//fix parameters
var g = 9.81 //gravitational acceleration
var h0 = 5 //initial height of the water surface
//peak coordinates of the fixed parabola
var KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g; //constant term in the equation of the parabola
var peak = [0, -KK+h0];
//point for mirroring
var pmirr = board.create('point', [0, h0/2], {
visible: false
});
//define radius from the y-axis for I3 and I4
var R34 = Math.sqrt(2);
//function for projecting poomntson the parabola
var PProject = function(xx,yy,zz) {
var K1 = sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g;
return project([xx * Math.sin(v), K1/2 * yy * yy-KK+h0, zz * Math.cos(v)], cam);
}
//p1-p3 are used for drawing the elliptical curves circ1 and prbl2
var p1 = board.create('point', [r, 0], {
fixed: true,
name: 'p_1',
visible: false
});
var p2 = board.create('point', [-r, 0], {
fixed: true,
name: 'p_2',
visible: false
});
var p3 = board.create('point', [
function() {
var KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g,
c =[0,-KK+h0,0];
//alert(KK);
//alert(h0);
return project(c, cam);
}
], {
visible: true,
name: 'p3'
});
//divisor when drawing points A-C for ellipses and points A2-C2
var div = Math.sqrt(2)
//point variables for drawing circles
var A = board.create('point', [
function() {
var c = [r / div, 0, r / div];
return project(c, cam);
}
], {
name: 'A',
visible: false
});
var B = board.create('point', [
function() {
var c = [-r / div, 0, r / div];
return project(c, cam);
}
], {
name: 'B',
visible: false
});
var C = board.create('point', [
function() {
var c = [r / div, 0, -r / div];
return project(c, cam);
}
], {
name: 'C',
visible: false
});
//I-I4 are points for drawing the rotating parabola
var I = board.create('point', [
function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g;
return project([r * Math.sin(v), K1/2 * r * r-KK+h0, r * Math.cos(v)], cam);
}
], {
visible: true,
name: 'I'
});
var I2 = board.create('point', [
function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g;
return project([-r * Math.sin(v), K1/2 * r * r-KK+h0, -r * Math.cos(v)], cam);
}
], {
visible: true,
name: 'I_2'
});
var I3 = board.create('point', [
function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g;
return project([R34 * Math.sin(v), K1/2 * R34 * R34-KK+h0, R34 * Math.cos(v)], cam);
}
], {
visible: true,
name: 'I_3'
});
var I4 = board.create('point', [
function() {
var K1 = sOmega.Value() * sOmega.Value() / g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g;
return project([-R34 * Math.sin(v), K1/2 * R34 * R34-KK+h0, -R34 * Math.cos(v)], cam);
}
], {
visible: true,
name: 'I_4'
});
//draw circle on surface y=0
var circ1 = board.create('conic', [A, B, C, p2, p1]);
//draw a mirror circle of circ1 w.r.t. to pmirr and a small circle that delimits the parabolas
var circ2 = board.create('mirrorelement', [circ1, pmirr]);
//draw the rotating parabola
var prbl2 = board.create('conic', [I, I2, I3, I4, p3], {
strokeColor: '#CA7291',
strokeWidth: 2,
//trace :true
});
debugger;
//add textbox
var txt1 = board.create('text', [3, 7, 'The blue lines delimit the volume of water when Omega = 0 and the red parabola delimits the volume without water as the bucket is rotating (surface h(r)). The water volume is constant, independent of Omega']);
Here is the fiddle I am working on and would want to get to work
https://jsfiddle.net/c8tr4dh3/2/
HTML
<div id="jxgbox" class="jxgbox" style="width:500px; height:500px">
</div>
JS
const board = JXG.JSXGraph.initBoard('jxgbox', {
boundingbox: [-10, 10, 10, -10],
axis: true,
showCopyright: true,
showNavigation: true,
pan: false,
grid: false,
zoom: {
factorX: 1.25,
factorY: 1.25,
wheel: false
}
});
//create z axis
var zAxis = board.create('axis', [
[0, 0],
[-1, -1]
], {
ticks: {
majorHeight: 10,
drawLabels: false
}
});
//create direction of view for projections
var cam = [4, 4, 30]; // [x,y,z]
var r = 6.0;
var origin = [0, 0, 0];
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//create slider for rotating the parabola
var sRadius = board.create('slider', [
[1, -8.5],
[6, -8.5],
[-10, 0, 10]
], {
name: 'angle',
needsRegularUpdate: true
//snapWidth: 1
});
//create slider for adjusting the angular speed (inactive)
var sOmega = board.create('slider', [
[1, -7.5],
[6, -7.5],
[0, 0, 10]
], {
name: 'Omega',
needsRegularUpdate: true
//snapWidth: 1,
});
//fix parameters
var g = 9.81 //gravitational acceleration
var h0 = 5 //initial height of the water surface
var K1 = sOmega.Value() * sOmega.Value() / g; //coeffficient of the quadratic term of the parabola
var KK = 1/4*sOmega.Value()*sOmega.Value()*r*r/g; //constant term in the equation of the parabola
//peak coordinates of the fixed parabola
var peak = [0, -KK+h0];
//slider auxiliary variable
var v = sRadius.Value() * Math.PI * 0.5 / 10.0;
//define radius from the y-axis for I3 and I4
var R34 = Math.sqrt(2);
// Function for parallel projection
var project = function(crd, cam) {
var d = -crd[2] / cam[2];
return [1, crd[0] + d * cam[0], crd[1] + d * cam[1]];
};
//function creates points for drawing conic sections
function PPoint(xx, yy,zz,namep,fixval) {
this.XX=xx;
this.YY=yy;
this.ZZ=zz;
this.Namep=namep;
this.Fixval=fixval
}
//method for drawing each Point
PPoint.prototype.draw = function(pp) {
board.create('point', [function() {
var c = [pp.XX,pp.YY,pp.ZZ];
//debugger
return project(c, cam);
}
], {
fixed: this.Fixval,
name: this.Namep,
visible: true
})
}
var div=Math.sqrt(2);
//create and draw points
var p3 = new PPoint(0,peak[1],0,'p_3','false');
//debugger
var I_1 = new PPoint(r*Math.sin(v),K1/2*r*r-KK+h0,r*Math.cos(v),'I_1','false');
var I_2 = new PPoint(-r*Math.sin(v),K1/2*r*r-KK+h0,-r*Math.cos(v),'I_2','false');
var I_3 = new PPoint(R34*Math.sin(v),K1/2*R34*R34-KK+h0,R34*Math.cos(v),'I_3','false');
var I_4 = new PPoint(-R34*Math.sin(v),K1/2*R34*R34-KK+h0,-R34*Math.cos(v),'I_4','false');
p3.draw(p3)
I_1.draw(I_1)
I_2.draw(I_2)
I_3.draw(I_3)
//debugger;
I_4.draw(I_4)
//draw the rotating parabola
var prbl = board.create('conic', [[I_1.XX,I_1.YY,I_1.ZZ], [I_2.XX,I_2.YY,I_2.ZZ], [I_3.XX,I_3.YY,I_3.ZZ], [I_4.XX,I_4.YY,I_4.ZZ],[p3.XX,p3.YY,p3.ZZ]], {
strokeColor: '#CA7291',
strokeWidth: 2,
//trace :true
});
//debugger;
//add textbox
var txt1 = board.create('text', [3, 7, 'The blue lines delimit the volume of water when Omega = 0 and the red parabola delimits the volume without water as the bucket is rotating (surface h(r)). The water volume is constant, independent of Omega']);
The blue circles in the first fiddle are not critical, they can be added to the other one later.
Having done some debugging, the parents of the parabola all have "isReal: true" in both fiddles, but in the fiddle that isn't working the parabola itself has "isReal: false" while the fiddle that's working has "isReal: true" for the parabola. Not sure whether that's relevant, though.
In the non-working fiddle, I also tried enclosing the whole code into "board.on('mouse,function(){here all code from line 59 onwards{) to get the points move, but that didn't help; the points aren't drawn at all, not even the initial positions.
It seems that in your updated code posted above there is a very simple error: The value of sign is stored in the property pp.S, but you try to access it as pp.sign. My suggestion is to use the following code:
function PPoint2(radius,sign,namep,fixval) {
this.R = radius;
this.S = sign;
this.Namep = namep;
this.Fixval = fixval;
}
//method for drawing each Point
PPoint2.prototype.draw = function() {
var pp = this;
this.point = board.create('point', [function() {
var K1 = sOmega.Value()*sOmega.Value()/g,
KK = 1/4*sOmega.Value()*sOmega.Value()/g,
v = sRadius.Value() * Math.PI * 0.5 / 10.0,
c = [pp.S*pp.R*Math.sin(v),
K1/2*pp.R*pp.R-KK+h0,
pp.S*pp.R*Math.cos(v)];
return project(c, cam);
}], {
fixed: this.Fixval,
name: this.Namep,
visible: true
});
};
//create and draw points
var p3 = new PPoint2(0,-1,'p_3','false');
var I_1 = new PPoint2(r,1,'I_1','false');
p3.draw();
I_1.draw();
Related
I've created a geo chart that shows the map of the US:
var data = google.visualization.arrayToDataTable(response.data.states);
var formatter = new google.visualization.NumberFormat({
pattern: '$###,###.##'
});
formatter.format(data, 1);
var options = {
region: 'US',
displayMode: 'regions',
resolution: 'provinces',
colorAxis: {
colors: ['#e6f4ff', '#008ffb', '#ff4560']
},
geochartVersion: 11,
legend: {
numberFormat: '$###,###.##'
},
};
var chart = new google.visualization.GeoChart(document.getElementById(='kt_charts_states'));
chart.draw(data, options);
Now this properly creates the chart and each states fill is based on a gradient scale from the colorAxis option. My questions is: How do you go about getting the specific fill color of a state / series? For example:
EDIT:
Here's some sample data that I'm using:
[
['State', 'Sales', 'Order Count'],
['Alabama', 5939.95, 6],
['Arizona', 5256.83, 9],
['Arkansas', 7917.89, 14],
['California', 24587.1, 37],
['Colorado', 7494.87, 8],
]
We can interpolate colors ourselves using formula:
r = (1 - point - start)/end * r1 + (point-start)/end * r2
g = (1 - point - start)/end * g1 + (point-start)/end * g2
b = (1 - point - start)/end * b1 + (point-start)/end * b2
where,
rgb - the color we want to find on the gradient
r1g1b1 - starting color
r2g2b2 - end color
point - sales value of the state
start - minimum sales: $80
end - maximum sales: $25,460.26
Using above formula, following demo considers multiple colors on the colorAxis/gradient. And shows state names with their corresponding names on top left.
let info = [
['State', 'Sales', 'Order Count'],
['Alabama', 1939.95, 6],
['Arizona', 5256.83, 9],
['Arkansas', 7917.89, 14],
['California', 20587.1, 32],
['Colorado', 7494.87, 8],
['Idaho', 80, 1],
['Missouri', 25460.26, 40],
['Texas', 13000, 20],
];
let gradient = ['#e6f4ff', '#008ffb', '#ff4560'];
let range = {
start: 80,
end: 25460.26
};
google.charts.load('current', {
'packages': ['geochart'],
// Note: Because this chart requires geocoding, you'll need mapsApiKey.
// See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings
'mapsApiKey': 'AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWY'
});
google.charts.setOnLoadCallback(drawRegionsMap);
function drawRegionsMap() {
var data = google.visualization.arrayToDataTable(info);
var formatter = new google.visualization.NumberFormat({
pattern: '$###,###.##'
});
formatter.format(data, 1);
var options = {
region: 'US',
displayMode: 'regions',
resolution: 'provinces',
colorAxis: {
colors: gradient
},
geochartVersion: 11,
legend: {
numberFormat: '$###,###.##'
},
};
var chart = new google.visualization.GeoChart(document.getElementById('kt_charts_states'));
chart.draw(data, options);
};
// display colors for each state
let content = ''
info.forEach((state, i) => {
if (i === 0) return;
content += `<div style='background-color: ${getStateColor(gradient, range, state[1])}'>${state[0]}</div>`;
});
stat.innerHTML = content;
// interpolate colors
function getStateColor(gradient, range, value) {
let colors = gradient.map(c => hexToRgb(c));
let sectionLength = (range.end - range.start) / (gradient.length - 1);
let section = (value / sectionLength) | 0;
if (section + 1 == gradient.length) section -= 1;
value = value - sectionLength * section - range.start;
let result = ['r', 'g', 'b'].map(c => {
// interpolate color from start color to end color
let out = (1 - (value / sectionLength)) * colors[section][c];
out += (value / sectionLength) * colors[section + 1][c];
return Math.ceil(out);
});
return rgbToHex(...result);
}
// utility functions
function hexToRgb(hex) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function(m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + ((r | 0) << 16) + ((g | 0) << 8) + (b | 0)).toString(16).slice(1);
}
#kt_charts_states{
width: 95vw;
height:92vh;
}
#stat {
position: fixed;
top: .5rem;
right: 1rem;
}
#stat div{
width: 100px
}
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div id="kt_charts_states"></div>
<div id="stat"></div>
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).
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%;
}
I have a function that creates a simple THREE.PlaneGeometry and using an image texture material maps a piece of the image onto the plane. What I am trying to figure out is how to convert this logic from using a THREE.PlaneGeometry to a THREE.PlaneBufferGeometry, but I cannot figure out how to access the vertices & alter in the same way.
I apologize if this is a repeat question. I did search around & if one existed, I could not find it.
Here is the function & below it is a sample call for it:
/**
* options:
* imageSize: { w: #, h: # } - size of the source image
* planeSize: { w: #, h:# } - size of the actual plane to create
* position: { x: #, y:#, z: # } - position of the next plane
* material: material - the material to apply
* clipRect: { x: #, y:#, w: #, h:# } - the part of the image to clip
* x and y from the bottom left of the image.
* w and h width and height of the image region,
* name: name of the mesh
*/
function createPlane( opts ) {
var i, faces, v, vertexes, point, p,
plane = new THREE.PlaneGeometry( opts.planeSize.w, opts.planeSize.h, 1, 1 ),
mesh = new THREE.Mesh(plane, opts.material),
imgRect = {
x: opts.clipRect.x / opts.imageSize.w,
y: opts.clipRect.y / opts.imageSize.h,
w: opts.clipRect.w / opts.imageSize.w,
h: opts.clipRect.h / opts.imageSize.h
};
if (opts.name !== '') {
mesh.name = opts.name;
}
if (opts.position !== null) {
mesh.position.set(
opts.position.x,
opts.position.y,
opts.position.z
);
}
for( i = 0; i < plane.faceVertexUvs.length; i++) {
faces = plane.faceVertexUvs[i];
for( v = 0; v < faces.length; v++) {
vertexes = faces[v];
for( p = 0; p < vertexes.length; p ++ ) {
point = vertexes[p];
point.x = imgRect.x + ( point.x * imgRect.w );
point.y = imgRect.y + ( point.y * imgRect.h );
}
}
}
return mesh;
}
Here is a sample call:
var plane = createPlane({
imageSize: { w: 1024, h: 1024},
planeSize: { w: 23, h: 31 },
position: null,
material: new THREE.MeshBasicMaterial({
map: myTexture, // defined elsewhere
transparent: true
}),
name: 'myPlane',
clipRect: { x: 958, y: 226, w: 23, h: 31 }
});
You can set them directly like this:
object.geometry.attributes.uv.array[0] = 0.1;
Another way (less likely to break in the future I guess):
var quad_uvs =
[
0.0, 1.0,
1.0, 1.0,
0.0, 0.0,
1.0, 0.0
];
var uvs = new Float32Array( quad_uvs);
object.geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) );
i have list of polylines. i want to make it more organized. how can i make my polyline list into the GeoJSON?
var point1 = new coordsToLatLngArray(0, 100);
var point2 = new coordsToLatLngArray(200, 150);
var point3 = new coordsToLatLngArray(400, 70);
var point4 = new coordsToLatLngArray(10, 70);
var point5 = new coordsToLatLngArray(90, 50);
var pointList = [point1, point2, point3, point4, point5];
var firstpolyline = new L.polyline(pointList, {
color: 'red',
weight: 3,
opacity: 0.5,
smoothFactor: 1
});
firstpolyline.addTo(map);
i have trying to work with: http://leafletjs.com/examples/geojson.html
but it didn't work. my line were outside the map:
var geojsonFeature = [{
"type": "LineString",
"coordinates":
[
[0,0],
[-100,100]
]
}];
var myStyle = {
"color": "#ff7800",
"weight": 5,
"opacity": 0.65
};
var myLayer = L.geoJson(coordsToLatLngArray(), {style: myStyle }).addTo(l);
myLayer.addData(geojsonFeature);
Demo of the problem:
http://jsfiddle.net/n4k1psuh/
There will only be passed a single argument to coordsToLatLngArray, an array:
[longitude,latitude]
The function must look like this:
var coordsToLatLngArray = function(c) {
var e = fixLat(c[1]),
t = c[0],
n = e / latScale + latScaleVal / 2 / latScale,
r = t / longScale + longScaleVal / 2 / longScale;
return [-n, r]
};
Note: in geoJSON a coordinate has a reversed order, [longitude,latitude], so the coordinates must be:
[
[100, 0],
[150, 200],
[70,400],
[70,10],
[50,90]
]
to draw the feature call:
var myLayer = L.geoJson(geojsonFeature, {
style: myStyle,
coordsToLatLng:coordsToLatLngArray
}).addTo(l);
Demo: http://jsfiddle.net/Ltaw5teo/