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.
- package/dist/index.esm.js +166 -39
- package/dist/index.esm.js.map +1 -1
- package/dist/index.esm.min.js +1 -1
- package/dist/index.esm.min.js.map +1 -1
- package/dist/plotboilerplate.browser.min.js +1 -1
- package/dist/plotboilerplate.browser.min.js.map +1 -1
- package/dist/plotboilerplate.js +164 -37
- package/dist/plotboilerplate.min.js +1 -1
- package/package.json +1 -1
- package/src/cjs/Bounds.d.ts +11 -1
- package/src/cjs/Bounds.js +13 -1
- package/src/cjs/Bounds.js.map +1 -1
- package/src/cjs/CubicBezierCurve.d.ts +20 -3
- package/src/cjs/CubicBezierCurve.js +25 -2
- package/src/cjs/CubicBezierCurve.js.map +1 -1
- package/src/cjs/Line.d.ts +46 -4
- package/src/cjs/Line.js +60 -3
- package/src/cjs/Line.js.map +1 -1
- package/src/cjs/PlotBoilerplate.d.ts +4 -2
- package/src/cjs/PlotBoilerplate.js +44 -28
- package/src/cjs/PlotBoilerplate.js.map +1 -1
- package/src/cjs/Polygon.js +1 -1
- package/src/cjs/Polygon.js.map +1 -1
- package/src/cjs/Vector.d.ts +14 -3
- package/src/cjs/Vector.js +21 -2
- package/src/cjs/Vector.js.map +1 -1
- package/src/cjs/interfaces/additionals.d.ts +95 -0
- package/src/cjs/interfaces/additionals.js +3 -0
- package/src/cjs/interfaces/additionals.js.map +1 -0
- package/src/cjs/interfaces/core.d.ts +4 -0
- package/src/cjs/interfaces/core.js +1 -0
- package/src/cjs/interfaces/core.js.map +1 -1
- package/src/cjs/interfaces/index.d.ts +1 -0
- package/src/cjs/interfaces/index.js +1 -0
- package/src/cjs/interfaces/index.js.map +1 -1
- package/src/cjs/utils/algorithms/arrayShuffle.d.ts +8 -0
- package/src/cjs/utils/algorithms/arrayShuffle.js +15 -0
- package/src/cjs/utils/algorithms/arrayShuffle.js.map +1 -0
- package/src/cjs/utils/algorithms/detectPaths.d.ts +20 -0
- package/src/cjs/utils/algorithms/detectPaths.js +169 -0
- package/src/cjs/utils/algorithms/detectPaths.js.map +1 -0
- package/src/cjs/utils/datastructures/GenericPath.d.ts +75 -0
- package/src/cjs/utils/datastructures/GenericPath.js +105 -0
- package/src/cjs/utils/datastructures/GenericPath.js.map +1 -0
- package/src/cjs/utils/datastructures/GirihTile.d.ts +1 -1
- package/src/esm/Bounds.d.ts +11 -1
- package/src/esm/Bounds.js +13 -1
- package/src/esm/Bounds.js.map +1 -1
- package/src/esm/CubicBezierCurve.d.ts +20 -3
- package/src/esm/CubicBezierCurve.js +25 -2
- package/src/esm/CubicBezierCurve.js.map +1 -1
- package/src/esm/Line.d.ts +46 -4
- package/src/esm/Line.js +60 -3
- package/src/esm/Line.js.map +1 -1
- package/src/esm/PlotBoilerplate.d.ts +4 -2
- package/src/esm/PlotBoilerplate.js +44 -28
- package/src/esm/PlotBoilerplate.js.map +1 -1
- package/src/esm/Polygon.js +1 -1
- package/src/esm/Polygon.js.map +1 -1
- package/src/esm/Vector.d.ts +13 -2
- package/src/esm/Vector.js +23 -4
- package/src/esm/Vector.js.map +1 -1
- package/src/esm/interfaces/additionals.d.ts +95 -0
- package/src/esm/interfaces/additionals.js +2 -0
- package/src/esm/interfaces/additionals.js.map +1 -0
- package/src/esm/interfaces/core.d.ts +4 -0
- package/src/esm/interfaces/core.js +1 -0
- package/src/esm/interfaces/core.js.map +1 -1
- package/src/esm/interfaces/index.d.ts +1 -0
- package/src/esm/interfaces/index.js +1 -0
- package/src/esm/interfaces/index.js.map +1 -1
- package/src/esm/utils/algorithms/arrayShuffle.d.ts +8 -0
- package/src/esm/utils/algorithms/arrayShuffle.js +11 -0
- package/src/esm/utils/algorithms/arrayShuffle.js.map +1 -0
- package/src/esm/utils/algorithms/detectPaths.d.ts +20 -0
- package/src/esm/utils/algorithms/detectPaths.js +165 -0
- package/src/esm/utils/algorithms/detectPaths.js.map +1 -0
- package/src/esm/utils/datastructures/GenericPath.d.ts +75 -0
- package/src/esm/utils/datastructures/GenericPath.js +100 -0
- package/src/esm/utils/datastructures/GenericPath.js.map +1 -0
- package/src/esm/utils/datastructures/GirihTile.d.ts +1 -1
- package/src/ts/Bounds.ts +14 -1
- package/src/ts/CubicBezierCurve.ts +29 -4
- package/src/ts/Line.ts +70 -6
- package/src/ts/PlotBoilerplate.ts +47 -32
- package/src/ts/Polygon.ts +1 -1
- package/src/ts/Vector.ts +24 -4
- package/src/ts/interfaces/additionals.ts +105 -0
- package/src/ts/interfaces/core.ts +2 -0
- package/src/ts/interfaces/index.ts +1 -0
- package/src/ts/utils/algorithms/arrayShuffle.ts +11 -0
- package/src/ts/utils/algorithms/detectPaths.ts +195 -0
- package/src/ts/utils/datastructures/GenericPath.ts +113 -0
- package/src/ts/utils/datastructures/GirihTile.ts +1 -1
- 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
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
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,
|
|
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(
|
|
1364
|
-
draw.setCurrentId(
|
|
1365
|
-
fill.setCurrentClassName(
|
|
1366
|
-
fill.setCurrentId(
|
|
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(
|
|
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 =
|
|
1612
|
-
_self.canvas.style.left =
|
|
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 =
|
|
1616
|
-
_self.canvas.style.height =
|
|
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
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
|
-
* @
|
|
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
|
|
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
|
-
|
|
147
|
-
|
|
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
|
|
@@ -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
|
+
};
|