Skip to content

Commit 95119c4

Browse files
committed
move polar polygon logic to polar/helper.js
- add move findIndexOfMin to lib/search.js
1 parent 4d20388 commit 95119c4

File tree

4 files changed

+254
-194
lines changed

4 files changed

+254
-194
lines changed

src/lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ lib.sorterAsc = searchModule.sorterAsc;
6262
lib.sorterDes = searchModule.sorterDes;
6363
lib.distinctVals = searchModule.distinctVals;
6464
lib.roundUp = searchModule.roundUp;
65+
lib.findIndexOfMin = searchModule.findIndexOfMin;
6566

6667
var statsModule = require('./stats');
6768
lib.aggNums = statsModule.aggNums;

src/lib/search.js

+24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var isNumeric = require('fast-isnumeric');
1313
var loggers = require('./loggers');
14+
var identity = require('./identity');
1415

1516
// don't trust floating point equality - fraction of bin size to call
1617
// "on the line" and ensure that they go the right way specified by
@@ -113,3 +114,26 @@ exports.roundUp = function(val, arrayIn, reverse) {
113114
}
114115
return arrayIn[low];
115116
};
117+
118+
/* find index in array 'arr' that minimizes 'fn'
119+
*
120+
* @param {array} arr : array where to search
121+
* @param {fn (optional)} fn : function to minimize,
122+
* if not given, fn is the identity function
123+
* @return {integer}
124+
*/
125+
exports.findIndexOfMin = function(arr, fn) {
126+
fn = fn || identity;
127+
128+
var min = Infinity;
129+
var ind;
130+
131+
for(var i = 0; i < arr.length; i++) {
132+
var v = fn(arr[i]);
133+
if(v < min) {
134+
min = v;
135+
ind = i;
136+
}
137+
}
138+
return ind;
139+
};

src/plots/polar/helpers.js

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/**
2+
* Copyright 2012-2018, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var polygonTester = require('../../lib/polygon').tester;
13+
14+
var findIndexOfMin = Lib.findIndexOfMin;
15+
var isAngleInsideSector = Lib.isAngleInsideSector;
16+
var angleDist = Lib.angleDist;
17+
var deg2rad = Lib.deg2rad;
18+
19+
/* is pt (r,a) inside polygon made up vertices at angles 'vangles'
20+
* inside a given polar sector
21+
*
22+
* @param {number} r : pt's radial coordinate
23+
* @param {number} a : pt's angular coordinate in *radians*
24+
* @param {2-item array} rRng : sector's radial range
25+
* @param {2-item array} sector : sector angles in *degrees*
26+
* @param {array} vangles : angles of polygon vertices in *radians*
27+
* @return {boolean}
28+
*/
29+
function isPtInsidePolygon(r, a, rRng, sector, vangles) {
30+
if(!isAngleInsideSector(a, sector)) return false;
31+
32+
var r0, r1;
33+
34+
if(rRng[0] < rRng[1]) {
35+
r0 = rRng[0];
36+
r1 = rRng[1];
37+
} else {
38+
r0 = rRng[1];
39+
r1 = rRng[0];
40+
}
41+
42+
var polygonIn = polygonTester(makePolygon(r0, sector, vangles));
43+
var polygonOut = polygonTester(makePolygon(r1, sector, vangles));
44+
var xy = [r * Math.cos(a), r * Math.sin(a)];
45+
return polygonOut.contains(xy) && !polygonIn.contains(xy);
46+
}
47+
48+
// find intersection of 'v0' <-> 'v1' edge with a ray at angle 'a'
49+
// (i.e. a line that starts from the origin at angle 'a')
50+
// given an (xp,yp) pair on the 'v0' <-> 'v1' line
51+
// (N.B. 'v0' and 'v1' are angles in radians)
52+
function findIntersectionXY(v0, v1, a, xpyp) {
53+
var xstar, ystar;
54+
55+
var xp = xpyp[0];
56+
var yp = xpyp[1];
57+
var dsin = clampTiny(Math.sin(v1) - Math.sin(v0));
58+
var dcos = clampTiny(Math.cos(v1) - Math.cos(v0));
59+
var tanA = Math.tan(a);
60+
var cotanA = clampTiny(1 / tanA);
61+
var m = dsin / dcos;
62+
var b = yp - m * xp;
63+
64+
if(cotanA) {
65+
if(dsin && dcos) {
66+
// given
67+
// g(x) := v0 -> v1 line = m*x + b
68+
// h(x) := ray at angle 'a' = m*x = tanA*x
69+
// solve g(xstar) = h(xstar)
70+
xstar = b / (tanA - m);
71+
ystar = tanA * xstar;
72+
} else if(dcos) {
73+
// horizontal v0 -> v1
74+
xstar = yp * cotanA;
75+
ystar = yp;
76+
} else {
77+
// vertical v0 -> v1
78+
xstar = xp;
79+
ystar = xp * tanA;
80+
}
81+
} else {
82+
// vertical ray
83+
if(dsin && dcos) {
84+
xstar = 0;
85+
ystar = b;
86+
} else if(dcos) {
87+
xstar = 0;
88+
ystar = yp;
89+
} else {
90+
// does this case exists?
91+
xstar = ystar = NaN;
92+
}
93+
}
94+
95+
return [xstar, ystar];
96+
}
97+
98+
// solves l^2 = (f(x)^2 - yp)^2 + (x - xp)^2
99+
// rearranged into 0 = a*x^2 + b * x + c
100+
//
101+
// where f(x) = m*x + t + yp
102+
// and (x0, x1) = (-b +/- del) / (2*a)
103+
function findXYatLength(l, m, xp, yp) {
104+
var t = -m * xp;
105+
var a = m * m + 1;
106+
var b = 2 * (m * t - xp);
107+
var c = t * t + xp * xp - l * l;
108+
var del = Math.sqrt(b * b - 4 * a * c);
109+
var x0 = (-b + del) / (2 * a);
110+
var x1 = (-b - del) / (2 * a);
111+
return [
112+
[x0, m * x0 + t + yp],
113+
[x1, m * x1 + t + yp]
114+
];
115+
}
116+
117+
function makeRegularPolygon(r, vangles) {
118+
var len = vangles.length;
119+
var vertices = new Array(len + 1);
120+
var i;
121+
for(i = 0; i < len; i++) {
122+
var va = vangles[i];
123+
vertices[i] = [r * Math.cos(va), r * Math.sin(va)];
124+
}
125+
vertices[i] = vertices[0].slice();
126+
return vertices;
127+
}
128+
129+
function makeClippedPolygon(r, sector, vangles) {
130+
var len = vangles.length;
131+
var vertices = [];
132+
var i, j;
133+
134+
function a2xy(a) {
135+
return [r * Math.cos(a), r * Math.sin(a)];
136+
}
137+
138+
function findXY(va0, va1, s) {
139+
return findIntersectionXY(va0, va1, s, a2xy(va0));
140+
}
141+
142+
function cycleIndex(ind) {
143+
return Lib.mod(ind, len);
144+
}
145+
146+
function isInside(v) {
147+
return isAngleInsideSector(v, sector);
148+
}
149+
150+
var s0 = deg2rad(sector[0]);
151+
var s1 = deg2rad(sector[1]);
152+
153+
// find index in sector closest to sector[0],
154+
// use it to find intersection of v[i0] <-> v[i0-1] edge with sector radius
155+
var i0 = findIndexOfMin(vangles, function(v) {
156+
return isInside(v) ? angleDist(v, s0) : Infinity;
157+
});
158+
var xy0 = findXY(vangles[i0], vangles[cycleIndex(i0 - 1)], s0);
159+
vertices.push(xy0);
160+
161+
// fill in in-sector vertices
162+
for(i = i0, j = 0; j < len; i++, j++) {
163+
var va = vangles[cycleIndex(i)];
164+
if(!isInside(va)) break;
165+
vertices.push(a2xy(va));
166+
}
167+
168+
// find index in sector closest to sector[1],
169+
// use it to find intersection of v[iN] <-> v[iN+1] edge with sector radius
170+
var iN = findIndexOfMin(vangles, function(v) {
171+
return isInside(v) ? angleDist(v, s1) : Infinity;
172+
});
173+
var xyN = findXY(vangles[iN], vangles[cycleIndex(iN + 1)], s1);
174+
vertices.push(xyN);
175+
176+
vertices.push([0, 0]);
177+
vertices.push(vertices[0].slice());
178+
179+
return vertices;
180+
}
181+
182+
function makePolygon(r, sector, vangles) {
183+
return Lib.isFullCircle(sector) ?
184+
makeRegularPolygon(r, vangles) :
185+
makeClippedPolygon(r, sector, vangles);
186+
}
187+
188+
function findPolygonOffset(r, sector, vangles) {
189+
var minX = Infinity;
190+
var minY = Infinity;
191+
var vertices = makePolygon(r, sector, vangles);
192+
193+
for(var i = 0; i < vertices.length; i++) {
194+
var v = vertices[i];
195+
minX = Math.min(minX, v[0]);
196+
minY = Math.min(minY, -v[1]);
197+
}
198+
return [minX, minY];
199+
}
200+
201+
// to more easily catch 'almost zero' numbers in if-else blocks
202+
function clampTiny(v) {
203+
return Math.abs(v) > 1e-10 ? v : 0;
204+
}
205+
206+
module.exports = {
207+
isPtInsidePolygon: isPtInsidePolygon,
208+
209+
makePolygon: makePolygon,
210+
makeRegularPolygon: makeRegularPolygon,
211+
makeClippedPolygon: makeClippedPolygon,
212+
213+
findPolygonOffset: findPolygonOffset,
214+
findIntersectionXY: findIntersectionXY,
215+
findXYatLength: findXYatLength,
216+
217+
clampTiny: clampTiny
218+
};

0 commit comments

Comments
 (0)