plotboilerplate 1.23.3 → 1.24.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 (159) hide show
  1. package/README.md +6 -0
  2. package/dist/index.esm.js +1390 -265
  3. package/dist/index.esm.js.map +1 -1
  4. package/dist/index.esm.min.js +1 -1
  5. package/dist/index.esm.min.js.map +1 -1
  6. package/dist/plotboilerplate.browser.min.js +1 -1
  7. package/dist/plotboilerplate.browser.min.js.map +1 -1
  8. package/dist/plotboilerplate.js +1264 -99
  9. package/dist/plotboilerplate.min.js +1 -1
  10. package/package.json +1 -1
  11. package/src/cjs/BezierPath.d.ts +41 -5
  12. package/src/cjs/BezierPath.js +52 -2
  13. package/src/cjs/BezierPath.js.map +1 -1
  14. package/src/cjs/Bounds.d.ts +67 -3
  15. package/src/cjs/Bounds.js +101 -3
  16. package/src/cjs/Bounds.js.map +1 -1
  17. package/src/cjs/Circle.d.ts +46 -4
  18. package/src/cjs/Circle.js +82 -3
  19. package/src/cjs/Circle.js.map +1 -1
  20. package/src/cjs/CircleSector.d.ts +51 -2
  21. package/src/cjs/CircleSector.js +112 -3
  22. package/src/cjs/CircleSector.js.map +1 -1
  23. package/src/cjs/CubicBezierCurve.d.ts +113 -7
  24. package/src/cjs/CubicBezierCurve.js +281 -33
  25. package/src/cjs/CubicBezierCurve.js.map +1 -1
  26. package/src/cjs/Line.d.ts +38 -3
  27. package/src/cjs/Line.js +64 -1
  28. package/src/cjs/Line.js.map +1 -1
  29. package/src/cjs/PlotBoilerplate.js +2 -9
  30. package/src/cjs/PlotBoilerplate.js.map +1 -1
  31. package/src/cjs/Polygon.d.ts +48 -4
  32. package/src/cjs/Polygon.js +91 -16
  33. package/src/cjs/Polygon.js.map +1 -1
  34. package/src/cjs/Triangle.d.ts +52 -3
  35. package/src/cjs/Triangle.js +80 -1
  36. package/src/cjs/Triangle.js.map +1 -1
  37. package/src/cjs/VEllipse.d.ts +62 -3
  38. package/src/cjs/VEllipse.js +181 -17
  39. package/src/cjs/VEllipse.js.map +1 -1
  40. package/src/cjs/VEllipseSector.d.ts +86 -2
  41. package/src/cjs/VEllipseSector.js +148 -4
  42. package/src/cjs/VEllipseSector.js.map +1 -1
  43. package/src/cjs/Vector.d.ts +12 -2
  44. package/src/cjs/Vector.js +15 -2
  45. package/src/cjs/Vector.js.map +1 -1
  46. package/src/cjs/VertTuple.d.ts +28 -4
  47. package/src/cjs/VertTuple.js +36 -3
  48. package/src/cjs/VertTuple.js.map +1 -1
  49. package/src/cjs/Vertex.d.ts +17 -2
  50. package/src/cjs/Vertex.js +18 -1
  51. package/src/cjs/Vertex.js.map +1 -1
  52. package/src/cjs/interfaces/core.d.ts +39 -0
  53. package/src/cjs/interfaces/core.js +2 -0
  54. package/src/cjs/interfaces/core.js.map +1 -1
  55. package/src/cjs/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
  56. package/src/cjs/utils/algorithms/convexPolygonIncircle.js +14 -6
  57. package/src/cjs/utils/algorithms/convexPolygonIncircle.js.map +1 -1
  58. package/src/cjs/utils/createRandomizedPolygon.d.ts +14 -0
  59. package/src/cjs/utils/createRandomizedPolygon.js +32 -0
  60. package/src/cjs/utils/createRandomizedPolygon.js.map +1 -0
  61. package/src/cjs/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
  62. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js +37 -23
  63. package/src/cjs/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  64. package/src/cjs/utils/helpers/CircleSectorHelper.d.ts +10 -1
  65. package/src/cjs/utils/helpers/CircleSectorHelper.js +19 -20
  66. package/src/cjs/utils/helpers/CircleSectorHelper.js.map +1 -1
  67. package/src/cjs/utils/helpers/VEllipseHelper.d.ts +30 -0
  68. package/src/cjs/utils/helpers/VEllipseHelper.js +86 -0
  69. package/src/cjs/utils/helpers/VEllipseHelper.js.map +1 -0
  70. package/src/cjs/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
  71. package/src/cjs/utils/helpers/VEllipseSectorHelper.js +107 -25
  72. package/src/cjs/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  73. package/src/createDemoDrawables.js +3 -1
  74. package/src/esm/BezierPath.d.ts +41 -5
  75. package/src/esm/BezierPath.js +50 -2
  76. package/src/esm/BezierPath.js.map +1 -1
  77. package/src/esm/Bounds.d.ts +67 -3
  78. package/src/esm/Bounds.js +101 -3
  79. package/src/esm/Bounds.js.map +1 -1
  80. package/src/esm/Circle.d.ts +46 -4
  81. package/src/esm/Circle.js +79 -3
  82. package/src/esm/Circle.js.map +1 -1
  83. package/src/esm/CircleSector.d.ts +51 -2
  84. package/src/esm/CircleSector.js +108 -3
  85. package/src/esm/CircleSector.js.map +1 -1
  86. package/src/esm/CubicBezierCurve.d.ts +113 -7
  87. package/src/esm/CubicBezierCurve.js +277 -33
  88. package/src/esm/CubicBezierCurve.js.map +1 -1
  89. package/src/esm/Line.d.ts +38 -3
  90. package/src/esm/Line.js +62 -1
  91. package/src/esm/Line.js.map +1 -1
  92. package/src/esm/PlotBoilerplate.js +2 -9
  93. package/src/esm/PlotBoilerplate.js.map +1 -1
  94. package/src/esm/Polygon.d.ts +48 -4
  95. package/src/esm/Polygon.js +89 -16
  96. package/src/esm/Polygon.js.map +1 -1
  97. package/src/esm/Triangle.d.ts +52 -3
  98. package/src/esm/Triangle.js +77 -1
  99. package/src/esm/Triangle.js.map +1 -1
  100. package/src/esm/VEllipse.d.ts +62 -3
  101. package/src/esm/VEllipse.js +178 -17
  102. package/src/esm/VEllipse.js.map +1 -1
  103. package/src/esm/VEllipseSector.d.ts +86 -2
  104. package/src/esm/VEllipseSector.js +146 -7
  105. package/src/esm/VEllipseSector.js.map +1 -1
  106. package/src/esm/Vector.d.ts +12 -2
  107. package/src/esm/Vector.js +15 -2
  108. package/src/esm/Vector.js.map +1 -1
  109. package/src/esm/VertTuple.d.ts +28 -4
  110. package/src/esm/VertTuple.js +36 -3
  111. package/src/esm/VertTuple.js.map +1 -1
  112. package/src/esm/Vertex.d.ts +17 -2
  113. package/src/esm/Vertex.js +18 -1
  114. package/src/esm/Vertex.js.map +1 -1
  115. package/src/esm/interfaces/core.d.ts +39 -0
  116. package/src/esm/interfaces/core.js +2 -0
  117. package/src/esm/interfaces/core.js.map +1 -1
  118. package/src/esm/utils/algorithms/convexPolygonIncircle.d.ts +2 -1
  119. package/src/esm/utils/algorithms/convexPolygonIncircle.js +15 -7
  120. package/src/esm/utils/algorithms/convexPolygonIncircle.js.map +1 -1
  121. package/src/esm/utils/algorithms/createRandomizedPolygon.d.ts +14 -0
  122. package/src/esm/utils/algorithms/createRandomizedPolygon.js +28 -0
  123. package/src/esm/utils/algorithms/createRandomizedPolygon.js.map +1 -0
  124. package/src/esm/utils/createRandomizedPolygon.d.ts +14 -0
  125. package/src/esm/utils/createRandomizedPolygon.js +28 -0
  126. package/src/esm/utils/createRandomizedPolygon.js.map +1 -0
  127. package/src/esm/utils/helpers/BezierPathInteractionHelper.d.ts +9 -7
  128. package/src/esm/utils/helpers/BezierPathInteractionHelper.js +35 -22
  129. package/src/esm/utils/helpers/BezierPathInteractionHelper.js.map +1 -1
  130. package/src/esm/utils/helpers/CircleSectorHelper.d.ts +10 -1
  131. package/src/esm/utils/helpers/CircleSectorHelper.js +19 -20
  132. package/src/esm/utils/helpers/CircleSectorHelper.js.map +1 -1
  133. package/src/esm/utils/helpers/VEllipseHelper.d.ts +30 -0
  134. package/src/esm/utils/helpers/VEllipseHelper.js +81 -0
  135. package/src/esm/utils/helpers/VEllipseHelper.js.map +1 -0
  136. package/src/esm/utils/helpers/VEllipseSectorHelper.d.ts +42 -3
  137. package/src/esm/utils/helpers/VEllipseSectorHelper.js +106 -24
  138. package/src/esm/utils/helpers/VEllipseSectorHelper.js.map +1 -1
  139. package/src/ts/BezierPath.ts +58 -5
  140. package/src/ts/Bounds.ts +110 -6
  141. package/src/ts/Circle.ts +87 -5
  142. package/src/ts/CircleSector.ts +115 -5
  143. package/src/ts/CubicBezierCurve.ts +332 -42
  144. package/src/ts/Line.ts +66 -3
  145. package/src/ts/PlotBoilerplate.ts +3 -9
  146. package/src/ts/Polygon.ts +103 -22
  147. package/src/ts/Triangle.ts +85 -3
  148. package/src/ts/VEllipse.ts +214 -19
  149. package/src/ts/VEllipseSector.ts +154 -8
  150. package/src/ts/Vector.ts +16 -2
  151. package/src/ts/VertTuple.ts +39 -4
  152. package/src/ts/Vertex.ts +20 -2
  153. package/src/ts/interfaces/core.ts +42 -0
  154. package/src/ts/utils/algorithms/convexPolygonIncircle.ts +82 -78
  155. package/src/ts/utils/createRandomizedPolygon.ts +31 -0
  156. package/src/ts/utils/helpers/BezierPathInteractionHelper.ts +45 -29
  157. package/src/ts/utils/helpers/CircleSectorHelper.ts +115 -125
  158. package/src/ts/utils/helpers/VEllipseHelper.ts +113 -0
  159. package/src/ts/utils/helpers/VEllipseSectorHelper.ts +142 -25
@@ -15,7 +15,11 @@
15
15
  * @modified 2021-03-19 Added the `VEllipse.rotate` function.
16
16
  * @modified 2022-02-02 Added the `destroy` method.
17
17
  * @modified 2022-02-02 Cleared the `VEllipse.toSVGString` function (deprecated). Use `drawutilssvg` instead.
18
- * @version 1.3.0
18
+ * @modified 2025-03-31 ATTENTION: modified the winding direction of the `tangentAt` method to match with the Circle method. This is a breaking change!
19
+ * @modified 2025-03-31 Adding the `VEllipse.move(amount: XYCoords)` method.
20
+ * @modified 2025-04-19 Adding the `VEllipse.getBounds()` method.
21
+ * @modified 2025-04-24 Adding the `VEllipse.getExtremePoints()` method for calculating minima and maxima.
22
+ * @version 1.4.0
19
23
  *
20
24
  * @file VEllipse
21
25
  * @fileoverview Ellipses with a center and an x- and a y-axis (stored as a vertex).
@@ -25,8 +29,11 @@ import { Line } from "./Line";
25
29
  import { Vector } from "./Vector";
26
30
  import { Vertex } from "./Vertex";
27
31
  import { UIDGenerator } from "./UIDGenerator";
28
- import { SVGSerializable, UID, XYCoords } from "./interfaces";
32
+ import { IBounded, Intersectable, SVGSerializable, UID, XYCoords } from "./interfaces";
29
33
  import { CubicBezierCurve } from "./CubicBezierCurve";
34
+ import { VertTuple } from "./VertTuple";
35
+ import { Circle } from "./Circle";
36
+ import { Bounds } from "./Bounds";
30
37
 
31
38
  /**
32
39
  * @classdesc An ellipse class based on two vertices [centerX,centerY] and [radiusX,radiusY].
@@ -36,7 +43,7 @@ import { CubicBezierCurve } from "./CubicBezierCurve";
36
43
  * @requires UIDGenerator
37
44
  * @requires Vertex
38
45
  */
39
- export class VEllipse implements SVGSerializable {
46
+ export class VEllipse implements IBounded, Intersectable, SVGSerializable {
40
47
  /**
41
48
  * Required to generate proper CSS classes and other class related IDs.
42
49
  **/
@@ -154,12 +161,119 @@ export class VEllipse implements SVGSerializable {
154
161
  * @return {number} The signed vertical radius of this ellipse.
155
162
  */
156
163
  signedRadiusV(): number {
157
- // return Math.abs(this.axis.y - this.center.y);
158
164
  // Rotate axis back to origin before calculating radius
159
- // return Math.abs(new Vertex(this.axis).rotate(-this.rotation,this.center).y - this.center.y);
160
165
  return new Vertex(this.axis).rotate(-this.rotation, this.center).y - this.center.y;
161
166
  }
162
167
 
168
+ /**
169
+ * Get the the minima and maxima (points) of this (rotated) ellipse.
170
+ *
171
+ * @method getExtremePoints
172
+ * @instance
173
+ * @memberof VEllipse
174
+ * @return {[Vertex, Vertex, Vertex, Vertex]} Get the the minima and maxima (points) of this (rotated) ellipse.
175
+ */
176
+ getExtremePoints(): [Vertex, Vertex, Vertex, Vertex] {
177
+ const a: number = this.radiusH();
178
+ const b: number = this.radiusV();
179
+
180
+ // Calculate t_x values
181
+ const t_x1: number = Math.atan2(-b * Math.sin(this.rotation), a * Math.cos(this.rotation));
182
+ const t_x2: number = t_x1 + Math.PI;
183
+
184
+ // Calculate x values at t_x
185
+ const x_x1: number =
186
+ this.center.x + a * Math.cos(t_x1) * Math.cos(this.rotation) - b * Math.sin(t_x1) * Math.sin(this.rotation);
187
+ const y_x1: number =
188
+ this.center.y + a * Math.cos(t_x1) * Math.sin(this.rotation) + b * Math.sin(t_x1) * Math.cos(this.rotation);
189
+ const x_x2: number =
190
+ this.center.x + a * Math.cos(t_x2) * Math.cos(this.rotation) - b * Math.sin(t_x2) * Math.sin(this.rotation);
191
+ const y_x2: number =
192
+ this.center.y + a * Math.cos(t_x2) * Math.sin(this.rotation) + b * Math.sin(t_x2) * Math.cos(this.rotation);
193
+
194
+ let x_max: Vertex, x_min: Vertex;
195
+ if (x_x1 > x_x2) {
196
+ x_max = new Vertex(x_x1, y_x1);
197
+ x_min = new Vertex(x_x2, y_x2);
198
+ } else {
199
+ x_max = new Vertex(x_x2, y_x2);
200
+ x_min = new Vertex(x_x1, y_x1);
201
+ }
202
+
203
+ // Calculate t_y values
204
+ const t_y1: number = Math.atan2(b * Math.cos(this.rotation), a * Math.sin(this.rotation));
205
+ const t_y2: number = t_y1 + Math.PI;
206
+
207
+ // Calculate y values at t_y
208
+ const x_y1: number =
209
+ this.center.x + a * Math.cos(t_y1) * Math.cos(this.rotation) - b * Math.sin(t_y1) * Math.sin(this.rotation);
210
+ const y_y1: number =
211
+ this.center.y + a * Math.cos(t_y1) * Math.sin(this.rotation) + b * Math.sin(t_y1) * Math.cos(this.rotation);
212
+ const x_y2: number =
213
+ this.center.x + a * Math.cos(t_y2) * Math.cos(this.rotation) - b * Math.sin(t_y2) * Math.sin(this.rotation);
214
+ const y_y2: number =
215
+ this.center.y + a * Math.cos(t_y2) * Math.sin(this.rotation) + b * Math.sin(t_y2) * Math.cos(this.rotation);
216
+
217
+ let y_max: Vertex, y_min: Vertex;
218
+ if (y_y1 > y_y2) {
219
+ y_max = new Vertex(x_y1, y_y1);
220
+ y_min = new Vertex(x_y2, y_y2);
221
+ } else {
222
+ y_max = new Vertex(x_y2, y_y2);
223
+ y_min = new Vertex(x_y1, y_y1);
224
+ }
225
+
226
+ return [x_max, x_min, y_max, y_min];
227
+ }
228
+
229
+ //--- BEGIN --- Implement interface `IBounded`
230
+ /**
231
+ * Get the bounds of this ellipse.
232
+ *
233
+ * The bounds are approximated by the underlying segment buffer; the more segment there are,
234
+ * the more accurate will be the returned bounds.
235
+ *
236
+ * @method getBounds
237
+ * @instance
238
+ * @memberof VEllipse
239
+ * @return {Bounds} The bounds of this ellipse.
240
+ **/
241
+ getBounds(): Bounds {
242
+ // Thanks to Cuixiping
243
+ // https://stackoverflow.com/questions/87734/how-do-you-calculate-the-axis-aligned-bounding-box-of-an-ellipse
244
+ const r1: number = this.radiusH();
245
+ const r2: number = this.radiusV();
246
+
247
+ const ux: number = r1 * Math.cos(this.rotation);
248
+ const uy: number = r1 * Math.sin(this.rotation);
249
+ const vx: number = r2 * Math.cos(this.rotation + Math.PI / 2);
250
+ const vy: number = r2 * Math.sin(this.rotation + Math.PI / 2);
251
+
252
+ const bbox_halfwidth = Math.sqrt(ux * ux + vx * vx);
253
+ const bbox_halfheight = Math.sqrt(uy * uy + vy * vy);
254
+
255
+ return new Bounds(
256
+ { x: this.center.x - bbox_halfwidth, y: this.center.y - bbox_halfheight },
257
+ { x: this.center.x + bbox_halfwidth, y: this.center.y + bbox_halfheight }
258
+ );
259
+ }
260
+ //--- BEGIN --- Implement interface `IBounded`
261
+
262
+ /**
263
+ * Move the ellipse by the given amount. This is equivalent by moving the `center` and `axis` points.
264
+ *
265
+ * @method move
266
+ * @param {XYCoords} amount - The amount to move.
267
+ * @instance
268
+ * @memberof VEllipse
269
+ * @return {VEllipse} this for chaining
270
+ **/
271
+ move(amount: XYCoords): VEllipse {
272
+ this.center.add(amount);
273
+ this.axis.add(amount);
274
+ return this;
275
+ }
276
+
163
277
  /**
164
278
  * Scale this ellipse by the given factor from the center point. The factor will be applied to both radii.
165
279
  *
@@ -222,9 +336,8 @@ export class VEllipse implements SVGSerializable {
222
336
  * @param {number} angle - The angle to get the normal vector at.
223
337
  * @param {number=1.0} length - [optional, default=1] The length of the returned vector.
224
338
  */
225
-
226
339
  normalAt(angle: number, length?: number): Vector {
227
- const point: Vertex = this.vertAt(angle);
340
+ const point: Vertex = this.vertAt(angle - this.rotation); // HERE IS THE CORRECT BEHAVIOR!
228
341
  const foci: [Vertex, Vertex] = this.getFoci();
229
342
  // Calculate the angle between [point,focusA] and [point,focusB]
230
343
  const angleA: number = new Line(point, foci[0]).angle();
@@ -236,11 +349,14 @@ export class VEllipse implements SVGSerializable {
236
349
  .addX(50)
237
350
  .clone()
238
351
  .rotate(Math.PI + centerAngle, point);
239
- if (this.center.distance(endPointA) < this.center.distance(endPointB)) {
240
- return new Vector(point, endPointB);
241
- } else {
242
- return new Vector(point, endPointA);
352
+ const resultVector: Vector =
353
+ this.center.distance(endPointA) < this.center.distance(endPointB)
354
+ ? new Vector(point, endPointB)
355
+ : new Vector(point, endPointA);
356
+ if (typeof length === "number") {
357
+ resultVector.setLength(length);
243
358
  }
359
+ return resultVector;
244
360
  }
245
361
 
246
362
  /**
@@ -260,9 +376,7 @@ export class VEllipse implements SVGSerializable {
260
376
  */
261
377
  tangentAt(angle: number, length?: number): Vector {
262
378
  const normal: Vector = this.normalAt(angle, length);
263
- // Rotate the normal by 90 degrees, then it is the tangent.
264
- normal.b.rotate(Math.PI / 2, normal.a);
265
- return normal;
379
+ return normal.inv().perp();
266
380
  }
267
381
 
268
382
  /**
@@ -315,6 +429,8 @@ export class VEllipse implements SVGSerializable {
315
429
  /**
316
430
  * Get equally distributed points on the outline of this ellipse.
317
431
  *
432
+ * @method getEquidistantVertices
433
+ * @instance
318
434
  * @param {number} pointCount - The number of points.
319
435
  * @returns {Array<Vertex>}
320
436
  */
@@ -327,6 +443,85 @@ export class VEllipse implements SVGSerializable {
327
443
  return result;
328
444
  }
329
445
 
446
+ //--- BEGIN --- Implement interface `Intersectable`
447
+ /**
448
+ * Get the line intersections as vectors with this ellipse.
449
+ *
450
+ * @method lineIntersections
451
+ * @instance
452
+ * @param {VertTuple<Vector> ray - The line/ray to intersect this ellipse with.
453
+ * @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
454
+ * @returns
455
+ */
456
+ lineIntersections(ray: VertTuple<Vector>, inVectorBoundsOnly: boolean = false): Array<Vertex> {
457
+ // Question: what happens to extreme versions when ellipse is a line (width or height is zero)?
458
+ // This would result in a Division_by_Zero exception!
459
+
460
+ // Step A: create clones for operations (keep originals unchanged)
461
+ const ellipseCopy: VEllipse = this.clone(); // VEllipse
462
+ const rayCopy: VertTuple<Vector> = ray.clone(); // Vector
463
+
464
+ // Step B: move both so ellipse's center is located at (0,0)
465
+ const moveAmount: Vertex = ellipseCopy.center.clone().inv();
466
+ ellipseCopy.move(moveAmount);
467
+ rayCopy.add(moveAmount);
468
+
469
+ // Step C: rotate eclipse backwards it's rotation, so that rotation is zero (0.0).
470
+ // Rotate together with ray!
471
+ const rotationAmount: number = -ellipseCopy.rotation;
472
+ ellipseCopy.rotate(rotationAmount); // Rotation around (0,0) = center of translated ellipse
473
+ rayCopy.a.rotate(rotationAmount, ellipseCopy.center);
474
+ rayCopy.b.rotate(rotationAmount, ellipseCopy.center);
475
+
476
+ // Step D: find x/y factors to use for scaling to transform the ellipse to a circle.
477
+ // Scale together with vector ray.
478
+ const radiusH: number = ellipseCopy.radiusH();
479
+ const radiusV: number = ellipseCopy.radiusV();
480
+ const scalingFactors: XYCoords = radiusH > radiusV ? { x: radiusV / radiusH, y: 1.0 } : { x: 1.0, y: radiusH / radiusV };
481
+
482
+ // Step E: scale ellipse AND ray by calculated factors.
483
+ ellipseCopy.axis.scaleXY(scalingFactors);
484
+ rayCopy.a.scaleXY(scalingFactors);
485
+ rayCopy.b.scaleXY(scalingFactors);
486
+
487
+ // Intermediate result: now the ellipse is transformed to a circle and we can calculate intersections :)
488
+ // Step F: calculate circle+line intersecions
489
+ const tmpCircle: Circle = new Circle(new Vertex(), ellipseCopy.radiusH()); // radiusH() === radiusV()
490
+ const intersections: Vertex[] = tmpCircle.lineIntersections(rayCopy, inVectorBoundsOnly);
491
+
492
+ // Step G: transform intersecions back to original configuration
493
+ intersections.forEach(function (intersectionPoint) {
494
+ // Reverse transformation from above.
495
+ intersectionPoint.scaleXY({ x: 1 / scalingFactors.x, y: 1 / scalingFactors.y }, ellipseCopy.center);
496
+ intersectionPoint.rotate(-rotationAmount, ellipseCopy.center);
497
+ intersectionPoint.sub(moveAmount);
498
+ });
499
+
500
+ return intersections;
501
+ }
502
+
503
+ /**
504
+ * Get all line intersections of this polygon and their tangents along the shape.
505
+ *
506
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
507
+ *
508
+ * @param line
509
+ * @param lineIntersectionTangents
510
+ * @returns
511
+ */
512
+ lineIntersectionTangents(line: VertTuple<any>, inVectorBoundsOnly: boolean = false): Array<Vector> {
513
+ // Find the intersections of all lines plus their tangents inside the circle bounds
514
+ const interSectionPoints: Array<Vertex> = this.lineIntersections(line, inVectorBoundsOnly);
515
+ return interSectionPoints.map((vert: Vertex) => {
516
+ // Calculate angle
517
+ const lineFromCenter = new Line(this.center, vert);
518
+ const angle: number = lineFromCenter.angle();
519
+ // Calculate tangent at angle
520
+ return this.tangentAt(angle);
521
+ });
522
+ }
523
+ //--- END --- Implement interface `Intersectable`
524
+
330
525
  /**
331
526
  * Convert this ellipse into cubic Bézier curves.
332
527
  *
@@ -340,7 +535,7 @@ export class VEllipse implements SVGSerializable {
340
535
  // http://www.spaceroots.org/documents/ellipse/node22.html
341
536
 
342
537
  // Note that ellipses with radiusH=0 or radiusV=0 cannot be represented as Bézier curves.
343
- // Return a single line here (as a Bézier curve)
538
+ // Return a single line here (as a Bézier curve)?
344
539
  // if (Math.abs(this.radiusV()) < 0.00001) {
345
540
  // const radiusH = this.radiusH();
346
541
  // return [
@@ -372,10 +567,10 @@ export class VEllipse implements SVGSerializable {
372
567
 
373
568
  const curves: Array<CubicBezierCurve> = [];
374
569
  const angles: Array<number> = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, segmentCount);
375
- let curAngle: number = angles[0];
570
+ let curAngle: number = angles[0] + this.rotation;
376
571
  let startPoint: Vertex = this.vertAt(curAngle);
377
572
  for (var i = 0; i < angles.length; i++) {
378
- let nextAngle = angles[(i + 1) % angles.length];
573
+ let nextAngle = angles[(i + 1) % angles.length] + this.rotation;
379
574
  let endPoint: Vertex = this.vertAt(nextAngle);
380
575
 
381
576
  if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
@@ -389,8 +584,8 @@ export class VEllipse implements SVGSerializable {
389
584
  );
390
585
  curves.push(curve);
391
586
  } else {
392
- let startTangent: Vector = this.tangentAt(curAngle);
393
- let endTangent: Vector = this.tangentAt(nextAngle);
587
+ let startTangent: Vector = this.tangentAt(curAngle + this.rotation);
588
+ let endTangent: Vector = this.tangentAt(nextAngle + this.rotation);
394
589
 
395
590
  // Find intersection (ignore that the result might be null in some extreme cases)
396
591
  let intersection: Vertex = startTangent.intersection(endTangent) as Vertex;
@@ -6,9 +6,17 @@
6
6
  * @date 2021-02-26
7
7
  * @modified 2022-02-02 Added the `destroy` method.
8
8
  * @modified 2022-11-01 Tweaked the `endpointToCenterParameters` function to handle negative values, too, without errors.
9
- * @version 1.1.1
9
+ * @modified 2025-04-01 Adapting a the `toCubicBezier` calculation to match an underlying change in the vertAt and tangentAt calculation of ellipses (was required to hamonize both methods with circles).
10
+ * @modified 2025-04-02 Adding `VEllipseSector.containsAngle` method.
11
+ * @modified 2025-04-02 Adding `VEllipseSector.lineIntersections` and `VEllipseSector.lineIntersectionTangents` and implementing `Intersectable`.
12
+ * @modified 2025-04-07 Adding value wrapping (0 to TWO_PI) to the `VEllipseSector.containsAngle` method.
13
+ * @modified 2025-04-09 Adding the `VEllipseSector.move` method.
14
+ * @modified 2025-04-19 Added the `VEllipseSector.getStartPoint` and `getEndPoint` methods.
15
+ * @modified 2025-04-23 Added the `VEllipseSector.getBounds` method.
16
+ * @version 1.2.0
10
17
  */
11
18
 
19
+ import { Bounds } from "./Bounds";
12
20
  import { CubicBezierCurve } from "./CubicBezierCurve";
13
21
  import { geomutils } from "./geomutils";
14
22
  import { SVGPathParams, UID, XYCoords } from "./interfaces";
@@ -17,6 +25,7 @@ import { UIDGenerator } from "./UIDGenerator";
17
25
  import { Vector } from "./Vector";
18
26
  import { VEllipse } from "./VEllipse";
19
27
  import { Vertex } from "./Vertex";
28
+ import { VertTuple } from "./VertTuple";
20
29
 
21
30
  /**
22
31
  * @classdesc A class for elliptic sectors.
@@ -93,6 +102,143 @@ export class VEllipseSector {
93
102
  this.endAngle = geomutils.wrapMinMax(endAngle, 0, Math.PI * 2);
94
103
  }
95
104
 
105
+ /**
106
+ * Move the ellipse sector by the given amount.
107
+ *
108
+ * @method move
109
+ * @param {XYCoords} amount - The amount to move.
110
+ * @instance
111
+ * @memberof VEllipseSector
112
+ * @return {VEllipseSector} this for chaining
113
+ **/
114
+ move(amount: XYCoords): VEllipseSector {
115
+ this.ellipse.move(amount);
116
+ return this;
117
+ }
118
+
119
+ /**
120
+ * Checks wether the given angle (must be inside 0 and PI*2) is contained inside this sector.
121
+ *
122
+ * @param {number} angle - The numeric angle to check.
123
+ * @method containsAngle
124
+ * @instance
125
+ * @memberof VEllipseSectpr
126
+ * @return {boolean} True if (and only if) this sector contains the given angle.
127
+ */
128
+ containsAngle(angle: number) {
129
+ angle = geomutils.mapAngleTo2PI(angle); // wrapMinMax(angle, 0, Math.PI * 2);
130
+ const sAngle: number = geomutils.mapAngleTo2PI(this.startAngle);
131
+ const eAngle: number = geomutils.mapAngleTo2PI(this.endAngle);
132
+ // TODO: cleanup
133
+ // if (this.startAngle <= this.endAngle) {
134
+ // return angle >= this.startAngle && angle < this.endAngle;
135
+ // } else {
136
+ // // startAngle > endAngle
137
+ // return angle >= this.startAngle || angle < this.endAngle;
138
+ // }
139
+ if (sAngle <= eAngle) {
140
+ return angle >= sAngle && angle < eAngle;
141
+ } else {
142
+ // startAngle > endAngle
143
+ return angle >= sAngle || angle < eAngle;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Get the sectors starting point (on the underlying ellipse, located at the start angle).
149
+ *
150
+ * @method getStartPoint
151
+ * @instance
152
+ * @memberof VEllipseSector
153
+ * @return {Vertex} The sector's stating point.
154
+ */
155
+ getStartPoint(): Vertex {
156
+ return this.ellipse.vertAt(this.startAngle);
157
+ }
158
+
159
+ /**
160
+ * Get the sectors ending point (on the underlying ellipse, located at the end angle).
161
+ *
162
+ * @method getEndPoint
163
+ * @instance
164
+ * @memberof VEllipseSector
165
+ * @return {Vertex} The sector's ending point.
166
+ */
167
+ getEndPoint(): Vertex {
168
+ return this.ellipse.vertAt(this.endAngle);
169
+ }
170
+
171
+ //--- BEGIN --- Implement interface `IBounded`
172
+ /**
173
+ * Get the bounds of this elliptic sector.
174
+ *
175
+ * The bounds are approximated by the underlying segment buffer; the more segment there are,
176
+ * the more accurate will be the returned bounds.
177
+ *
178
+ * @method getBounds
179
+ * @instance
180
+ * @memberof VEllipse
181
+ * @return {Bounds} The bounds of this elliptic sector.
182
+ **/
183
+ getBounds(): Bounds {
184
+ // Calculage angles from east, west, north and south box points and check if they are inside
185
+ const extremes = this.ellipse.getExtremePoints();
186
+ const candidates: Array<Vertex> = extremes.filter(point => {
187
+ const angle: number = new Line(this.ellipse.center, point).angle() - this.ellipse.rotation;
188
+ return this.containsAngle(angle);
189
+ });
190
+ return Bounds.computeFromVertices([this.getStartPoint(), this.getEndPoint()].concat(candidates));
191
+ }
192
+
193
+ //--- BEGIN --- Implement interface `Intersectable`
194
+ /**
195
+ * Get the line intersections as vectors with this ellipse.
196
+ *
197
+ * @method lineIntersections
198
+ * @instance
199
+ * @memberof VEllipseSectpr
200
+ * @param {VertTuple<Vector>} ray - The line/ray to intersect this ellipse with.
201
+ * @param {boolean} inVectorBoundsOnly - (default=false) Set to true if only intersections within the vector bounds are of interest.
202
+ * @returns
203
+ */
204
+ lineIntersections(ray: VertTuple<Vector>, inVectorBoundsOnly: boolean = false): Array<Vertex> {
205
+ // First get all line intersections from underlying ellipse.
206
+ const ellipseIntersections: Array<Vertex> = this.ellipse.lineIntersections(ray, inVectorBoundsOnly);
207
+ // Drop all intersection points that are not contained in the circle sectors bounds.
208
+ const tmpLine = new Line(this.ellipse.center, new Vertex());
209
+ return ellipseIntersections.filter((intersectionPoint: Vertex) => {
210
+ tmpLine.b.set(intersectionPoint);
211
+ const lineAngle = tmpLine.angle();
212
+ return this.containsAngle(lineAngle - this.ellipse.rotation);
213
+ });
214
+ }
215
+
216
+ /**
217
+ * Get all line intersections of this polygon and their tangents along the shape.
218
+ *
219
+ * This method returns all intersection tangents (as vectors) with this shape. The returned array of vectors is in no specific order.
220
+ *
221
+ * @method lineIntersections
222
+ * @memberof VEllipseSectpr
223
+ * @param line
224
+ * @param lineIntersectionTangents
225
+ * @returns
226
+ */
227
+ lineIntersectionTangents(line: VertTuple<any>, inVectorBoundsOnly: boolean = false): Array<Vector> {
228
+ // Find the intersections of all lines plus their tangents inside the circle bounds
229
+ const interSectionPoints: Array<Vertex> = this.lineIntersections(line, inVectorBoundsOnly);
230
+ return interSectionPoints.map((vert: Vertex) => {
231
+ // Calculate angle
232
+ const lineFromCenter = new Line(this.ellipse.center, vert);
233
+ const angle: number = lineFromCenter.angle();
234
+ // console.log("angle", (angle / Math.PI) * 180.0);
235
+ // const angle = Math.random() * Math.PI * 2; // TODO
236
+ // Calculate tangent at angle
237
+ return this.ellipse.tangentAt(angle);
238
+ });
239
+ }
240
+ //--- END --- Implement interface `Intersectable`
241
+
96
242
  /**
97
243
  * Convert this elliptic sector into cubic Bézier curves.
98
244
  *
@@ -125,8 +271,8 @@ export class VEllipseSector {
125
271
  let nextAngle = angles[(i + 1) % angles.length];
126
272
  let endPoint: Vertex = this.ellipse.vertAt(nextAngle);
127
273
 
128
- let startTangent: Vector = this.ellipse.tangentAt(curAngle);
129
- let endTangent: Vector = this.ellipse.tangentAt(nextAngle);
274
+ let startTangent: Vector = this.ellipse.tangentAt(curAngle + this.ellipse.rotation);
275
+ let endTangent: Vector = this.ellipse.tangentAt(nextAngle + this.ellipse.rotation);
130
276
 
131
277
  // Distorted ellipses can only be approximated by linear Bézier segments
132
278
  if (Math.abs(radiusV) < 0.0001 || Math.abs(radiusH) < 0.0001) {
@@ -257,23 +403,23 @@ export class VEllipseSector {
257
403
  endAngle: number,
258
404
  fullEllipsePointCount: number
259
405
  ): Array<number> => {
260
- var ellipseAngles = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, fullEllipsePointCount);
406
+ var ellipseAngles: number[] = VEllipse.utils.equidistantVertAngles(radiusH, radiusV, fullEllipsePointCount);
261
407
  ellipseAngles = ellipseAngles.map((angle: number) => VEllipseSector.ellipseSectorUtils.normalizeAngle(angle));
262
408
 
263
- var angleIsInRange = (angle: number) => {
409
+ const angleIsInRange = (angle: number) => {
264
410
  if (startAngle < endAngle) return angle >= startAngle && angle <= endAngle;
265
411
  else return angle >= startAngle || (angle <= endAngle && angle >= 0);
266
412
  };
267
413
  // Drop all angles outside the sector
268
- var ellipseAngles = ellipseAngles.filter(angleIsInRange);
414
+ ellipseAngles = ellipseAngles.filter(angleIsInRange);
269
415
 
270
416
  // Now we need to sort the angles to the first one in the array is the closest to startAngle.
271
417
  // --> find the angle that is closest to the start angle
272
- var startIndex = VEllipseSector.ellipseSectorUtils.findClosestToStartAngle(startAngle, endAngle, ellipseAngles);
418
+ const startIndex: number = VEllipseSector.ellipseSectorUtils.findClosestToStartAngle(startAngle, endAngle, ellipseAngles);
273
419
 
274
420
  // Bring all angles into the correct order
275
421
  // Idea: use splice or slice here?
276
- var angles: Array<number> = [];
422
+ const angles: Array<number> = [];
277
423
  for (var i = 0; i < ellipseAngles.length; i++) {
278
424
  angles.push(ellipseAngles[(startIndex + i) % ellipseAngles.length]);
279
425
  }
package/src/ts/Vector.ts CHANGED
@@ -12,7 +12,8 @@
12
12
  * @modified 2022-02-02 Added the `destroy` method.
13
13
  * @modified 2022-02-02 Cleared the `Vector.toSVGString` function (deprecated). Use `drawutilssvg` instead.
14
14
  * @modified 2022-10-25 Added the `getOrthogonal` method.
15
- * @version 1.5.0
15
+ * @modified 2025-04-14 Added the `Vector.rotate(number)` method.
16
+ * @version 1.6.0
16
17
  *
17
18
  * @file Vector
18
19
  * @public
@@ -53,7 +54,6 @@ export class Vector extends VertTuple<Vector> implements SVGSerializable {
53
54
  /**
54
55
  * Get the perpendicular of this vector which is located at a.
55
56
  *
56
- * @param {Number} t The position on the vector.
57
57
  * @return {Vector} A new vector being the perpendicular of this vector sitting on a.
58
58
  **/
59
59
  perp(): Vector {
@@ -136,6 +136,20 @@ export class Vector extends VertTuple<Vector> implements SVGSerializable {
136
136
  return new Vector(linePoint, startPoint.add(this.a));
137
137
  }
138
138
 
139
+ /**
140
+ * Rotate this vector by the given angle around the first point `a`.
141
+ *
142
+ * @name rotate
143
+ * @method rotate
144
+ * @return {Vector} this - for chaining.
145
+ * @instance
146
+ * @memberof Vector
147
+ */
148
+ rotate(angle: number): Vector {
149
+ this.b.rotate(angle, this.a);
150
+ return this;
151
+ }
152
+
139
153
  static utils = {
140
154
  /**
141
155
  * Generate a four-point arrow head, starting at the vector end minus the
@@ -13,7 +13,10 @@
13
13
  * @modified 2024-09-10 Chaging the first param of `pointDistance` from `Vertex` to less strict type `XYCoords`. This should not break anything.
14
14
  * @modified 2024-09-10 Adding the optional `epsilon` param to the `hasPoint` method.
15
15
  * @modified 2024-12-02 Added the `epsilon` param to the `colinear` method. Default is 1.0e-6.
16
- * @version 1.3.0
16
+ * @modified 2025-03-31 Added the `VertTuple.revert` method.
17
+ * @modified 2025-04-15 Changed param of `VertTuple.moveTo` method from `Vertex` to `XYCoords`.
18
+ * @modified 2025-04-15 Added method `VertTuple.move` method.
19
+ * @version 1.4.0
17
20
  */
18
21
 
19
22
  import { Vertex } from "./Vertex";
@@ -124,9 +127,9 @@ export class VertTuple<T extends VertTuple<T>> {
124
127
  *
125
128
  * @method add
126
129
  * @param {XYCoords} amount The amount (x,y) to add.
127
- * @return {Line} this
128
130
  * @instance
129
131
  * @memberof VertTuple
132
+ * @return {VertTuple<T>} this
130
133
  **/
131
134
  add(amount: XYCoords): VertTuple<T> {
132
135
  this.a.add(amount);
@@ -134,6 +137,23 @@ export class VertTuple<T extends VertTuple<T>> {
134
137
  return this;
135
138
  }
136
139
 
140
+ /**
141
+ * Reverse this vertex tuple: a becomes b, and b becomes a.
142
+ * This operation is in-place.
143
+ *
144
+ * @method add
145
+ * @param {XYCoords} amount The amount (x,y) to add.
146
+ * @instance
147
+ * @memberof VertTuple
148
+ * @return {VertTuple<T>} this
149
+ */
150
+ revert(): VertTuple<T> {
151
+ const tmp: Vertex = this.a;
152
+ this.a = this.b;
153
+ this.b = tmp;
154
+ return this;
155
+ }
156
+
137
157
  /**
138
158
  * Normalize this line (set to length 1).
139
159
  *
@@ -165,18 +185,33 @@ export class VertTuple<T extends VertTuple<T>> {
165
185
  * Move this line to a new location.
166
186
  *
167
187
  * @method moveTo
168
- * @param {Vertex} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
188
+ * @param {XYCoords} newA - The new desired location of 'a'. Vertex 'b' will be moved, too.
169
189
  * @return {VertTuple} this
170
190
  * @instance
171
191
  * @memberof VertTuple
172
192
  **/
173
- moveTo(newA: Vertex): VertTuple<T> {
193
+ moveTo(newA: XYCoords): VertTuple<T> {
174
194
  let diff = this.a.difference(newA);
175
195
  this.a.add(diff);
176
196
  this.b.add(diff);
177
197
  return this;
178
198
  }
179
199
 
200
+ /**
201
+ * Move this line by the given amount
202
+ *
203
+ * @method move
204
+ * @param {XYCoords} amount - The amount to move both point of this tuple.
205
+ * @return {VertTuple} this
206
+ * @instance
207
+ * @memberof VertTuple
208
+ **/
209
+ move(amount: XYCoords): VertTuple<T> {
210
+ this.a.add(amount);
211
+ this.b.add(amount);
212
+ return this;
213
+ }
214
+
180
215
  /**
181
216
  * Get the angle between this and the passed line (in radians).
182
217
  *