@joint/core 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/LICENSE +376 -0
  2. package/README.md +49 -0
  3. package/dist/geometry.js +6486 -0
  4. package/dist/geometry.min.js +8 -0
  5. package/dist/joint.d.ts +5536 -0
  6. package/dist/joint.js +39629 -0
  7. package/dist/joint.min.js +8 -0
  8. package/dist/joint.nowrap.js +39626 -0
  9. package/dist/joint.nowrap.min.js +8 -0
  10. package/dist/vectorizer.js +9135 -0
  11. package/dist/vectorizer.min.js +8 -0
  12. package/dist/version.mjs +3 -0
  13. package/index.js +3 -0
  14. package/joint.mjs +27 -0
  15. package/package.json +192 -0
  16. package/src/V/annotation.mjs +0 -0
  17. package/src/V/index.mjs +2642 -0
  18. package/src/anchors/index.mjs +123 -0
  19. package/src/config/index.mjs +12 -0
  20. package/src/connectionPoints/index.mjs +202 -0
  21. package/src/connectionStrategies/index.mjs +73 -0
  22. package/src/connectors/curve.mjs +553 -0
  23. package/src/connectors/index.mjs +6 -0
  24. package/src/connectors/jumpover.mjs +452 -0
  25. package/src/connectors/normal.mjs +12 -0
  26. package/src/connectors/rounded.mjs +17 -0
  27. package/src/connectors/smooth.mjs +44 -0
  28. package/src/connectors/straight.mjs +110 -0
  29. package/src/dia/Cell.mjs +945 -0
  30. package/src/dia/CellView.mjs +1316 -0
  31. package/src/dia/Element.mjs +519 -0
  32. package/src/dia/ElementView.mjs +859 -0
  33. package/src/dia/Graph.mjs +1112 -0
  34. package/src/dia/HighlighterView.mjs +319 -0
  35. package/src/dia/Link.mjs +565 -0
  36. package/src/dia/LinkView.mjs +2207 -0
  37. package/src/dia/Paper.mjs +3171 -0
  38. package/src/dia/PaperLayer.mjs +75 -0
  39. package/src/dia/ToolView.mjs +69 -0
  40. package/src/dia/ToolsView.mjs +128 -0
  41. package/src/dia/attributes/calc.mjs +128 -0
  42. package/src/dia/attributes/connection.mjs +75 -0
  43. package/src/dia/attributes/defs.mjs +76 -0
  44. package/src/dia/attributes/eval.mjs +64 -0
  45. package/src/dia/attributes/index.mjs +69 -0
  46. package/src/dia/attributes/legacy.mjs +148 -0
  47. package/src/dia/attributes/offset.mjs +53 -0
  48. package/src/dia/attributes/props.mjs +30 -0
  49. package/src/dia/attributes/shape.mjs +92 -0
  50. package/src/dia/attributes/text.mjs +180 -0
  51. package/src/dia/index.mjs +13 -0
  52. package/src/dia/layers/GridLayer.mjs +176 -0
  53. package/src/dia/ports.mjs +874 -0
  54. package/src/elementTools/Control.mjs +153 -0
  55. package/src/elementTools/HoverConnect.mjs +37 -0
  56. package/src/elementTools/index.mjs +5 -0
  57. package/src/env/index.mjs +43 -0
  58. package/src/g/bezier.mjs +175 -0
  59. package/src/g/curve.mjs +956 -0
  60. package/src/g/ellipse.mjs +245 -0
  61. package/src/g/extend.mjs +64 -0
  62. package/src/g/geometry.helpers.mjs +58 -0
  63. package/src/g/index.mjs +17 -0
  64. package/src/g/intersection.mjs +511 -0
  65. package/src/g/line.bearing.mjs +30 -0
  66. package/src/g/line.length.mjs +5 -0
  67. package/src/g/line.mjs +356 -0
  68. package/src/g/line.squaredLength.mjs +10 -0
  69. package/src/g/path.mjs +2260 -0
  70. package/src/g/point.mjs +375 -0
  71. package/src/g/points.mjs +247 -0
  72. package/src/g/polygon.mjs +51 -0
  73. package/src/g/polyline.mjs +523 -0
  74. package/src/g/rect.mjs +556 -0
  75. package/src/g/types.mjs +10 -0
  76. package/src/highlighters/addClass.mjs +27 -0
  77. package/src/highlighters/index.mjs +5 -0
  78. package/src/highlighters/list.mjs +111 -0
  79. package/src/highlighters/mask.mjs +220 -0
  80. package/src/highlighters/opacity.mjs +17 -0
  81. package/src/highlighters/stroke.mjs +100 -0
  82. package/src/layout/index.mjs +4 -0
  83. package/src/layout/ports/port.mjs +188 -0
  84. package/src/layout/ports/portLabel.mjs +224 -0
  85. package/src/linkAnchors/index.mjs +76 -0
  86. package/src/linkTools/Anchor.mjs +235 -0
  87. package/src/linkTools/Arrowhead.mjs +103 -0
  88. package/src/linkTools/Boundary.mjs +48 -0
  89. package/src/linkTools/Button.mjs +121 -0
  90. package/src/linkTools/Connect.mjs +85 -0
  91. package/src/linkTools/HoverConnect.mjs +161 -0
  92. package/src/linkTools/Segments.mjs +393 -0
  93. package/src/linkTools/Vertices.mjs +253 -0
  94. package/src/linkTools/helpers.mjs +33 -0
  95. package/src/linkTools/index.mjs +8 -0
  96. package/src/mvc/Collection.mjs +560 -0
  97. package/src/mvc/Data.mjs +46 -0
  98. package/src/mvc/Dom/Dom.mjs +587 -0
  99. package/src/mvc/Dom/Event.mjs +130 -0
  100. package/src/mvc/Dom/animations.mjs +122 -0
  101. package/src/mvc/Dom/events.mjs +69 -0
  102. package/src/mvc/Dom/index.mjs +13 -0
  103. package/src/mvc/Dom/methods.mjs +392 -0
  104. package/src/mvc/Dom/props.mjs +77 -0
  105. package/src/mvc/Dom/vars.mjs +5 -0
  106. package/src/mvc/Events.mjs +337 -0
  107. package/src/mvc/Listener.mjs +33 -0
  108. package/src/mvc/Model.mjs +239 -0
  109. package/src/mvc/View.mjs +323 -0
  110. package/src/mvc/ViewBase.mjs +182 -0
  111. package/src/mvc/index.mjs +9 -0
  112. package/src/mvc/mvcUtils.mjs +90 -0
  113. package/src/polyfills/array.js +4 -0
  114. package/src/polyfills/base64.js +68 -0
  115. package/src/polyfills/index.mjs +5 -0
  116. package/src/polyfills/number.js +3 -0
  117. package/src/polyfills/string.js +3 -0
  118. package/src/polyfills/typedArray.js +47 -0
  119. package/src/routers/index.mjs +6 -0
  120. package/src/routers/manhattan.mjs +856 -0
  121. package/src/routers/metro.mjs +91 -0
  122. package/src/routers/normal.mjs +6 -0
  123. package/src/routers/oneSide.mjs +60 -0
  124. package/src/routers/orthogonal.mjs +323 -0
  125. package/src/routers/rightAngle.mjs +1056 -0
  126. package/src/shapes/index.mjs +3 -0
  127. package/src/shapes/standard.mjs +755 -0
  128. package/src/util/cloneCells.mjs +67 -0
  129. package/src/util/getRectPoint.mjs +65 -0
  130. package/src/util/index.mjs +5 -0
  131. package/src/util/svgTagTemplate.mjs +110 -0
  132. package/src/util/util.mjs +1754 -0
  133. package/src/util/utilHelpers.mjs +2402 -0
  134. package/src/util/wrappers.mjs +56 -0
  135. package/types/geometry.d.ts +815 -0
  136. package/types/index.d.ts +53 -0
  137. package/types/joint.d.ts +4391 -0
  138. package/types/joint.head.d.ts +12 -0
  139. package/types/vectorizer.d.ts +327 -0
@@ -0,0 +1,375 @@
1
+ /*
2
+ Point is the most basic object consisting of x/y coordinate.
3
+
4
+ Possible instantiations are:
5
+ * `Point(10, 20)`
6
+ * `new Point(10, 20)`
7
+ * `Point('10 20')`
8
+ * `Point(Point(10, 20))`
9
+ */
10
+ import { normalizeAngle, random, snapToGrid, toDeg, toRad } from './geometry.helpers.mjs';
11
+ import { bearing } from './line.bearing.mjs';
12
+ import { squaredLength } from './line.squaredLength.mjs';
13
+ import { length } from './line.length.mjs';
14
+ import { types } from './types.mjs';
15
+
16
+ const {
17
+ abs,
18
+ cos,
19
+ sin,
20
+ sqrt,
21
+ min,
22
+ max,
23
+ atan2,
24
+ round,
25
+ pow,
26
+ PI
27
+ } = Math;
28
+
29
+ export const Point = function(x, y) {
30
+
31
+ if (!(this instanceof Point)) {
32
+ return new Point(x, y);
33
+ }
34
+
35
+ if (typeof x === 'string') {
36
+ var xy = x.split(x.indexOf('@') === -1 ? ' ' : '@');
37
+ x = parseFloat(xy[0]);
38
+ y = parseFloat(xy[1]);
39
+
40
+ } else if (Object(x) === x) {
41
+ y = x.y;
42
+ x = x.x;
43
+ }
44
+
45
+ this.x = x === undefined ? 0 : x;
46
+ this.y = y === undefined ? 0 : y;
47
+ };
48
+
49
+ // Alternative constructor, from polar coordinates.
50
+ // @param {number} Distance.
51
+ // @param {number} Angle in radians.
52
+ // @param {point} [optional] Origin.
53
+ Point.fromPolar = function(distance, angle, origin) {
54
+
55
+ origin = new Point(origin);
56
+ var x = abs(distance * cos(angle));
57
+ var y = abs(distance * sin(angle));
58
+ var deg = normalizeAngle(toDeg(angle));
59
+
60
+ if (deg < 90) {
61
+ y = -y;
62
+
63
+ } else if (deg < 180) {
64
+ x = -x;
65
+ y = -y;
66
+
67
+ } else if (deg < 270) {
68
+ x = -x;
69
+ }
70
+
71
+ return new Point(origin.x + x, origin.y + y);
72
+ };
73
+
74
+ // Create a point with random coordinates that fall into the range `[x1, x2]` and `[y1, y2]`.
75
+ Point.random = function(x1, x2, y1, y2) {
76
+
77
+ return new Point(random(x1, x2), random(y1, y2));
78
+ };
79
+
80
+ Point.prototype = {
81
+
82
+ type: types.Point,
83
+
84
+ chooseClosest: function(points) {
85
+
86
+ var n = points.length;
87
+ if (n === 1) return new Point(points[0]);
88
+ var closest = null;
89
+ var minSqrDistance = Infinity;
90
+ for (var i = 0; i < n; i++) {
91
+ var p = new Point(points[i]);
92
+ var sqrDistance = this.squaredDistance(p);
93
+ if (sqrDistance < minSqrDistance) {
94
+ closest = p;
95
+ minSqrDistance = sqrDistance;
96
+ }
97
+ }
98
+ return closest;
99
+ },
100
+
101
+ // If point lies outside rectangle `r`, return the nearest point on the boundary of rect `r`,
102
+ // otherwise return point itself.
103
+ // (see Squeak Smalltalk, Point>>adhereTo:)
104
+ adhereToRect: function(r) {
105
+
106
+ if (r.containsPoint(this)) {
107
+ return this;
108
+ }
109
+
110
+ this.x = min(max(this.x, r.x), r.x + r.width);
111
+ this.y = min(max(this.y, r.y), r.y + r.height);
112
+ return this;
113
+ },
114
+
115
+ // Compute the angle between vector from me to p1 and the vector from me to p2.
116
+ // ordering of points p1 and p2 is important!
117
+ // theta function's angle convention:
118
+ // returns angles between 0 and 180 when the angle is counterclockwise
119
+ // returns angles between 180 and 360 to convert clockwise angles into counterclockwise ones
120
+ // returns NaN if any of the points p1, p2 is coincident with this point
121
+ angleBetween: function(p1, p2) {
122
+
123
+ var angleBetween = (this.equals(p1) || this.equals(p2)) ? NaN : (this.theta(p2) - this.theta(p1));
124
+
125
+ if (angleBetween < 0) {
126
+ angleBetween += 360; // correction to keep angleBetween between 0 and 360
127
+ }
128
+
129
+ return angleBetween;
130
+ },
131
+
132
+ // Return the bearing between me and the given point.
133
+ bearing: function(point) {
134
+ return bearing(this, point);
135
+ },
136
+
137
+ // Returns change in angle from my previous position (-dx, -dy) to my new position
138
+ // relative to ref point.
139
+ changeInAngle: function(dx, dy, ref) {
140
+
141
+ // Revert the translation and measure the change in angle around x-axis.
142
+ return this.clone().offset(-dx, -dy).theta(ref) - this.theta(ref);
143
+ },
144
+
145
+ clone: function() {
146
+
147
+ return new Point(this);
148
+ },
149
+
150
+ // Returns the cross product of this point relative to two other points
151
+ // this point is the common point
152
+ // point p1 lies on the first vector, point p2 lies on the second vector
153
+ // watch out for the ordering of points p1 and p2!
154
+ // positive result indicates a clockwise ("right") turn from first to second vector
155
+ // negative result indicates a counterclockwise ("left") turn from first to second vector
156
+ // zero indicates that the first and second vector are collinear
157
+ // note that the above directions are reversed from the usual answer on the Internet
158
+ // that is because we are in a left-handed coord system (because the y-axis points downward)
159
+ cross: function(p1, p2) {
160
+
161
+ return (p1 && p2) ? (((p2.x - this.x) * (p1.y - this.y)) - ((p2.y - this.y) * (p1.x - this.x))) : NaN;
162
+ },
163
+
164
+ difference: function(dx, dy) {
165
+
166
+ if ((Object(dx) === dx)) {
167
+ dy = dx.y;
168
+ dx = dx.x;
169
+ }
170
+
171
+ return new Point(this.x - (dx || 0), this.y - (dy || 0));
172
+ },
173
+
174
+ // Returns distance between me and point `p`.
175
+ distance: function(p) {
176
+ return length(this, p);
177
+ },
178
+
179
+ // Returns the dot product of this point with given other point
180
+ dot: function(p) {
181
+
182
+ return p ? (this.x * p.x + this.y * p.y) : NaN;
183
+ },
184
+
185
+ equals: function(p) {
186
+
187
+ return !!p &&
188
+ this.x === p.x &&
189
+ this.y === p.y;
190
+ },
191
+
192
+ // Linear interpolation
193
+ lerp: function(p, t) {
194
+
195
+ var x = this.x;
196
+ var y = this.y;
197
+ return new Point((1 - t) * x + t * p.x, (1 - t) * y + t * p.y);
198
+ },
199
+
200
+ magnitude: function() {
201
+
202
+ return sqrt((this.x * this.x) + (this.y * this.y)) || 0.01;
203
+ },
204
+
205
+ // Returns a manhattan (taxi-cab) distance between me and point `p`.
206
+ manhattanDistance: function(p) {
207
+
208
+ return abs(p.x - this.x) + abs(p.y - this.y);
209
+ },
210
+
211
+ // Move point on line starting from ref ending at me by
212
+ // distance distance.
213
+ move: function(ref, distance) {
214
+
215
+ var theta = toRad((new Point(ref)).theta(this));
216
+ var offset = this.offset(cos(theta) * distance, -sin(theta) * distance);
217
+ return offset;
218
+ },
219
+
220
+ // Scales x and y such that the distance between the point and the origin (0,0) is equal to the given length.
221
+ normalize: function(length) {
222
+
223
+ var scale = (length || 1) / this.magnitude();
224
+ return this.scale(scale, scale);
225
+ },
226
+
227
+ // Offset me by the specified amount.
228
+ offset: function(dx, dy) {
229
+
230
+ if ((Object(dx) === dx)) {
231
+ dy = dx.y;
232
+ dx = dx.x;
233
+ }
234
+
235
+ this.x += dx || 0;
236
+ this.y += dy || 0;
237
+ return this;
238
+ },
239
+
240
+ // Returns a point that is the reflection of me with
241
+ // the center of inversion in ref point.
242
+ reflection: function(ref) {
243
+
244
+ return (new Point(ref)).move(this, this.distance(ref));
245
+ },
246
+
247
+ // Rotate point by angle around origin.
248
+ // Angle is flipped because this is a left-handed coord system (y-axis points downward).
249
+ rotate: function(origin, angle) {
250
+
251
+ if (angle === 0) return this;
252
+
253
+ origin = origin || new Point(0, 0);
254
+
255
+ angle = toRad(normalizeAngle(-angle));
256
+ var cosAngle = cos(angle);
257
+ var sinAngle = sin(angle);
258
+
259
+ var x = (cosAngle * (this.x - origin.x)) - (sinAngle * (this.y - origin.y)) + origin.x;
260
+ var y = (sinAngle * (this.x - origin.x)) + (cosAngle * (this.y - origin.y)) + origin.y;
261
+
262
+ this.x = x;
263
+ this.y = y;
264
+ return this;
265
+ },
266
+
267
+ round: function(precision) {
268
+
269
+ let f = 1; // case 0
270
+ if (precision) {
271
+ switch (precision) {
272
+ case 1: f = 10; break;
273
+ case 2: f = 100; break;
274
+ case 3: f = 1000; break;
275
+ default: f = pow(10, precision); break;
276
+ }
277
+ }
278
+
279
+ this.x = round(this.x * f) / f;
280
+ this.y = round(this.y * f) / f;
281
+ return this;
282
+ },
283
+
284
+ // Scale point with origin.
285
+ scale: function(sx, sy, origin) {
286
+
287
+ origin = (origin && new Point(origin)) || new Point(0, 0);
288
+ this.x = origin.x + sx * (this.x - origin.x);
289
+ this.y = origin.y + sy * (this.y - origin.y);
290
+ return this;
291
+ },
292
+
293
+ snapToGrid: function(gx, gy) {
294
+
295
+ this.x = snapToGrid(this.x, gx);
296
+ this.y = snapToGrid(this.y, gy || gx);
297
+ return this;
298
+ },
299
+
300
+ squaredDistance: function(p) {
301
+ return squaredLength(this, p);
302
+ },
303
+
304
+ // Compute the angle between me and `p` and the x axis.
305
+ // (cartesian-to-polar coordinates conversion)
306
+ // Return theta angle in degrees.
307
+ theta: function(p) {
308
+
309
+ p = new Point(p);
310
+
311
+ // Invert the y-axis.
312
+ var y = -(p.y - this.y);
313
+ var x = p.x - this.x;
314
+ var rad = atan2(y, x); // defined for all 0 corner cases
315
+
316
+ // Correction for III. and IV. quadrant.
317
+ if (rad < 0) {
318
+ rad = 2 * PI + rad;
319
+ }
320
+
321
+ return 180 * rad / PI;
322
+ },
323
+
324
+ toJSON: function() {
325
+
326
+ return { x: this.x, y: this.y };
327
+ },
328
+
329
+ // Converts rectangular to polar coordinates.
330
+ // An origin can be specified, otherwise it's 0@0.
331
+ toPolar: function(o) {
332
+
333
+ o = (o && new Point(o)) || new Point(0, 0);
334
+ var x = this.x;
335
+ var y = this.y;
336
+ this.x = sqrt((x - o.x) * (x - o.x) + (y - o.y) * (y - o.y)); // r
337
+ this.y = toRad(o.theta(new Point(x, y)));
338
+ return this;
339
+ },
340
+
341
+ toString: function() {
342
+
343
+ return this.x + '@' + this.y;
344
+ },
345
+
346
+ serialize: function() {
347
+
348
+ return this.x + ',' + this.y;
349
+ },
350
+
351
+ update: function(x, y) {
352
+
353
+ if ((Object(x) === x)) {
354
+ y = x.y;
355
+ x = x.x;
356
+ }
357
+
358
+ this.x = x || 0;
359
+ this.y = y || 0;
360
+ return this;
361
+ },
362
+
363
+ // Compute the angle between the vector from 0,0 to me and the vector from 0,0 to p.
364
+ // Returns NaN if p is at 0,0.
365
+ vectorAngle: function(p) {
366
+
367
+ var zero = new Point(0, 0);
368
+ return zero.angleBetween(this, p);
369
+ }
370
+ };
371
+
372
+ Point.prototype.translate = Point.prototype.offset;
373
+
374
+ // For backwards compatibility:
375
+ export const point = Point;
@@ -0,0 +1,247 @@
1
+ export function parsePoints(svgString) {
2
+
3
+ // Step 1: Discard surrounding spaces
4
+ const trimmedString = svgString.trim();
5
+ if (trimmedString === '') return [];
6
+
7
+ const points = [];
8
+
9
+ // Step 2: Split at commas (+ their surrounding spaces) or at multiple spaces
10
+ // ReDoS mitigation: Have an anchor at the beginning of each alternation
11
+ // Note: This doesn't simplify double (or more) commas - causes empty coords
12
+ // This regex is used by `split()`, so it doesn't need to use /g
13
+ const coords = trimmedString.split(/\b\s*,\s*|,\s*|\s+/);
14
+
15
+ const numCoords = coords.length;
16
+ for (let i = 0; i < numCoords; i += 2) {
17
+ // Step 3: Convert each coord to number
18
+ // Note: If the coord cannot be converted to a number, it will be `NaN`
19
+ // Note: If the coord is empty ("", e.g. from ",," input), it will be `0`
20
+ // Note: If we end up with an odd number of coords, the last point's second coord will be `NaN`
21
+ points.push({ x: +coords[i], y: +coords[i + 1] });
22
+ }
23
+ return points;
24
+ }
25
+
26
+ export function clonePoints(points) {
27
+ const numPoints = points.length;
28
+ if (numPoints === 0) return [];
29
+ const newPoints = [];
30
+ for (let i = 0; i < numPoints; i++) {
31
+ const point = points[i].clone();
32
+ newPoints.push(point);
33
+ }
34
+ return newPoints;
35
+ }
36
+
37
+ // Returns a convex-hull polyline from this polyline.
38
+ // Implements the Graham scan (https://en.wikipedia.org/wiki/Graham_scan).
39
+ // Output polyline starts at the first element of the original polyline that is on the hull, then continues clockwise.
40
+ // Minimal polyline is found (only vertices of the hull are reported, no collinear points).
41
+ export function convexHull(points) {
42
+
43
+ const { abs } = Math;
44
+
45
+ var i;
46
+ var n;
47
+
48
+ var numPoints = points.length;
49
+ if (numPoints === 0) return []; // if points array is empty
50
+
51
+ // step 1: find the starting point - point with the lowest y (if equality, highest x)
52
+ var startPoint;
53
+ for (i = 0; i < numPoints; i++) {
54
+ if (startPoint === undefined) {
55
+ // if this is the first point we see, set it as start point
56
+ startPoint = points[i];
57
+
58
+ } else if (points[i].y < startPoint.y) {
59
+ // start point should have lowest y from all points
60
+ startPoint = points[i];
61
+
62
+ } else if ((points[i].y === startPoint.y) && (points[i].x > startPoint.x)) {
63
+ // if two points have the lowest y, choose the one that has highest x
64
+ // there are no points to the right of startPoint - no ambiguity about theta 0
65
+ // if there are several coincident start point candidates, first one is reported
66
+ startPoint = points[i];
67
+ }
68
+ }
69
+
70
+ // step 2: sort the list of points
71
+ // sorting by angle between line from startPoint to point and the x-axis (theta)
72
+
73
+ // step 2a: create the point records = [point, originalIndex, angle]
74
+ var sortedPointRecords = [];
75
+ for (i = 0; i < numPoints; i++) {
76
+
77
+ var angle = startPoint.theta(points[i]);
78
+ if (angle === 0) {
79
+ angle = 360; // give highest angle to start point
80
+ // the start point will end up at end of sorted list
81
+ // the start point will end up at beginning of hull points list
82
+ }
83
+
84
+ var entry = [points[i], i, angle];
85
+ sortedPointRecords.push(entry);
86
+ }
87
+
88
+ // step 2b: sort the list in place
89
+ sortedPointRecords.sort(function(record1, record2) {
90
+ // returning a negative number here sorts record1 before record2
91
+ // if first angle is smaller than second, first angle should come before second
92
+
93
+ var sortOutput = record1[2] - record2[2]; // negative if first angle smaller
94
+ if (sortOutput === 0) {
95
+ // if the two angles are equal, sort by originalIndex
96
+ sortOutput = record2[1] - record1[1]; // negative if first index larger
97
+ // coincident points will be sorted in reverse-numerical order
98
+ // so the coincident points with lower original index will be considered first
99
+ }
100
+
101
+ return sortOutput;
102
+ });
103
+
104
+ // step 2c: duplicate start record from the top of the stack to the bottom of the stack
105
+ if (sortedPointRecords.length > 2) {
106
+ var startPointRecord = sortedPointRecords[sortedPointRecords.length - 1];
107
+ sortedPointRecords.unshift(startPointRecord);
108
+ }
109
+
110
+ // step 3a: go through sorted points in order and find those with right turns
111
+ // we want to get our results in clockwise order
112
+ var insidePoints = {}; // dictionary of points with left turns - cannot be on the hull
113
+ var hullPointRecords = []; // stack of records with right turns - hull point candidates
114
+
115
+ var currentPointRecord;
116
+ var currentPoint;
117
+ var lastHullPointRecord;
118
+ var lastHullPoint;
119
+ var secondLastHullPointRecord;
120
+ var secondLastHullPoint;
121
+ while (sortedPointRecords.length !== 0) {
122
+
123
+ currentPointRecord = sortedPointRecords.pop();
124
+ currentPoint = currentPointRecord[0];
125
+
126
+ // check if point has already been discarded
127
+ // keys for insidePoints are stored in the form 'point.x@point.y@@originalIndex'
128
+ if (insidePoints.hasOwnProperty(currentPointRecord[0] + '@@' + currentPointRecord[1])) {
129
+ // this point had an incorrect turn at some previous iteration of this loop
130
+ // this disqualifies it from possibly being on the hull
131
+ continue;
132
+ }
133
+
134
+ var correctTurnFound = false;
135
+ while (!correctTurnFound) {
136
+
137
+ if (hullPointRecords.length < 2) {
138
+ // not enough points for comparison, just add current point
139
+ hullPointRecords.push(currentPointRecord);
140
+ correctTurnFound = true;
141
+
142
+ } else {
143
+ lastHullPointRecord = hullPointRecords.pop();
144
+ lastHullPoint = lastHullPointRecord[0];
145
+ secondLastHullPointRecord = hullPointRecords.pop();
146
+ secondLastHullPoint = secondLastHullPointRecord[0];
147
+
148
+ var crossProduct = secondLastHullPoint.cross(lastHullPoint, currentPoint);
149
+
150
+ if (crossProduct < 0) {
151
+ // found a right turn
152
+ hullPointRecords.push(secondLastHullPointRecord);
153
+ hullPointRecords.push(lastHullPointRecord);
154
+ hullPointRecords.push(currentPointRecord);
155
+ correctTurnFound = true;
156
+
157
+ } else if (crossProduct === 0) {
158
+ // the three points are collinear
159
+ // three options:
160
+ // there may be a 180 or 0 degree angle at lastHullPoint
161
+ // or two of the three points are coincident
162
+ var THRESHOLD = 1e-10; // we have to take rounding errors into account
163
+ var angleBetween = lastHullPoint.angleBetween(secondLastHullPoint, currentPoint);
164
+ if (abs(angleBetween - 180) < THRESHOLD) { // rounding around 180 to 180
165
+ // if the cross product is 0 because the angle is 180 degrees
166
+ // discard last hull point (add to insidePoints)
167
+ //insidePoints.unshift(lastHullPoint);
168
+ insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
169
+ // reenter second-to-last hull point (will be last at next iter)
170
+ hullPointRecords.push(secondLastHullPointRecord);
171
+ // do not do anything with current point
172
+ // correct turn not found
173
+
174
+ } else if (lastHullPoint.equals(currentPoint) || secondLastHullPoint.equals(lastHullPoint)) {
175
+ // if the cross product is 0 because two points are the same
176
+ // discard last hull point (add to insidePoints)
177
+ //insidePoints.unshift(lastHullPoint);
178
+ insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
179
+ // reenter second-to-last hull point (will be last at next iter)
180
+ hullPointRecords.push(secondLastHullPointRecord);
181
+ // do not do anything with current point
182
+ // correct turn not found
183
+
184
+ } else if (abs(((angleBetween + 1) % 360) - 1) < THRESHOLD) { // rounding around 0 and 360 to 0
185
+ // if the cross product is 0 because the angle is 0 degrees
186
+ // remove last hull point from hull BUT do not discard it
187
+ // reenter second-to-last hull point (will be last at next iter)
188
+ hullPointRecords.push(secondLastHullPointRecord);
189
+ // put last hull point back into the sorted point records list
190
+ sortedPointRecords.push(lastHullPointRecord);
191
+ // we are switching the order of the 0deg and 180deg points
192
+ // correct turn not found
193
+ }
194
+
195
+ } else {
196
+ // found a left turn
197
+ // discard last hull point (add to insidePoints)
198
+ //insidePoints.unshift(lastHullPoint);
199
+ insidePoints[lastHullPointRecord[0] + '@@' + lastHullPointRecord[1]] = lastHullPoint;
200
+ // reenter second-to-last hull point (will be last at next iter of loop)
201
+ hullPointRecords.push(secondLastHullPointRecord);
202
+ // do not do anything with current point
203
+ // correct turn not found
204
+ }
205
+ }
206
+ }
207
+ }
208
+ // at this point, hullPointRecords contains the output points in clockwise order
209
+ // the points start with lowest-y,highest-x startPoint, and end at the same point
210
+
211
+ // step 3b: remove duplicated startPointRecord from the end of the array
212
+ if (hullPointRecords.length > 2) {
213
+ hullPointRecords.pop();
214
+ }
215
+
216
+ // step 4: find the lowest originalIndex record and put it at the beginning of hull
217
+ var lowestHullIndex; // the lowest originalIndex on the hull
218
+ var indexOfLowestHullIndexRecord = -1; // the index of the record with lowestHullIndex
219
+ n = hullPointRecords.length;
220
+ for (i = 0; i < n; i++) {
221
+
222
+ var currentHullIndex = hullPointRecords[i][1];
223
+
224
+ if (lowestHullIndex === undefined || currentHullIndex < lowestHullIndex) {
225
+ lowestHullIndex = currentHullIndex;
226
+ indexOfLowestHullIndexRecord = i;
227
+ }
228
+ }
229
+
230
+ var hullPointRecordsReordered = [];
231
+ if (indexOfLowestHullIndexRecord > 0) {
232
+ var newFirstChunk = hullPointRecords.slice(indexOfLowestHullIndexRecord);
233
+ var newSecondChunk = hullPointRecords.slice(0, indexOfLowestHullIndexRecord);
234
+ hullPointRecordsReordered = newFirstChunk.concat(newSecondChunk);
235
+
236
+ } else {
237
+ hullPointRecordsReordered = hullPointRecords;
238
+ }
239
+
240
+ var hullPoints = [];
241
+ n = hullPointRecordsReordered.length;
242
+ for (i = 0; i < n; i++) {
243
+ hullPoints.push(hullPointRecordsReordered[i][0]);
244
+ }
245
+
246
+ return hullPoints;
247
+ }
@@ -0,0 +1,51 @@
1
+ import { Point } from './point.mjs';
2
+ import { Polyline } from './polyline.mjs';
3
+ import { extend } from './extend.mjs';
4
+ import { types } from './types.mjs';
5
+ import { clonePoints, parsePoints, convexHull } from './points.mjs';
6
+
7
+ export const Polygon = function(points) {
8
+
9
+ if (!(this instanceof Polygon)) {
10
+ return new Polygon(points);
11
+ }
12
+
13
+ if (typeof points === 'string') {
14
+ return new Polygon.parse(points);
15
+ }
16
+
17
+ this.points = (Array.isArray(points) ? points.map(Point) : []);
18
+ };
19
+
20
+ Polygon.parse = function(svgString) {
21
+ return new Polygon(parsePoints(svgString));
22
+ };
23
+
24
+ Polygon.fromRect = function(rect) {
25
+ return new Polygon([
26
+ rect.topLeft(),
27
+ rect.topRight(),
28
+ rect.bottomRight(),
29
+ rect.bottomLeft()
30
+ ]);
31
+ };
32
+
33
+ Polygon.prototype = extend(Polyline.prototype, {
34
+
35
+ type: types.Polygon,
36
+
37
+ clone: function() {
38
+ return new Polygon(clonePoints(this.points));
39
+ },
40
+
41
+ convexHull: function() {
42
+ return new Polygon(convexHull(this.points));
43
+ },
44
+
45
+ lengthPoints: function() {
46
+ const { start, end, points } = this;
47
+ if (points.length <= 1 || start.equals(end)) return points;
48
+ return [...points, start.clone()];
49
+ }
50
+
51
+ });