Skip to content

Commit beae56b

Browse files
committed
Modify scattersmith trace to use re and im data series
This commit also adds a `smith` plot type, which is a copy of polar, with minimal changes needed to host the `scattersmith` trace type.
1 parent a182498 commit beae56b

File tree

14 files changed

+2611
-96
lines changed

14 files changed

+2611
-96
lines changed

src/plots/smith/constants.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
module.exports = {
4+
attr: 'subplot',
5+
name: 'smith',
6+
7+
axisNames: ['angularaxis', 'radialaxis'],
8+
axisName2dataArray: {angularaxis: 'theta', radialaxis: 'r'},
9+
10+
layerNames: [
11+
'draglayer',
12+
'plotbg',
13+
'backplot',
14+
'angular-grid',
15+
'radial-grid',
16+
'frontplot',
17+
'angular-line',
18+
'radial-line',
19+
'angular-axis',
20+
'radial-axis'
21+
],
22+
23+
radialDragBoxSize: 50,
24+
angularDragBoxSize: 30,
25+
cornerLen: 25,
26+
cornerHalfWidth: 2,
27+
28+
// pixels to move mouse before you stop clamping to starting point
29+
MINDRAG: 8,
30+
// smallest radial distance [px] allowed for a zoombox
31+
MINZOOM: 20,
32+
// distance [px] off (r=0) or (r=radius) where we transition
33+
// from single-sided to two-sided radial zoom
34+
OFFEDGE: 20
35+
};

src/plots/smith/helpers.js

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
'use strict';
2+
3+
var Lib = require('../../lib');
4+
var polygonTester = require('../../lib/polygon').tester;
5+
6+
var findIndexOfMin = Lib.findIndexOfMin;
7+
var isAngleInsideSector = Lib.isAngleInsideSector;
8+
var angleDelta = Lib.angleDelta;
9+
var angleDist = Lib.angleDist;
10+
11+
/**
12+
* is pt (r,a) inside polygon made up vertices at angles 'vangles'
13+
* inside a given polar sector
14+
*
15+
* @param {number} r : pt's radial coordinate
16+
* @param {number} a : pt's angular coordinate in *radians*
17+
* @param {2-item array} rBnds : sector's radial bounds
18+
* @param {2-item array} aBnds : sector's angular bounds *radians*
19+
* @param {array} vangles : angles of polygon vertices in *radians*
20+
* @return {boolean}
21+
*/
22+
function isPtInsidePolygon(r, a, rBnds, aBnds, vangles) {
23+
if(!isAngleInsideSector(a, aBnds)) return false;
24+
25+
var r0, r1;
26+
27+
if(rBnds[0] < rBnds[1]) {
28+
r0 = rBnds[0];
29+
r1 = rBnds[1];
30+
} else {
31+
r0 = rBnds[1];
32+
r1 = rBnds[0];
33+
}
34+
35+
var polygonIn = polygonTester(makePolygon(r0, aBnds[0], aBnds[1], vangles));
36+
var polygonOut = polygonTester(makePolygon(r1, aBnds[0], aBnds[1], vangles));
37+
var xy = [r * Math.cos(a), r * Math.sin(a)];
38+
return polygonOut.contains(xy) && !polygonIn.contains(xy);
39+
}
40+
41+
// find intersection of 'v0' <-> 'v1' edge with a ray at angle 'a'
42+
// (i.e. a line that starts from the origin at angle 'a')
43+
// given an (xp,yp) pair on the 'v0' <-> 'v1' line
44+
// (N.B. 'v0' and 'v1' are angles in radians)
45+
function findIntersectionXY(v0, v1, a, xpyp) {
46+
var xstar, ystar;
47+
48+
var xp = xpyp[0];
49+
var yp = xpyp[1];
50+
var dsin = clampTiny(Math.sin(v1) - Math.sin(v0));
51+
var dcos = clampTiny(Math.cos(v1) - Math.cos(v0));
52+
var tanA = Math.tan(a);
53+
var cotanA = clampTiny(1 / tanA);
54+
var m = dsin / dcos;
55+
var b = yp - m * xp;
56+
57+
if(cotanA) {
58+
if(dsin && dcos) {
59+
// given
60+
// g(x) := v0 -> v1 line = m*x + b
61+
// h(x) := ray at angle 'a' = m*x = tanA*x
62+
// solve g(xstar) = h(xstar)
63+
xstar = b / (tanA - m);
64+
ystar = tanA * xstar;
65+
} else if(dcos) {
66+
// horizontal v0 -> v1
67+
xstar = yp * cotanA;
68+
ystar = yp;
69+
} else {
70+
// vertical v0 -> v1
71+
xstar = xp;
72+
ystar = xp * tanA;
73+
}
74+
} else {
75+
// vertical ray
76+
if(dsin && dcos) {
77+
xstar = 0;
78+
ystar = b;
79+
} else if(dcos) {
80+
xstar = 0;
81+
ystar = yp;
82+
} else {
83+
// does this case exists?
84+
xstar = ystar = NaN;
85+
}
86+
}
87+
88+
return [xstar, ystar];
89+
}
90+
91+
// solves l^2 = (f(x)^2 - yp)^2 + (x - xp)^2
92+
// rearranged into 0 = a*x^2 + b * x + c
93+
//
94+
// where f(x) = m*x + t + yp
95+
// and (x0, x1) = (-b +/- del) / (2*a)
96+
function findXYatLength(l, m, xp, yp) {
97+
var t = -m * xp;
98+
var a = m * m + 1;
99+
var b = 2 * (m * t - xp);
100+
var c = t * t + xp * xp - l * l;
101+
var del = Math.sqrt(b * b - 4 * a * c);
102+
var x0 = (-b + del) / (2 * a);
103+
var x1 = (-b - del) / (2 * a);
104+
return [
105+
[x0, m * x0 + t + yp],
106+
[x1, m * x1 + t + yp]
107+
];
108+
}
109+
110+
function makeRegularPolygon(r, vangles) {
111+
var len = vangles.length;
112+
var vertices = new Array(len + 1);
113+
var i;
114+
for(i = 0; i < len; i++) {
115+
var va = vangles[i];
116+
vertices[i] = [r * Math.cos(va), r * Math.sin(va)];
117+
}
118+
vertices[i] = vertices[0].slice();
119+
return vertices;
120+
}
121+
122+
function makeClippedPolygon(r, a0, a1, vangles) {
123+
var len = vangles.length;
124+
var vertices = [];
125+
var i, j;
126+
127+
function a2xy(a) {
128+
return [r * Math.cos(a), r * Math.sin(a)];
129+
}
130+
131+
function findXY(va0, va1, s) {
132+
return findIntersectionXY(va0, va1, s, a2xy(va0));
133+
}
134+
135+
function cycleIndex(ind) {
136+
return Lib.mod(ind, len);
137+
}
138+
139+
function isInside(v) {
140+
return isAngleInsideSector(v, [a0, a1]);
141+
}
142+
143+
// find index in sector closest to a0
144+
// use it to find intersection of v[i0] <-> v[i0-1] edge with sector radius
145+
var i0 = findIndexOfMin(vangles, function(v) {
146+
return isInside(v) ? angleDist(v, a0) : Infinity;
147+
});
148+
var xy0 = findXY(vangles[i0], vangles[cycleIndex(i0 - 1)], a0);
149+
vertices.push(xy0);
150+
151+
// fill in in-sector vertices
152+
for(i = i0, j = 0; j < len; i++, j++) {
153+
var va = vangles[cycleIndex(i)];
154+
if(!isInside(va)) break;
155+
vertices.push(a2xy(va));
156+
}
157+
158+
// find index in sector closest to a1,
159+
// use it to find intersection of v[iN] <-> v[iN+1] edge with sector radius
160+
var iN = findIndexOfMin(vangles, function(v) {
161+
return isInside(v) ? angleDist(v, a1) : Infinity;
162+
});
163+
var xyN = findXY(vangles[iN], vangles[cycleIndex(iN + 1)], a1);
164+
vertices.push(xyN);
165+
166+
vertices.push([0, 0]);
167+
vertices.push(vertices[0].slice());
168+
169+
return vertices;
170+
}
171+
172+
function makePolygon(r, a0, a1, vangles) {
173+
return Lib.isFullCircle([a0, a1]) ?
174+
makeRegularPolygon(r, vangles) :
175+
makeClippedPolygon(r, a0, a1, vangles);
176+
}
177+
178+
function findPolygonOffset(r, a0, a1, vangles) {
179+
var minX = Infinity;
180+
var minY = Infinity;
181+
var vertices = makePolygon(r, a0, a1, vangles);
182+
183+
for(var i = 0; i < vertices.length; i++) {
184+
var v = vertices[i];
185+
minX = Math.min(minX, v[0]);
186+
minY = Math.min(minY, -v[1]);
187+
}
188+
return [minX, minY];
189+
}
190+
191+
/**
192+
* find vertex angles (in 'vangles') the enclose angle 'a'
193+
*
194+
* @param {number} a : angle in *radians*
195+
* @param {array} vangles : angles of polygon vertices in *radians*
196+
* @return {2-item array}
197+
*/
198+
function findEnclosingVertexAngles(a, vangles) {
199+
var minFn = function(v) {
200+
var adelta = angleDelta(v, a);
201+
return adelta > 0 ? adelta : Infinity;
202+
};
203+
var i0 = findIndexOfMin(vangles, minFn);
204+
var i1 = Lib.mod(i0 + 1, vangles.length);
205+
return [vangles[i0], vangles[i1]];
206+
}
207+
208+
// to more easily catch 'almost zero' numbers in if-else blocks
209+
function clampTiny(v) {
210+
return Math.abs(v) > 1e-10 ? v : 0;
211+
}
212+
213+
function transformForSVG(pts0, cx, cy) {
214+
cx = cx || 0;
215+
cy = cy || 0;
216+
217+
var len = pts0.length;
218+
var pts1 = new Array(len);
219+
220+
for(var i = 0; i < len; i++) {
221+
var pt = pts0[i];
222+
pts1[i] = [cx + pt[0], cy - pt[1]];
223+
}
224+
return pts1;
225+
}
226+
227+
/**
228+
* path polygon
229+
*
230+
* @param {number} r : polygon 'radius'
231+
* @param {number} a0 : first angular coordinate in *radians*
232+
* @param {number} a1 : second angular coordinate in *radians*
233+
* @param {array} vangles : angles of polygon vertices in *radians*
234+
* @param {number (optional)} cx : x coordinate of center
235+
* @param {number (optional)} cy : y coordinate of center
236+
* @return {string} svg path
237+
*
238+
*/
239+
function pathPolygon(r, a0, a1, vangles, cx, cy) {
240+
var poly = makePolygon(r, a0, a1, vangles);
241+
return 'M' + transformForSVG(poly, cx, cy).join('L');
242+
}
243+
244+
/**
245+
* path a polygon 'annulus'
246+
* i.e. a polygon with a concentric hole
247+
*
248+
* N.B. this routine uses the evenodd SVG rule
249+
*
250+
* @param {number} r0 : first radial coordinate
251+
* @param {number} r1 : second radial coordinate
252+
* @param {number} a0 : first angular coordinate in *radians*
253+
* @param {number} a1 : second angular coordinate in *radians*
254+
* @param {array} vangles : angles of polygon vertices in *radians*
255+
* @param {number (optional)} cx : x coordinate of center
256+
* @param {number (optional)} cy : y coordinate of center
257+
* @return {string} svg path
258+
*
259+
*/
260+
function pathPolygonAnnulus(r0, r1, a0, a1, vangles, cx, cy) {
261+
var rStart, rEnd;
262+
263+
if(r0 < r1) {
264+
rStart = r0;
265+
rEnd = r1;
266+
} else {
267+
rStart = r1;
268+
rEnd = r0;
269+
}
270+
271+
var inner = transformForSVG(makePolygon(rStart, a0, a1, vangles), cx, cy);
272+
var outer = transformForSVG(makePolygon(rEnd, a0, a1, vangles), cx, cy);
273+
return 'M' + outer.reverse().join('L') + 'M' + inner.join('L');
274+
}
275+
276+
module.exports = {
277+
isPtInsidePolygon: isPtInsidePolygon,
278+
findPolygonOffset: findPolygonOffset,
279+
findEnclosingVertexAngles: findEnclosingVertexAngles,
280+
findIntersectionXY: findIntersectionXY,
281+
findXYatLength: findXYatLength,
282+
clampTiny: clampTiny,
283+
pathPolygon: pathPolygon,
284+
pathPolygonAnnulus: pathPolygonAnnulus
285+
};

0 commit comments

Comments
 (0)