plotboilerplate 1.18.0 → 1.19.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 (95) hide show
  1. package/dist/index.esm.js +166 -39
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/index.esm.min.js +1 -1
  4. package/dist/index.esm.min.js.map +1 -1
  5. package/dist/plotboilerplate.browser.min.js +1 -1
  6. package/dist/plotboilerplate.browser.min.js.map +1 -1
  7. package/dist/plotboilerplate.js +164 -37
  8. package/dist/plotboilerplate.min.js +1 -1
  9. package/package.json +1 -1
  10. package/src/cjs/Bounds.d.ts +11 -1
  11. package/src/cjs/Bounds.js +13 -1
  12. package/src/cjs/Bounds.js.map +1 -1
  13. package/src/cjs/CubicBezierCurve.d.ts +20 -3
  14. package/src/cjs/CubicBezierCurve.js +25 -2
  15. package/src/cjs/CubicBezierCurve.js.map +1 -1
  16. package/src/cjs/Line.d.ts +46 -4
  17. package/src/cjs/Line.js +60 -3
  18. package/src/cjs/Line.js.map +1 -1
  19. package/src/cjs/PlotBoilerplate.d.ts +4 -2
  20. package/src/cjs/PlotBoilerplate.js +44 -28
  21. package/src/cjs/PlotBoilerplate.js.map +1 -1
  22. package/src/cjs/Polygon.js +1 -1
  23. package/src/cjs/Polygon.js.map +1 -1
  24. package/src/cjs/Vector.d.ts +14 -3
  25. package/src/cjs/Vector.js +21 -2
  26. package/src/cjs/Vector.js.map +1 -1
  27. package/src/cjs/interfaces/additionals.d.ts +95 -0
  28. package/src/cjs/interfaces/additionals.js +3 -0
  29. package/src/cjs/interfaces/additionals.js.map +1 -0
  30. package/src/cjs/interfaces/core.d.ts +4 -0
  31. package/src/cjs/interfaces/core.js +1 -0
  32. package/src/cjs/interfaces/core.js.map +1 -1
  33. package/src/cjs/interfaces/index.d.ts +1 -0
  34. package/src/cjs/interfaces/index.js +1 -0
  35. package/src/cjs/interfaces/index.js.map +1 -1
  36. package/src/cjs/utils/algorithms/arrayShuffle.d.ts +8 -0
  37. package/src/cjs/utils/algorithms/arrayShuffle.js +15 -0
  38. package/src/cjs/utils/algorithms/arrayShuffle.js.map +1 -0
  39. package/src/cjs/utils/algorithms/detectPaths.d.ts +20 -0
  40. package/src/cjs/utils/algorithms/detectPaths.js +169 -0
  41. package/src/cjs/utils/algorithms/detectPaths.js.map +1 -0
  42. package/src/cjs/utils/datastructures/GenericPath.d.ts +75 -0
  43. package/src/cjs/utils/datastructures/GenericPath.js +105 -0
  44. package/src/cjs/utils/datastructures/GenericPath.js.map +1 -0
  45. package/src/cjs/utils/datastructures/GirihTile.d.ts +1 -1
  46. package/src/esm/Bounds.d.ts +11 -1
  47. package/src/esm/Bounds.js +13 -1
  48. package/src/esm/Bounds.js.map +1 -1
  49. package/src/esm/CubicBezierCurve.d.ts +20 -3
  50. package/src/esm/CubicBezierCurve.js +25 -2
  51. package/src/esm/CubicBezierCurve.js.map +1 -1
  52. package/src/esm/Line.d.ts +46 -4
  53. package/src/esm/Line.js +60 -3
  54. package/src/esm/Line.js.map +1 -1
  55. package/src/esm/PlotBoilerplate.d.ts +4 -2
  56. package/src/esm/PlotBoilerplate.js +44 -28
  57. package/src/esm/PlotBoilerplate.js.map +1 -1
  58. package/src/esm/Polygon.js +1 -1
  59. package/src/esm/Polygon.js.map +1 -1
  60. package/src/esm/Vector.d.ts +13 -2
  61. package/src/esm/Vector.js +23 -4
  62. package/src/esm/Vector.js.map +1 -1
  63. package/src/esm/interfaces/additionals.d.ts +95 -0
  64. package/src/esm/interfaces/additionals.js +2 -0
  65. package/src/esm/interfaces/additionals.js.map +1 -0
  66. package/src/esm/interfaces/core.d.ts +4 -0
  67. package/src/esm/interfaces/core.js +1 -0
  68. package/src/esm/interfaces/core.js.map +1 -1
  69. package/src/esm/interfaces/index.d.ts +1 -0
  70. package/src/esm/interfaces/index.js +1 -0
  71. package/src/esm/interfaces/index.js.map +1 -1
  72. package/src/esm/utils/algorithms/arrayShuffle.d.ts +8 -0
  73. package/src/esm/utils/algorithms/arrayShuffle.js +11 -0
  74. package/src/esm/utils/algorithms/arrayShuffle.js.map +1 -0
  75. package/src/esm/utils/algorithms/detectPaths.d.ts +20 -0
  76. package/src/esm/utils/algorithms/detectPaths.js +165 -0
  77. package/src/esm/utils/algorithms/detectPaths.js.map +1 -0
  78. package/src/esm/utils/datastructures/GenericPath.d.ts +75 -0
  79. package/src/esm/utils/datastructures/GenericPath.js +100 -0
  80. package/src/esm/utils/datastructures/GenericPath.js.map +1 -0
  81. package/src/esm/utils/datastructures/GirihTile.d.ts +1 -1
  82. package/src/ts/Bounds.ts +14 -1
  83. package/src/ts/CubicBezierCurve.ts +29 -4
  84. package/src/ts/Line.ts +70 -6
  85. package/src/ts/PlotBoilerplate.ts +47 -32
  86. package/src/ts/Polygon.ts +1 -1
  87. package/src/ts/Vector.ts +24 -4
  88. package/src/ts/interfaces/additionals.ts +105 -0
  89. package/src/ts/interfaces/core.ts +2 -0
  90. package/src/ts/interfaces/index.ts +1 -0
  91. package/src/ts/utils/algorithms/arrayShuffle.ts +11 -0
  92. package/src/ts/utils/algorithms/detectPaths.ts +195 -0
  93. package/src/ts/utils/datastructures/GenericPath.ts +113 -0
  94. package/src/ts/utils/datastructures/GirihTile.ts +1 -1
  95. package/changelog.md +0 -731
@@ -75,7 +75,9 @@
75
75
  * @modified 2021-04-25 Extending `remove` to accept arrays of drawables.
76
76
  * @modified 2021-11-16 Adding the `PBText` drawable.
77
77
  * @modified 2022-08-01 Added `title` to the params.
78
- * @version 1.15.1
78
+ * @modified 2022-10-25 Added the `origin` to the default draw config.
79
+ * @modified 2022-11-06 Adding an XML declaration to the SVG export routine.
80
+ * @version 1.16.0
79
81
  *
80
82
  * @file PlotBoilerplate
81
83
  * @fileoverview The main class.
@@ -270,7 +272,7 @@ export class PlotBoilerplate {
270
272
  * @memberof PlotBoilerplate
271
273
  * @instance
272
274
  */
273
- selectPolygon: Polygon;
275
+ selectPolygon: Polygon | null;
274
276
 
275
277
  /**
276
278
  * @member {Array<IDraggable>}
@@ -532,6 +534,9 @@ export class PlotBoilerplate {
532
534
  lineWidth: 1,
533
535
  fill: true,
534
536
  anchor: true
537
+ },
538
+ origin: {
539
+ color: "#000000"
535
540
  }
536
541
  }; // END drawConfig
537
542
 
@@ -554,14 +559,16 @@ export class PlotBoilerplate {
554
559
  this.config.enableGL = false;
555
560
  }
556
561
  if (this.config.enableGL) {
557
- const ctx: WebGLRenderingContext = this.canvas.getContext("webgl"); // webgl-experimental?
562
+ // Override the case 'null' here. If GL is not supported, well then nothing works.
563
+ const ctx: WebGLRenderingContext = this.canvas.getContext("webgl") as WebGLRenderingContext; // webgl-experimental?
558
564
  this.draw = new drawutilsgl(ctx, false);
559
565
  // PROBLEM: same instance of fill and draw when using WebGL.
560
566
  // Shader program cannot be duplicated on the same context.
561
567
  this.fill = (this.draw as drawutilsgl).copyInstance(true);
562
568
  console.warn("Initialized with experimental mode enableGL=true. Note that this is not yet fully implemented.");
563
569
  } else {
564
- const ctx: CanvasRenderingContext2D = this.canvas.getContext("2d");
570
+ // Override the case 'null' here. If context creation is not supported, well then nothing works.
571
+ const ctx: CanvasRenderingContext2D = this.canvas.getContext("2d") as CanvasRenderingContext2D;
565
572
  this.draw = new drawutils(ctx, false);
566
573
  this.fill = new drawutils(ctx, true);
567
574
  }
@@ -599,8 +606,8 @@ export class PlotBoilerplate {
599
606
  if (config.title) {
600
607
  this.eventCatcher.setAttribute("title", config.title);
601
608
  }
602
- this.draw.scale.set(this.config.scaleX, this.config.scaleY);
603
- this.fill.scale.set(this.config.scaleX, this.config.scaleY);
609
+ this.draw.scale.set(this.config.scaleX ?? 1.0, this.config.scaleY);
610
+ this.fill.scale.set(this.config.scaleX ?? 1.0, this.config.scaleY);
604
611
  this.vertices = [];
605
612
  this.selectPolygon = null;
606
613
  this.draggedElements = [];
@@ -655,9 +662,13 @@ export class PlotBoilerplate {
655
662
 
656
663
  tosvgDraw.beginDrawCycle(0);
657
664
  tosvgFill.beginDrawCycle(0);
658
- if (pb.config.preClear) pb.config.preClear();
659
- tosvgDraw.clear(pb.config.backgroundColor);
660
- if (pb.config.preDraw) pb.config.preDraw(tosvgDraw, tosvgFill);
665
+ if (pb.config.preClear) {
666
+ pb.config.preClear();
667
+ }
668
+ tosvgDraw.clear(pb.config.backgroundColor || "white");
669
+ if (pb.config.preDraw) {
670
+ pb.config.preDraw(tosvgDraw, tosvgFill);
671
+ }
661
672
  pb.drawAll(0, tosvgDraw, tosvgFill);
662
673
  pb.drawVertices(0, tosvgDraw);
663
674
  if (pb.config.postDraw) pb.config.postDraw(tosvgDraw, tosvgFill);
@@ -669,7 +680,8 @@ export class PlotBoilerplate {
669
680
  var serializer = new XMLSerializer();
670
681
  var svgCode = serializer.serializeToString(svgNode);
671
682
 
672
- var blob = new Blob([svgCode], { type: "image/svg;charset=utf-8" });
683
+ // Add: '<?xml version="1.0" encoding="utf-8"?>\n' ?
684
+ var blob = new Blob(['<?xml version="1.0" encoding="utf-8"?>\n' + svgCode], { type: "image/svg;charset=utf-8" });
673
685
  // See documentation for FileSaver.js for usage.
674
686
  // https://github.com/eligrey/FileSaver.js
675
687
  if (typeof globalThis["saveAs"] !== "function")
@@ -751,9 +763,9 @@ export class PlotBoilerplate {
751
763
  **/
752
764
  private updateCSSscale() {
753
765
  if (this.config.cssUniformScale) {
754
- PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX, this.config.cssScaleX);
766
+ PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX ?? 1.0, this.config.cssScaleX ?? 1.0);
755
767
  } else {
756
- PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX, this.config.cssScaleY);
768
+ PlotBoilerplate.utils.setCSSscale(this.canvas, this.config.cssScaleX ?? 1.0, this.config.cssScaleY ?? 1.0);
757
769
  }
758
770
  }
759
771
 
@@ -1011,7 +1023,7 @@ export class PlotBoilerplate {
1011
1023
  getVertexNear(pixelPosition: XYCoords, pixelTolerance: number): Vertex | undefined {
1012
1024
  var p: IDraggable | undefined = this.locatePointNear(
1013
1025
  this.transformMousePosition(pixelPosition.x, pixelPosition.y),
1014
- pixelTolerance / Math.min(this.config.cssScaleX, this.config.cssScaleY)
1026
+ pixelTolerance / Math.min(this.config.cssScaleX ?? 1.0, this.config.cssScaleY ?? 1.0)
1015
1027
  );
1016
1028
  if (p && p.typeName == "vertex") {
1017
1029
  return this.vertices[p.vindex];
@@ -1095,7 +1107,7 @@ export class PlotBoilerplate {
1095
1107
  drawOrigin(draw: DrawLib<any>) {
1096
1108
  // Add a crosshair to mark the origin
1097
1109
  draw.setCurrentId("origin");
1098
- draw.crosshair({ x: 0, y: 0 }, 10, "#000000");
1110
+ draw.crosshair({ x: 0, y: 0 }, 10, this.drawConfig.origin.color);
1099
1111
  }
1100
1112
 
1101
1113
  /**
@@ -1360,10 +1372,10 @@ export class PlotBoilerplate {
1360
1372
  } else {
1361
1373
  console.error("Cannot draw object. Unknown class.");
1362
1374
  }
1363
- draw.setCurrentClassName(null);
1364
- draw.setCurrentId(null);
1365
- fill.setCurrentClassName(null);
1366
- fill.setCurrentId(null);
1375
+ draw.setCurrentClassName(undefined);
1376
+ draw.setCurrentId(undefined);
1377
+ fill.setCurrentClassName(undefined);
1378
+ fill.setCurrentId(undefined);
1367
1379
  }
1368
1380
 
1369
1381
  /**
@@ -1473,7 +1485,7 @@ export class PlotBoilerplate {
1473
1485
  **/
1474
1486
  clear() {
1475
1487
  // Note that elements might have an alpha channel. Clear the scene first.
1476
- this.draw.clear(this.config.backgroundColor);
1488
+ this.draw.clear(this.config.backgroundColor || "white");
1477
1489
  }
1478
1490
 
1479
1491
  /**
@@ -1505,7 +1517,10 @@ export class PlotBoilerplate {
1505
1517
  viewport(): Bounds {
1506
1518
  return new Bounds(
1507
1519
  this.transformMousePosition(0, 0),
1508
- this.transformMousePosition(this.canvasSize.width * this.config.cssScaleX, this.canvasSize.height * this.config.cssScaleY)
1520
+ this.transformMousePosition(
1521
+ this.canvasSize.width * (this.config.cssScaleX ?? 1.0),
1522
+ this.canvasSize.height * (this.config.cssScaleY ?? 1.0)
1523
+ )
1509
1524
  );
1510
1525
  }
1511
1526
 
@@ -1569,8 +1584,8 @@ export class PlotBoilerplate {
1569
1584
  resizeCanvas() {
1570
1585
  const _self: PlotBoilerplate = this;
1571
1586
  const _setSize = (w: number, h: number) => {
1572
- w *= _self.config.canvasWidthFactor;
1573
- h *= _self.config.canvasHeightFactor;
1587
+ w *= _self.config.canvasWidthFactor ?? 1.0;
1588
+ h *= _self.config.canvasHeightFactor ?? 1.0;
1574
1589
  _self.canvasSize.width = w;
1575
1590
  _self.canvasSize.height = h;
1576
1591
  if (_self.canvas instanceof HTMLCanvasElement) {
@@ -1597,8 +1612,8 @@ export class PlotBoilerplate {
1597
1612
  var width: number = globalThis.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
1598
1613
  var height: number = globalThis.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
1599
1614
  _self.canvas.style.position = "absolute";
1600
- _self.canvas.style.width = _self.config.canvasWidthFactor * width + "px";
1601
- _self.canvas.style.height = _self.config.canvasWidthFactor * height + "px";
1615
+ _self.canvas.style.width = (_self.config.canvasWidthFactor ?? 1.0) * width + "px";
1616
+ _self.canvas.style.height = (_self.config.canvasWidthFactor ?? 1.0) * height + "px";
1602
1617
  _self.canvas.style.top = "0px";
1603
1618
  _self.canvas.style.left = "0px";
1604
1619
  _setSize(width, height);
@@ -1606,15 +1621,15 @@ export class PlotBoilerplate {
1606
1621
  // Set editor size
1607
1622
  _self.canvas.style.position = "absolute";
1608
1623
  const space: XYDimension = this.getAvailableContainerSpace();
1609
- _self.canvas.style.width = _self.config.canvasWidthFactor * space.width + "px";
1610
- _self.canvas.style.height = _self.config.canvasHeightFactor * space.height + "px";
1611
- _self.canvas.style.top = null;
1612
- _self.canvas.style.left = null;
1624
+ _self.canvas.style.width = (_self.config.canvasWidthFactor ?? 1.0) * space.width + "px";
1625
+ _self.canvas.style.height = (_self.config.canvasHeightFactor ?? 1.0) * space.height + "px";
1626
+ _self.canvas.style.top = "";
1627
+ _self.canvas.style.left = "";
1613
1628
  _setSize(space.width, space.height);
1614
1629
  } else {
1615
- _self.canvas.style.width = null;
1616
- _self.canvas.style.height = null;
1617
- _setSize(_self.config.defaultCanvasWidth, _self.config.defaultCanvasHeight);
1630
+ _self.canvas.style.width = "";
1631
+ _self.canvas.style.height = "";
1632
+ _setSize(_self.config.defaultCanvasWidth ?? 1024, _self.config.defaultCanvasHeight ?? 768);
1618
1633
  }
1619
1634
 
1620
1635
  if (_self.config.redrawOnResize) _self.redraw();
@@ -2269,7 +2284,7 @@ export class PlotBoilerplate {
2269
2284
  * @param {function} fallback - A default value if the key does not exist.
2270
2285
  * @return {function}
2271
2286
  **/
2272
- func: (obj: any, key: string, fallback: (...args: any[]) => any) => {
2287
+ func: (obj: any, key: string, fallback: ((...args: any[]) => any) | null) => {
2273
2288
  if (!obj.hasOwnProperty(key)) return fallback;
2274
2289
  if (typeof obj[key] !== "function") return fallback;
2275
2290
  return obj[key];
package/src/ts/Polygon.ts CHANGED
@@ -99,7 +99,7 @@ export class Polygon implements SVGSerializable {
99
99
  this.uid = UIDGenerator.next();
100
100
  if (typeof vertices == "undefined") vertices = [];
101
101
  this.vertices = vertices;
102
- this.isOpen = isOpen;
102
+ this.isOpen = isOpen || false;
103
103
  }
104
104
 
105
105
  /**
package/src/ts/Vector.ts CHANGED
@@ -11,7 +11,8 @@
11
11
  * @modified 2021-01-20 Added UID.
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
- * @version 1.4.0
14
+ * @modified 2022-10-25 Added the `getOrthogonal` method.
15
+ * @version 1.5.0
15
16
  *
16
17
  * @file Vector
17
18
  * @public
@@ -65,7 +66,7 @@ export class Vector extends VertTuple<Vector> implements SVGSerializable {
65
66
  }
66
67
 
67
68
  /**
68
- * The inverse of a vector is a vector witht the same magnitude but oppose direction.
69
+ * The inverse of a vector is a vector with the same magnitude but oppose direction.
69
70
  *
70
71
  * Please not that the origin of this vector changes here: a->b becomes b->a.
71
72
  *
@@ -116,6 +117,25 @@ export class Vector extends VertTuple<Vector> implements SVGSerializable {
116
117
  return new Vertex(this.a.x + a * (this.b.x - this.a.x), this.a.y + a * (this.b.y - this.a.y));
117
118
  }
118
119
 
120
+ /**
121
+ * Get the orthogonal "vector" of this vector (rotated by 90° clockwise).
122
+ *
123
+ * @name getOrthogonal
124
+ * @method getOrthogonal
125
+ * @return {Vector} A new vector with the same length that stands on this vector's point a.
126
+ * @instance
127
+ * @memberof Vector
128
+ **/
129
+ getOrthogonal(): Vector {
130
+ // Orthogonal of vector (0,0)->(x,y) is (0,0)->(-y,x)
131
+ const linePoint = this.a.clone();
132
+ const startPoint = this.b.clone().sub(this.a);
133
+ const tmp = startPoint.x;
134
+ startPoint.x = -startPoint.y;
135
+ startPoint.y = tmp;
136
+ return new Vector(linePoint, startPoint.add(this.a));
137
+ }
138
+
119
139
  static utils = {
120
140
  /**
121
141
  * Generate a four-point arrow head, starting at the vector end minus the
@@ -143,8 +163,8 @@ export class Vector extends VertTuple<Vector> implements SVGSerializable {
143
163
  * @param {number} scaleY - the vertical scaling during draw.
144
164
  **/
145
165
  buildArrowHead: (zA: Vertex, zB: Vertex, headlen: number, scaleX: number, scaleY: number) => {
146
- var angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
147
- var vertices = [];
166
+ const angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
167
+ const vertices: Array<Vertex> = [];
148
168
  vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
149
169
  vertices.push(
150
170
  new Vertex(
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @date 2022-10-17
3
+ */
4
+ import { Vertex } from "../Vertex";
5
+
6
+ /**
7
+ * A common interface for different kinds of path segments.
8
+ */
9
+ export interface PathSegment {
10
+ /**
11
+ * Get the start point of this path segment.
12
+ *
13
+ * @method getStartPoint
14
+ * @memberof PathSegment
15
+ * @return {Vertex} The start point of this path segment.
16
+ */
17
+ getStartPoint(): Vertex;
18
+
19
+ /**
20
+ * Get the end point of this path segment.
21
+ *
22
+ * @method getEndPoint
23
+ * @memberof PathSegment
24
+ * @return {Vertex} The end point of this path segment.
25
+ */
26
+ getEndPoint(): Vertex;
27
+
28
+ /**
29
+ * Get the tangent's end point at the start point of this segment.
30
+ *
31
+ * @method getStartTangent
32
+ * @memberof PathSegment
33
+ * @return {Vertex} The end point of the starting point's tangent.
34
+ */
35
+ getStartTangent(): Vertex;
36
+
37
+ /**
38
+ * Get the tangent's end point at the end point of this segment.
39
+ *
40
+ * @method getEndTangent
41
+ * @memberof PathSegment
42
+ * @return {Vertex} The end point of the ending point's tangent.
43
+ */
44
+ getEndTangent(): Vertex;
45
+
46
+ /**
47
+ * Create a deep clone of this path segment.
48
+ *
49
+ * @method clone
50
+ * @memberof PathSegment
51
+ * @return {PathSegment} A deep clone/copy of this path segment.
52
+ */
53
+ clone: () => PathSegment;
54
+
55
+ /**
56
+ * Revserse this path segment (in-place) and return this same instance (useful for chaining).
57
+ * The new path segment has start and end point swapped.
58
+ *
59
+ * @method reverse
60
+ * @memberof PathSegment
61
+ * @return {PathSegment} This path segment instance (for chaining).
62
+ */
63
+ reverse: () => PathSegment;
64
+ }
65
+
66
+ /**
67
+ * A common interface for general path representations.
68
+ */
69
+ export interface Path extends PathSegment {
70
+ /**
71
+ * Get the number of segments in this path.
72
+ *
73
+ * @method getSegmentCount
74
+ * @memberof Path
75
+ * @return {number} The number (integer) of path segments.
76
+ */
77
+ getSegmentCount: () => number;
78
+
79
+ /**
80
+ * Get the path segment at the given index.
81
+ *
82
+ * @method getSegmentAt
83
+ * @memberof Path
84
+ * @return {PathSegment} The paths segment at the given index.
85
+ */
86
+ getSegmentAt: (index: number) => PathSegment;
87
+
88
+ /**
89
+ * Create a deep clone of this path.
90
+ *
91
+ * @method clone
92
+ * @memberof Path
93
+ * @return {Path} A deep clone/copy of this path.
94
+ */
95
+ clone: () => Path;
96
+
97
+ /**
98
+ * Reverse this path (swap start and end and thus – the direction) in-place.
99
+ *
100
+ * @method revert
101
+ * @memberof Path
102
+ * @return {PathSegment} This path instance.
103
+ */
104
+ reverse: () => Path;
105
+ }
@@ -15,6 +15,7 @@
15
15
  * @modified 2021-06-21 Added `IBounds.getCenter()`.
16
16
  * @modified 2021-11-16 Added `text` options to the `DrawConfig`.
17
17
  * @modified 2022-08-01 Added `title` param to the `PBParams` interface.
18
+ * @modified 2022-10-25 Added `origin` param to the `DrawConfig` interface.
18
19
  **/
19
20
 
20
21
  import { Vertex } from "../Vertex";
@@ -199,6 +200,7 @@ export interface DrawConfig {
199
200
  fill?: boolean;
200
201
  anchor?: boolean;
201
202
  };
203
+ origin: { color: string };
202
204
  }
203
205
 
204
206
  /** This is a fix for the problem, that the constructor's "name" attribute is not
@@ -1,3 +1,4 @@
1
1
  export * from "./core";
2
2
  export * from "./DrawLib";
3
3
  export * from "./externals";
4
+ export * from "./additionals";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Randomize a given array in-place.
3
+ *
4
+ * @author Ikaros Kappler
5
+ * @date 2022-10-09
6
+ * @version 1.0.0
7
+ **/
8
+
9
+ export const arrayShuffle = (arr: Array<any>): void => {
10
+ arr.sort(() => Math.random() - 0.5);
11
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * A function to detect connected paths on the plane given by a random set of segments.
3
+ *
4
+ * Note that is algorithm operates IN PLACE and will ALTER your INPUT array.
5
+ *
6
+ * @author Ikaros Kappler
7
+ * @version 1.0.0
8
+ * @date 2022-10-17
9
+ */
10
+
11
+ import { Path, PathSegment } from "../../interfaces";
12
+ import { GenericPath } from "../datastructures/GenericPath";
13
+
14
+ /**
15
+ * An interface/datascructure for keeping track of visited path segments.
16
+ */
17
+ interface Visitation {
18
+ visited: boolean;
19
+ hasPredecessor: boolean;
20
+ hasSuccessor: boolean;
21
+ }
22
+
23
+ /**
24
+ * A private helper function that initializes the visitation tracking and reverses
25
+ * dangling path ends to connect them with preceding paths.
26
+ *
27
+ * Note that this function alters those dangling path segments IN PLACE!
28
+ *
29
+ * @param segments
30
+ * @param epsilon
31
+ * @returns
32
+ */
33
+ const initVisitationArray = (segments: Array<PathSegment>, epsilon: number): Array<Visitation> => {
34
+ const result: Array<Visitation> = []; // { visited: false, hasPredecessor: false }
35
+ for (var i = 0; i < segments.length; i++) {
36
+ result.push({ visited: false, hasPredecessor: false, hasSuccessor: false });
37
+ for (var j = 0; j < segments.length; j++) {
38
+ if (i === j) {
39
+ continue;
40
+ }
41
+ if (
42
+ segments[i].getStartPoint().distance(segments[j].getEndPoint()) < epsilon ||
43
+ segments[i].getStartPoint().distance(segments[j].getStartPoint()) < epsilon
44
+ ) {
45
+ result[i].hasPredecessor = true;
46
+ }
47
+ if (
48
+ segments[i].getEndPoint().distance(segments[j].getEndPoint()) < epsilon ||
49
+ segments[i].getEndPoint().distance(segments[j].getStartPoint()) < epsilon
50
+ ) {
51
+ result[i].hasSuccessor = true;
52
+ }
53
+ }
54
+ }
55
+ // After first initialization make sure that no loose path ends are dangling around
56
+ // if the affected path segment has a predecessor (but nu successor).
57
+ //
58
+ // Revert those segments. This makes the actual detection algorithm much easier.
59
+ for (var i = 0; i < segments.length; i++) {
60
+ if (!result[i].hasSuccessor && result[i].hasPredecessor) {
61
+ segments[i] = segments[i].reverse();
62
+ result[i].hasPredecessor = false;
63
+ result[i].hasSuccessor = true;
64
+ }
65
+ }
66
+ return result;
67
+ };
68
+
69
+ /**
70
+ * Find the next unvisited path segment (private helper function).
71
+ *
72
+ * There is a special order: starting path segments (those without any predecessor)
73
+ * will be preferred. If no more open paths are available (no starting path segments),
74
+ * then no more unvisited paths are available or all remaining paths are loops (without
75
+ * determined start/end).
76
+ *
77
+ * Doing this keeps us from the need to run a final loop to connect detected sub paths.
78
+ *
79
+ * @param {Array<PathSegment>} segments - The path segments to search in.
80
+ * @param { Array<Visitation>} isSegmentVisited
81
+ * @returns {number} The index of the next unvisited path segment or -1 if no more are available.
82
+ */
83
+ const locateUnvisitedSegment = (segments: Array<PathSegment>, isSegmentVisited: Array<Visitation>): number => {
84
+ // First run: detect beginnings
85
+ for (var i = 0; i < segments.length; i++) {
86
+ if (!isSegmentVisited[i].hasPredecessor && !isSegmentVisited[i].visited) {
87
+ return i;
88
+ }
89
+ }
90
+ // Second run: if no beginnings exist -> use inner path segment
91
+ for (var i = 0; i < segments.length; i++) {
92
+ if (!isSegmentVisited[i].visited) {
93
+ return i;
94
+ }
95
+ }
96
+ return -1;
97
+ };
98
+
99
+ /**
100
+ * Get the next adjacent path segment to the given (current) segment. This is a private
101
+ * helper function.
102
+ *
103
+ * Note that the function will revert the adjacent path segment if required, so the next
104
+ * starting point 'equals' the current ending point.
105
+ *
106
+ * The visitation tracker will be updated if the adjacent segment was found.
107
+ *
108
+ * @param {Array<PathSegment>} segments - The total set of available path segments (visited and invisited).
109
+ * @param {Array<Visitation>} isSegmentVisited - A tracker of visited path segments so far.
110
+ * @param {PathSegment} currentSegment - The current path segment to find the adjacent segment for.
111
+ * @param {number} epsilon - The epsilon to use to detect 'equal' start/end points. Must be >= 0.
112
+ * @returns {PathSegment | null} The next adjacent path segment of null if no such exists.
113
+ */
114
+ const getAdjacentSegment = (
115
+ segments: Array<PathSegment>,
116
+ isSegmentVisited: Array<Visitation>,
117
+ currentSegment: PathSegment,
118
+ epsilon: number
119
+ ): PathSegment | null => {
120
+ for (var j = 0; j < segments.length; j++) {
121
+ if (isSegmentVisited[j].visited) {
122
+ continue;
123
+ }
124
+ var nextSegment = segments[j];
125
+ // [start]---[end] [start]---[end]
126
+ if (currentSegment.getEndPoint().distance(nextSegment.getStartPoint()) < epsilon) {
127
+ isSegmentVisited[j].visited = true;
128
+ return nextSegment;
129
+ }
130
+ // [start]---[end] [end]---[start]
131
+ else if (currentSegment.getEndPoint().distance(nextSegment.getEndPoint()) < epsilon) {
132
+ isSegmentVisited[j].visited = true;
133
+ return nextSegment.reverse();
134
+ }
135
+ }
136
+ return null;
137
+ };
138
+
139
+ /**
140
+ * A private helper function to find the adjacent full path for the given path segment,
141
+ * considering the current path segment is a starting segment (or one inside a loop).
142
+ *
143
+ * @param {Array<PathSegment>} segments - The total set of available path segments.
144
+ * @param { Array<Visitation>} isSegmentVisited - A tracker to determine which segments have already been visited.
145
+ * @param {number} currentSegmentIndex - The index of the current segments to find the containing path for.
146
+ * @param {number} epsilon - The epsilon to use to determine 'equal' path points. Must be >= 0.
147
+ * @returns {GenericPath} The dected path which consists at least of the current path segment.
148
+ */
149
+ const detectAdjacentPath = (
150
+ segments: Array<PathSegment>,
151
+ isSegmentVisited: Array<Visitation>,
152
+ currentSegmentIndex: number,
153
+ epsilon: number
154
+ ): GenericPath => {
155
+ var currentSegment: PathSegment | null = segments[currentSegmentIndex];
156
+ isSegmentVisited[currentSegmentIndex].visited = true;
157
+ const path = new GenericPath(currentSegment); // { segments: [currentSegment], reverse };
158
+
159
+ var i: number = 0;
160
+ // A safety break if something goes wrong
161
+ while (i < segments.length && currentSegment) {
162
+ currentSegment = getAdjacentSegment(segments, isSegmentVisited, currentSegment, epsilon);
163
+ if (currentSegment) {
164
+ path.segments.push(currentSegment);
165
+ }
166
+ i++;
167
+ }
168
+ return path;
169
+ };
170
+
171
+ /**
172
+ * Run a path detection on the given set of path segments.
173
+ *
174
+ * Note that the array and some path segments may be altered (like reversal) IN PLACE.
175
+ *
176
+ * @param {Array<PathSegment>} segments - The total set (array) of available path segments.
177
+ * @param {number=1.0} epsilon - (optional) An epsilon to use to tell if two plane points should be considered 'equal'.
178
+ * @returns {Array<GenericPath>} An array containing all detected path (consisting of adjacent path segments of the original set).
179
+ */
180
+ export const detectPaths = (segments: Array<PathSegment>, epsilon?: number): Array<Path> => {
181
+ const eps = typeof epsilon === "undefined" || epsilon < 0 ? 1.0 : epsilon;
182
+ const isSegmentVisited: Array<Visitation> = initVisitationArray(segments, eps);
183
+ const resultPaths: Array<GenericPath> = [];
184
+ var nextSegmentIndex: number = -1;
185
+ var i = 0;
186
+ while ((nextSegmentIndex = locateUnvisitedSegment(segments, isSegmentVisited)) !== -1 && i < segments.length) {
187
+ isSegmentVisited[nextSegmentIndex].visited = true;
188
+ // A safety break (to avoid infinited loops during development).
189
+ i++;
190
+ const path = detectAdjacentPath(segments, isSegmentVisited, nextSegmentIndex, eps);
191
+ i += path.getSegmentCount() - 1;
192
+ resultPaths.push(path);
193
+ }
194
+ return resultPaths;
195
+ };