@skygraph/core 0.4.0 → 0.5.1
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/README.md +48 -0
- package/dist/{chunk-KP75DEA4.js → chunk-7FA2TBSZ.js} +206 -7
- package/dist/chunk-7FA2TBSZ.js.map +1 -0
- package/dist/graph.cjs +210 -6
- package/dist/graph.cjs.map +1 -1
- package/dist/graph.d.cts +131 -2
- package/dist/graph.d.ts +131 -2
- package/dist/graph.js +11 -1
- package/dist/index.cjs +210 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-KP75DEA4.js.map +0 -1
package/dist/graph.d.cts
CHANGED
|
@@ -402,7 +402,8 @@ interface RouteOrthogonalOptions {
|
|
|
402
402
|
targetBounds?: AABB;
|
|
403
403
|
/**
|
|
404
404
|
* Length (world units) of the perpendicular stub generated when
|
|
405
|
-
* `sourceBounds` / `targetBounds` is set. Defaults to `max(
|
|
405
|
+
* `sourceBounds` / `targetBounds` is set. Defaults to `max(20, gridSize)`
|
|
406
|
+
* — matches React Flow's `offset = 20` for smoothstep edges.
|
|
406
407
|
*/
|
|
407
408
|
stubLength?: number;
|
|
408
409
|
/**
|
|
@@ -412,6 +413,19 @@ interface RouteOrthogonalOptions {
|
|
|
412
413
|
* affects the L-route fallback.
|
|
413
414
|
*/
|
|
414
415
|
snap?: number;
|
|
416
|
+
/**
|
|
417
|
+
* Where the central bend sits on the connector when both
|
|
418
|
+
* `sourceBounds` and `targetBounds` are provided.
|
|
419
|
+
*
|
|
420
|
+
* - `0` → bend right at the source stub
|
|
421
|
+
* - `0.5` → midpoint between the two stubs (default — matches
|
|
422
|
+
* React Flow's `stepPosition`)
|
|
423
|
+
* - `1` → bend right at the target stub
|
|
424
|
+
*
|
|
425
|
+
* Ignored when bounds are missing (we fall back to the legacy L-route)
|
|
426
|
+
* or when A* is engaged (it picks bends from path-cost, not position).
|
|
427
|
+
*/
|
|
428
|
+
stepPosition?: number;
|
|
415
429
|
}
|
|
416
430
|
/**
|
|
417
431
|
* Compute an orthogonal (right-angled) path between two points.
|
|
@@ -483,6 +497,23 @@ interface BezierPathOptions {
|
|
|
483
497
|
* point when the caller doesn't already know it.
|
|
484
498
|
*/
|
|
485
499
|
declare function getBezierPath(opts: BezierPathOptions): string;
|
|
500
|
+
/**
|
|
501
|
+
* Compute the intersection of the line from `box`'s centre to
|
|
502
|
+
* `opposite` with the rectangle's border. Returns the border point and
|
|
503
|
+
* the side it sits on.
|
|
504
|
+
*
|
|
505
|
+
* This is the "floating anchor" trick used by React Flow's
|
|
506
|
+
* FloatingEdges example and most graph viz libraries: instead of
|
|
507
|
+
* binding an endpoint to a fixed corner or midpoint, you bind it to
|
|
508
|
+
* the centre and let it slide along the perimeter following the
|
|
509
|
+
* direction to the other node. The result is dramatically cleaner on
|
|
510
|
+
* dense / unstructured graphs because every edge picks the closest
|
|
511
|
+
* face automatically as nodes are dragged around.
|
|
512
|
+
*/
|
|
513
|
+
declare function floatingAnchor(box: AABB, opposite: Point): {
|
|
514
|
+
point: Point;
|
|
515
|
+
side: Side;
|
|
516
|
+
};
|
|
486
517
|
/**
|
|
487
518
|
* Pick the side of `box` whose midpoint is closest to `target`. Same
|
|
488
519
|
* logic as the internal helper used by orthogonal routing — exposed
|
|
@@ -490,6 +521,104 @@ declare function getBezierPath(opts: BezierPathOptions): string;
|
|
|
490
521
|
* it.
|
|
491
522
|
*/
|
|
492
523
|
declare function nearestSide(box: AABB, target: Point): Side;
|
|
524
|
+
/**
|
|
525
|
+
* Resolved edge endpoint geometry — output of {@link resolveEdgeEndpoint}.
|
|
526
|
+
*
|
|
527
|
+
* `point` is in world space, sitting on (or just outside of) the
|
|
528
|
+
* `box` border. `side` records which face the connector exits / enters
|
|
529
|
+
* so downstream consumers (bezier control handles, orthogonal stubs)
|
|
530
|
+
* can stay perpendicular.
|
|
531
|
+
*/
|
|
532
|
+
interface ResolvedEndpoint {
|
|
533
|
+
point: Point;
|
|
534
|
+
side: Side;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Compute the intersection of the segment between the centre of
|
|
538
|
+
* `intersectionBox` and `targetCenter` with the perimeter of
|
|
539
|
+
* `intersectionBox`.
|
|
540
|
+
*
|
|
541
|
+
* **Source / credit.** Direct port of the algorithm used by xyflow
|
|
542
|
+
* (React Flow) for FloatingEdges and by mxGraph / draw.io's
|
|
543
|
+
* `mxPerimeter.RectanglePerimeter`. The closed-form derivation is in
|
|
544
|
+
* https://math.stackexchange.com/questions/1724792 — it parametrises
|
|
545
|
+
* the rectangle as the manhattan-norm rhombus `|x| + |y| = 1` and
|
|
546
|
+
* solves for the unit intersection in one step, which is faster and
|
|
547
|
+
* numerically more stable than the angle-based formulation.
|
|
548
|
+
*
|
|
549
|
+
* We use this as the canonical endpoint resolver for every routing
|
|
550
|
+
* mode (`straight` / `orthogonal` / `bezier`). The visible benefit is
|
|
551
|
+
* stability: nothing in this function depends on user-supplied
|
|
552
|
+
* anchors — the side, the point, the angle of approach are all pure
|
|
553
|
+
* functions of the two rectangles' bounding boxes, so dragging a node
|
|
554
|
+
* around never produces the "wrong-side-of-the-box" / "edge cuts
|
|
555
|
+
* through the node body" artifacts that the anchor-honouring resolver
|
|
556
|
+
* used to leak.
|
|
557
|
+
*
|
|
558
|
+
* Returns the world-space intersection point. When `box` has zero
|
|
559
|
+
* area (degenerate node) the function returns its centre so callers
|
|
560
|
+
* can still draw a useful edge to it.
|
|
561
|
+
*/
|
|
562
|
+
declare function getNodeIntersection(intersectionBox: AABB, targetCenter: Point): Point;
|
|
563
|
+
/**
|
|
564
|
+
* Classify which face of `box` the `intersectionPoint` sits on.
|
|
565
|
+
* Companion to {@link getNodeIntersection} — together they replace the
|
|
566
|
+
* old anchor-driven endpoint resolver.
|
|
567
|
+
*
|
|
568
|
+
* The function rounds both the box position and the point to integer
|
|
569
|
+
* pixels (matches the xyflow heuristic) before comparing, which keeps
|
|
570
|
+
* floating-point noise from picking the wrong side when an
|
|
571
|
+
* intersection lands almost exactly on a corner.
|
|
572
|
+
*/
|
|
573
|
+
declare function getEdgePosition(box: AABB, intersectionPoint: Point): Side;
|
|
574
|
+
/**
|
|
575
|
+
* Single canonical edge endpoint resolver used by `<Diagram>` for
|
|
576
|
+
* every routing mode. Wraps {@link getNodeIntersection} +
|
|
577
|
+
* {@link getEdgePosition} into the `{ point, side }` shape the
|
|
578
|
+
* adapters expect, and applies a small outward `padding` so the
|
|
579
|
+
* arrowhead marker has room to sit between the path tip and the node
|
|
580
|
+
* border (matches React Flow / draw.io behaviour).
|
|
581
|
+
*
|
|
582
|
+
* **Important: anchor information is intentionally ignored here.**
|
|
583
|
+
* The `<Diagram>` API still accepts `from: { anchor: 'nw' }` etc. for
|
|
584
|
+
* back-compat and semantic intent, but the visual layer now derives
|
|
585
|
+
* the endpoint purely from bounding boxes. This is the floating-edges
|
|
586
|
+
* approach taken by every mainstream diagram library — it's what
|
|
587
|
+
* makes drag-around-the-canvas behaviour feel correct on randomly
|
|
588
|
+
* laid-out graphs.
|
|
589
|
+
*/
|
|
590
|
+
declare function resolveEdgeEndpoint(sourceBox: AABB, targetBox: AABB, padding?: number): ResolvedEndpoint;
|
|
591
|
+
/**
|
|
592
|
+
* Decide which face an endpoint sits on, combining two cues:
|
|
593
|
+
*
|
|
594
|
+
* 1. **Anchor position** — if `anchor` is already at one of `box`'s
|
|
595
|
+
* borders (within `tolerance`), the corresponding face is returned
|
|
596
|
+
* as-is. This lets consumers express intent ("exit from the right
|
|
597
|
+
* side") simply by placing the anchor on that border.
|
|
598
|
+
*
|
|
599
|
+
* 2. **Geometry fallback** — when the anchor is interior to `box`
|
|
600
|
+
* (or no border match) we fall back to {@link nearestSide} pointed
|
|
601
|
+
* at the opposite endpoint. This is the right answer for centre
|
|
602
|
+
* anchors and produces stable sides for big graphs where every
|
|
603
|
+
* pair of nodes has a different relative position.
|
|
604
|
+
*
|
|
605
|
+
* Returns the side along with a `confident` flag — `true` when the
|
|
606
|
+
* decision came from the anchor position (case 1), `false` when it
|
|
607
|
+
* fell back to geometry (case 2). Consumers that want to override the
|
|
608
|
+
* fallback in special cases can branch on the flag.
|
|
609
|
+
*
|
|
610
|
+
* Corner anchors (touching two borders simultaneously like `nw` / `ne`)
|
|
611
|
+
* are disambiguated by which face points more strongly at the opposite
|
|
612
|
+
* endpoint. **The disambiguation is also clamped to faces that face the
|
|
613
|
+
* other node** — a `ne` anchor on the source whose target sits to its
|
|
614
|
+
* left will pick `left` instead of `right`, because using `right` would
|
|
615
|
+
* route the edge AWAY from the target. This is the key fix for the
|
|
616
|
+
* "stub-then-180°-loop" artifact visible on randomly-laid-out graphs.
|
|
617
|
+
*/
|
|
618
|
+
declare function inferSide(box: AABB, anchor: Point, opposite: Point, tolerance?: number): {
|
|
619
|
+
side: Side;
|
|
620
|
+
confident: boolean;
|
|
621
|
+
};
|
|
493
622
|
|
|
494
623
|
/**
|
|
495
624
|
* Oriented Bounding Box — a rectangle in world space described by its centre,
|
|
@@ -539,4 +668,4 @@ declare function aabbFromOBB(obb: OBB): AABB;
|
|
|
539
668
|
*/
|
|
540
669
|
declare function obbContainsPoint(obb: OBB, p: Point): boolean;
|
|
541
670
|
|
|
542
|
-
export { type AABB, type Anchor, type AnchorId, type AnchorPolicy, type BezierPathOptions, type EdgeEndpoint, type EdgeId, type EdgeInit, type GraphEdge, type GraphEngine, type GraphEngineOptions, type GraphNode, type GraphState, type NodeId, type NodeInit, type NodeTransform, type NodeUpdate, type OBB$1 as OBB, type Outline, type Point, type RouteOrthogonalOptions, type Side, aabbFromOBB, createGraph, getBezierPath, nearestSide, obbContainsPoint, obbCorners, pointsToPath, pointsToRoundedPath, routeOrthogonal };
|
|
671
|
+
export { type AABB, type Anchor, type AnchorId, type AnchorPolicy, type BezierPathOptions, type EdgeEndpoint, type EdgeId, type EdgeInit, type GraphEdge, type GraphEngine, type GraphEngineOptions, type GraphNode, type GraphState, type NodeId, type NodeInit, type NodeTransform, type NodeUpdate, type OBB$1 as OBB, type Outline, type Point, type ResolvedEndpoint, type RouteOrthogonalOptions, type Side, aabbFromOBB, createGraph, floatingAnchor, getBezierPath, getEdgePosition, getNodeIntersection, inferSide, nearestSide, obbContainsPoint, obbCorners, pointsToPath, pointsToRoundedPath, resolveEdgeEndpoint, routeOrthogonal };
|
package/dist/graph.d.ts
CHANGED
|
@@ -402,7 +402,8 @@ interface RouteOrthogonalOptions {
|
|
|
402
402
|
targetBounds?: AABB;
|
|
403
403
|
/**
|
|
404
404
|
* Length (world units) of the perpendicular stub generated when
|
|
405
|
-
* `sourceBounds` / `targetBounds` is set. Defaults to `max(
|
|
405
|
+
* `sourceBounds` / `targetBounds` is set. Defaults to `max(20, gridSize)`
|
|
406
|
+
* — matches React Flow's `offset = 20` for smoothstep edges.
|
|
406
407
|
*/
|
|
407
408
|
stubLength?: number;
|
|
408
409
|
/**
|
|
@@ -412,6 +413,19 @@ interface RouteOrthogonalOptions {
|
|
|
412
413
|
* affects the L-route fallback.
|
|
413
414
|
*/
|
|
414
415
|
snap?: number;
|
|
416
|
+
/**
|
|
417
|
+
* Where the central bend sits on the connector when both
|
|
418
|
+
* `sourceBounds` and `targetBounds` are provided.
|
|
419
|
+
*
|
|
420
|
+
* - `0` → bend right at the source stub
|
|
421
|
+
* - `0.5` → midpoint between the two stubs (default — matches
|
|
422
|
+
* React Flow's `stepPosition`)
|
|
423
|
+
* - `1` → bend right at the target stub
|
|
424
|
+
*
|
|
425
|
+
* Ignored when bounds are missing (we fall back to the legacy L-route)
|
|
426
|
+
* or when A* is engaged (it picks bends from path-cost, not position).
|
|
427
|
+
*/
|
|
428
|
+
stepPosition?: number;
|
|
415
429
|
}
|
|
416
430
|
/**
|
|
417
431
|
* Compute an orthogonal (right-angled) path between two points.
|
|
@@ -483,6 +497,23 @@ interface BezierPathOptions {
|
|
|
483
497
|
* point when the caller doesn't already know it.
|
|
484
498
|
*/
|
|
485
499
|
declare function getBezierPath(opts: BezierPathOptions): string;
|
|
500
|
+
/**
|
|
501
|
+
* Compute the intersection of the line from `box`'s centre to
|
|
502
|
+
* `opposite` with the rectangle's border. Returns the border point and
|
|
503
|
+
* the side it sits on.
|
|
504
|
+
*
|
|
505
|
+
* This is the "floating anchor" trick used by React Flow's
|
|
506
|
+
* FloatingEdges example and most graph viz libraries: instead of
|
|
507
|
+
* binding an endpoint to a fixed corner or midpoint, you bind it to
|
|
508
|
+
* the centre and let it slide along the perimeter following the
|
|
509
|
+
* direction to the other node. The result is dramatically cleaner on
|
|
510
|
+
* dense / unstructured graphs because every edge picks the closest
|
|
511
|
+
* face automatically as nodes are dragged around.
|
|
512
|
+
*/
|
|
513
|
+
declare function floatingAnchor(box: AABB, opposite: Point): {
|
|
514
|
+
point: Point;
|
|
515
|
+
side: Side;
|
|
516
|
+
};
|
|
486
517
|
/**
|
|
487
518
|
* Pick the side of `box` whose midpoint is closest to `target`. Same
|
|
488
519
|
* logic as the internal helper used by orthogonal routing — exposed
|
|
@@ -490,6 +521,104 @@ declare function getBezierPath(opts: BezierPathOptions): string;
|
|
|
490
521
|
* it.
|
|
491
522
|
*/
|
|
492
523
|
declare function nearestSide(box: AABB, target: Point): Side;
|
|
524
|
+
/**
|
|
525
|
+
* Resolved edge endpoint geometry — output of {@link resolveEdgeEndpoint}.
|
|
526
|
+
*
|
|
527
|
+
* `point` is in world space, sitting on (or just outside of) the
|
|
528
|
+
* `box` border. `side` records which face the connector exits / enters
|
|
529
|
+
* so downstream consumers (bezier control handles, orthogonal stubs)
|
|
530
|
+
* can stay perpendicular.
|
|
531
|
+
*/
|
|
532
|
+
interface ResolvedEndpoint {
|
|
533
|
+
point: Point;
|
|
534
|
+
side: Side;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Compute the intersection of the segment between the centre of
|
|
538
|
+
* `intersectionBox` and `targetCenter` with the perimeter of
|
|
539
|
+
* `intersectionBox`.
|
|
540
|
+
*
|
|
541
|
+
* **Source / credit.** Direct port of the algorithm used by xyflow
|
|
542
|
+
* (React Flow) for FloatingEdges and by mxGraph / draw.io's
|
|
543
|
+
* `mxPerimeter.RectanglePerimeter`. The closed-form derivation is in
|
|
544
|
+
* https://math.stackexchange.com/questions/1724792 — it parametrises
|
|
545
|
+
* the rectangle as the manhattan-norm rhombus `|x| + |y| = 1` and
|
|
546
|
+
* solves for the unit intersection in one step, which is faster and
|
|
547
|
+
* numerically more stable than the angle-based formulation.
|
|
548
|
+
*
|
|
549
|
+
* We use this as the canonical endpoint resolver for every routing
|
|
550
|
+
* mode (`straight` / `orthogonal` / `bezier`). The visible benefit is
|
|
551
|
+
* stability: nothing in this function depends on user-supplied
|
|
552
|
+
* anchors — the side, the point, the angle of approach are all pure
|
|
553
|
+
* functions of the two rectangles' bounding boxes, so dragging a node
|
|
554
|
+
* around never produces the "wrong-side-of-the-box" / "edge cuts
|
|
555
|
+
* through the node body" artifacts that the anchor-honouring resolver
|
|
556
|
+
* used to leak.
|
|
557
|
+
*
|
|
558
|
+
* Returns the world-space intersection point. When `box` has zero
|
|
559
|
+
* area (degenerate node) the function returns its centre so callers
|
|
560
|
+
* can still draw a useful edge to it.
|
|
561
|
+
*/
|
|
562
|
+
declare function getNodeIntersection(intersectionBox: AABB, targetCenter: Point): Point;
|
|
563
|
+
/**
|
|
564
|
+
* Classify which face of `box` the `intersectionPoint` sits on.
|
|
565
|
+
* Companion to {@link getNodeIntersection} — together they replace the
|
|
566
|
+
* old anchor-driven endpoint resolver.
|
|
567
|
+
*
|
|
568
|
+
* The function rounds both the box position and the point to integer
|
|
569
|
+
* pixels (matches the xyflow heuristic) before comparing, which keeps
|
|
570
|
+
* floating-point noise from picking the wrong side when an
|
|
571
|
+
* intersection lands almost exactly on a corner.
|
|
572
|
+
*/
|
|
573
|
+
declare function getEdgePosition(box: AABB, intersectionPoint: Point): Side;
|
|
574
|
+
/**
|
|
575
|
+
* Single canonical edge endpoint resolver used by `<Diagram>` for
|
|
576
|
+
* every routing mode. Wraps {@link getNodeIntersection} +
|
|
577
|
+
* {@link getEdgePosition} into the `{ point, side }` shape the
|
|
578
|
+
* adapters expect, and applies a small outward `padding` so the
|
|
579
|
+
* arrowhead marker has room to sit between the path tip and the node
|
|
580
|
+
* border (matches React Flow / draw.io behaviour).
|
|
581
|
+
*
|
|
582
|
+
* **Important: anchor information is intentionally ignored here.**
|
|
583
|
+
* The `<Diagram>` API still accepts `from: { anchor: 'nw' }` etc. for
|
|
584
|
+
* back-compat and semantic intent, but the visual layer now derives
|
|
585
|
+
* the endpoint purely from bounding boxes. This is the floating-edges
|
|
586
|
+
* approach taken by every mainstream diagram library — it's what
|
|
587
|
+
* makes drag-around-the-canvas behaviour feel correct on randomly
|
|
588
|
+
* laid-out graphs.
|
|
589
|
+
*/
|
|
590
|
+
declare function resolveEdgeEndpoint(sourceBox: AABB, targetBox: AABB, padding?: number): ResolvedEndpoint;
|
|
591
|
+
/**
|
|
592
|
+
* Decide which face an endpoint sits on, combining two cues:
|
|
593
|
+
*
|
|
594
|
+
* 1. **Anchor position** — if `anchor` is already at one of `box`'s
|
|
595
|
+
* borders (within `tolerance`), the corresponding face is returned
|
|
596
|
+
* as-is. This lets consumers express intent ("exit from the right
|
|
597
|
+
* side") simply by placing the anchor on that border.
|
|
598
|
+
*
|
|
599
|
+
* 2. **Geometry fallback** — when the anchor is interior to `box`
|
|
600
|
+
* (or no border match) we fall back to {@link nearestSide} pointed
|
|
601
|
+
* at the opposite endpoint. This is the right answer for centre
|
|
602
|
+
* anchors and produces stable sides for big graphs where every
|
|
603
|
+
* pair of nodes has a different relative position.
|
|
604
|
+
*
|
|
605
|
+
* Returns the side along with a `confident` flag — `true` when the
|
|
606
|
+
* decision came from the anchor position (case 1), `false` when it
|
|
607
|
+
* fell back to geometry (case 2). Consumers that want to override the
|
|
608
|
+
* fallback in special cases can branch on the flag.
|
|
609
|
+
*
|
|
610
|
+
* Corner anchors (touching two borders simultaneously like `nw` / `ne`)
|
|
611
|
+
* are disambiguated by which face points more strongly at the opposite
|
|
612
|
+
* endpoint. **The disambiguation is also clamped to faces that face the
|
|
613
|
+
* other node** — a `ne` anchor on the source whose target sits to its
|
|
614
|
+
* left will pick `left` instead of `right`, because using `right` would
|
|
615
|
+
* route the edge AWAY from the target. This is the key fix for the
|
|
616
|
+
* "stub-then-180°-loop" artifact visible on randomly-laid-out graphs.
|
|
617
|
+
*/
|
|
618
|
+
declare function inferSide(box: AABB, anchor: Point, opposite: Point, tolerance?: number): {
|
|
619
|
+
side: Side;
|
|
620
|
+
confident: boolean;
|
|
621
|
+
};
|
|
493
622
|
|
|
494
623
|
/**
|
|
495
624
|
* Oriented Bounding Box — a rectangle in world space described by its centre,
|
|
@@ -539,4 +668,4 @@ declare function aabbFromOBB(obb: OBB): AABB;
|
|
|
539
668
|
*/
|
|
540
669
|
declare function obbContainsPoint(obb: OBB, p: Point): boolean;
|
|
541
670
|
|
|
542
|
-
export { type AABB, type Anchor, type AnchorId, type AnchorPolicy, type BezierPathOptions, type EdgeEndpoint, type EdgeId, type EdgeInit, type GraphEdge, type GraphEngine, type GraphEngineOptions, type GraphNode, type GraphState, type NodeId, type NodeInit, type NodeTransform, type NodeUpdate, type OBB$1 as OBB, type Outline, type Point, type RouteOrthogonalOptions, type Side, aabbFromOBB, createGraph, getBezierPath, nearestSide, obbContainsPoint, obbCorners, pointsToPath, pointsToRoundedPath, routeOrthogonal };
|
|
671
|
+
export { type AABB, type Anchor, type AnchorId, type AnchorPolicy, type BezierPathOptions, type EdgeEndpoint, type EdgeId, type EdgeInit, type GraphEdge, type GraphEngine, type GraphEngineOptions, type GraphNode, type GraphState, type NodeId, type NodeInit, type NodeTransform, type NodeUpdate, type OBB$1 as OBB, type Outline, type Point, type ResolvedEndpoint, type RouteOrthogonalOptions, type Side, aabbFromOBB, createGraph, floatingAnchor, getBezierPath, getEdgePosition, getNodeIntersection, inferSide, nearestSide, obbContainsPoint, obbCorners, pointsToPath, pointsToRoundedPath, resolveEdgeEndpoint, routeOrthogonal };
|
package/dist/graph.js
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import {
|
|
2
2
|
aabbFromOBB,
|
|
3
3
|
createGraph,
|
|
4
|
+
floatingAnchor,
|
|
4
5
|
getBezierPath,
|
|
6
|
+
getEdgePosition,
|
|
7
|
+
getNodeIntersection,
|
|
8
|
+
inferSide,
|
|
5
9
|
nearestSide,
|
|
6
10
|
obbContainsPoint,
|
|
7
11
|
obbCorners,
|
|
8
12
|
pointsToPath,
|
|
9
13
|
pointsToRoundedPath,
|
|
14
|
+
resolveEdgeEndpoint,
|
|
10
15
|
routeOrthogonal
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-7FA2TBSZ.js";
|
|
12
17
|
import "./chunk-5CCD5Q4B.js";
|
|
13
18
|
export {
|
|
14
19
|
aabbFromOBB,
|
|
15
20
|
createGraph,
|
|
21
|
+
floatingAnchor,
|
|
16
22
|
getBezierPath,
|
|
23
|
+
getEdgePosition,
|
|
24
|
+
getNodeIntersection,
|
|
25
|
+
inferSide,
|
|
17
26
|
nearestSide,
|
|
18
27
|
obbContainsPoint,
|
|
19
28
|
obbCorners,
|
|
20
29
|
pointsToPath,
|
|
21
30
|
pointsToRoundedPath,
|
|
31
|
+
resolveEdgeEndpoint,
|
|
22
32
|
routeOrthogonal
|
|
23
33
|
};
|
|
24
34
|
//# sourceMappingURL=graph.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -38,8 +38,12 @@ __export(src_exports, {
|
|
|
38
38
|
createTypedCore: () => createTypedCore,
|
|
39
39
|
createVirtual: () => createVirtual,
|
|
40
40
|
detectConflicts: () => detectConflicts,
|
|
41
|
+
floatingAnchor: () => floatingAnchor,
|
|
41
42
|
freezeMiddleware: () => freezeMiddleware,
|
|
42
43
|
getBezierPath: () => getBezierPath,
|
|
44
|
+
getEdgePosition: () => getEdgePosition,
|
|
45
|
+
getNodeIntersection: () => getNodeIntersection,
|
|
46
|
+
inferSide: () => inferSide,
|
|
43
47
|
isAvailable: () => isAvailable,
|
|
44
48
|
listReservedPrefixes: () => listReservedPrefixes,
|
|
45
49
|
loggerMiddleware: () => loggerMiddleware,
|
|
@@ -52,6 +56,7 @@ __export(src_exports, {
|
|
|
52
56
|
pointsToPath: () => pointsToPath,
|
|
53
57
|
pointsToRoundedPath: () => pointsToRoundedPath,
|
|
54
58
|
reservePrefix: () => reservePrefix,
|
|
59
|
+
resolveEdgeEndpoint: () => resolveEdgeEndpoint,
|
|
55
60
|
resolveOperator: () => resolveOperator,
|
|
56
61
|
routeOrthogonal: () => routeOrthogonal,
|
|
57
62
|
validationMiddleware: () => validationMiddleware
|
|
@@ -2013,22 +2018,27 @@ function createGraph(core, options) {
|
|
|
2013
2018
|
function routeOrthogonal(start, end, options) {
|
|
2014
2019
|
const opts = typeof options === "string" ? { preferred: options } : options ?? {};
|
|
2015
2020
|
const gridSize = opts.gridSize ?? 10;
|
|
2016
|
-
const stubLength = opts.stubLength ?? Math.max(
|
|
2021
|
+
const stubLength = opts.stubLength ?? Math.max(20, gridSize);
|
|
2022
|
+
const stepPosition = opts.stepPosition ?? 0.5;
|
|
2017
2023
|
let sExit = null;
|
|
2018
2024
|
let sStub = null;
|
|
2019
2025
|
let sCorner = null;
|
|
2026
|
+
let sSide = null;
|
|
2020
2027
|
if (opts.sourceBounds) {
|
|
2021
|
-
const exitInfo = exitOnNearestSide(opts.sourceBounds, end);
|
|
2028
|
+
const exitInfo = exitOnNearestSide(opts.sourceBounds, end, start);
|
|
2022
2029
|
sExit = exitInfo.point;
|
|
2030
|
+
sSide = exitInfo.side;
|
|
2023
2031
|
sStub = extrudePoint(exitInfo.point, exitInfo.side, stubLength);
|
|
2024
2032
|
sCorner = orthoBridge(start, sExit, exitInfo.side);
|
|
2025
2033
|
}
|
|
2026
2034
|
let tExit = null;
|
|
2027
2035
|
let tStub = null;
|
|
2028
2036
|
let tCorner = null;
|
|
2037
|
+
let tSide = null;
|
|
2029
2038
|
if (opts.targetBounds) {
|
|
2030
|
-
const enterInfo = exitOnNearestSide(opts.targetBounds, start);
|
|
2039
|
+
const enterInfo = exitOnNearestSide(opts.targetBounds, start, end);
|
|
2031
2040
|
tExit = enterInfo.point;
|
|
2041
|
+
tSide = enterInfo.side;
|
|
2032
2042
|
tStub = extrudePoint(enterInfo.point, enterInfo.side, stubLength);
|
|
2033
2043
|
tCorner = orthoBridge(end, tExit, enterInfo.side);
|
|
2034
2044
|
}
|
|
@@ -2038,8 +2048,12 @@ function routeOrthogonal(start, end, options) {
|
|
|
2038
2048
|
const filteredObstacles = obstacles.length > 0 && (opts.sourceBounds || opts.targetBounds) ? obstacles.filter((o) => o !== opts.sourceBounds && o !== opts.targetBounds) : obstacles;
|
|
2039
2049
|
let core;
|
|
2040
2050
|
if (filteredObstacles.length === 0) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2051
|
+
if (sSide && tSide) {
|
|
2052
|
+
core = smoothStepRoute(coreStart, sSide, coreEnd, tSide, stepPosition);
|
|
2053
|
+
} else {
|
|
2054
|
+
const snap2 = opts.snap ?? gridSize;
|
|
2055
|
+
core = lRoute(coreStart, coreEnd, opts.preferred ?? "auto", snap2);
|
|
2056
|
+
}
|
|
2043
2057
|
} else {
|
|
2044
2058
|
const inflate = opts.inflate ?? 0;
|
|
2045
2059
|
const maxNodes = opts.maxNodes ?? 5e3;
|
|
@@ -2063,6 +2077,68 @@ function routeOrthogonal(start, end, options) {
|
|
|
2063
2077
|
if (!pointsEqual(end, points[points.length - 1])) pushIfDistinct(points, end);
|
|
2064
2078
|
return compressCollinear(points);
|
|
2065
2079
|
}
|
|
2080
|
+
function smoothStepRoute(coreStart, sSide, coreEnd, tSide, stepPosition) {
|
|
2081
|
+
const [sx, sy] = coreStart;
|
|
2082
|
+
const [tx, ty] = coreEnd;
|
|
2083
|
+
const sAxis = sideAxis(sSide);
|
|
2084
|
+
const tAxis = sideAxis(tSide);
|
|
2085
|
+
const sDir = sideDir(sSide);
|
|
2086
|
+
const tDir = sideDir(tSide);
|
|
2087
|
+
if (sAxis === tAxis) {
|
|
2088
|
+
if (sDir + tDir === 0) {
|
|
2089
|
+
if (sAxis === "x") {
|
|
2090
|
+
const cx = sx + (tx - sx) * stepPosition;
|
|
2091
|
+
return [
|
|
2092
|
+
[sx, sy],
|
|
2093
|
+
[cx, sy],
|
|
2094
|
+
[cx, ty],
|
|
2095
|
+
[tx, ty]
|
|
2096
|
+
];
|
|
2097
|
+
}
|
|
2098
|
+
const cy = sy + (ty - sy) * stepPosition;
|
|
2099
|
+
return [
|
|
2100
|
+
[sx, sy],
|
|
2101
|
+
[sx, cy],
|
|
2102
|
+
[tx, cy],
|
|
2103
|
+
[tx, ty]
|
|
2104
|
+
];
|
|
2105
|
+
}
|
|
2106
|
+
if (sAxis === "x") {
|
|
2107
|
+
const extX = sDir > 0 ? Math.max(sx, tx) : Math.min(sx, tx);
|
|
2108
|
+
return [
|
|
2109
|
+
[sx, sy],
|
|
2110
|
+
[extX, sy],
|
|
2111
|
+
[extX, ty],
|
|
2112
|
+
[tx, ty]
|
|
2113
|
+
];
|
|
2114
|
+
}
|
|
2115
|
+
const extY = sDir > 0 ? Math.max(sy, ty) : Math.min(sy, ty);
|
|
2116
|
+
return [
|
|
2117
|
+
[sx, sy],
|
|
2118
|
+
[sx, extY],
|
|
2119
|
+
[tx, extY],
|
|
2120
|
+
[tx, ty]
|
|
2121
|
+
];
|
|
2122
|
+
}
|
|
2123
|
+
if (sAxis === "x") {
|
|
2124
|
+
return [
|
|
2125
|
+
[sx, sy],
|
|
2126
|
+
[tx, sy],
|
|
2127
|
+
[tx, ty]
|
|
2128
|
+
];
|
|
2129
|
+
}
|
|
2130
|
+
return [
|
|
2131
|
+
[sx, sy],
|
|
2132
|
+
[sx, ty],
|
|
2133
|
+
[tx, ty]
|
|
2134
|
+
];
|
|
2135
|
+
}
|
|
2136
|
+
function sideAxis(s) {
|
|
2137
|
+
return s === "left" || s === "right" ? "x" : "y";
|
|
2138
|
+
}
|
|
2139
|
+
function sideDir(s) {
|
|
2140
|
+
return s === "right" || s === "bottom" ? 1 : -1;
|
|
2141
|
+
}
|
|
2066
2142
|
function pointsToPath(points) {
|
|
2067
2143
|
if (points.length === 0) return "";
|
|
2068
2144
|
const [head, ...rest] = points;
|
|
@@ -2122,6 +2198,31 @@ function getBezierPath(opts) {
|
|
|
2122
2198
|
const [c2x, c2y] = controlPoint(opts.targetSide, tx, ty, sx, sy, curvature);
|
|
2123
2199
|
return `M ${sx} ${sy} C ${c1x} ${c1y} ${c2x} ${c2y} ${tx} ${ty}`;
|
|
2124
2200
|
}
|
|
2201
|
+
function floatingAnchor(box, opposite) {
|
|
2202
|
+
const cx = box.x + box.w / 2;
|
|
2203
|
+
const cy = box.y + box.h / 2;
|
|
2204
|
+
const dx = opposite[0] - cx;
|
|
2205
|
+
const dy = opposite[1] - cy;
|
|
2206
|
+
if (dx === 0 && dy === 0) {
|
|
2207
|
+
return { point: [box.x + box.w, cy], side: "right" };
|
|
2208
|
+
}
|
|
2209
|
+
const hw = box.w / 2;
|
|
2210
|
+
const hh = box.h / 2;
|
|
2211
|
+
const tx = dx === 0 ? Infinity : hw / Math.abs(dx);
|
|
2212
|
+
const ty = dy === 0 ? Infinity : hh / Math.abs(dy);
|
|
2213
|
+
if (tx < ty) {
|
|
2214
|
+
const sign2 = dx > 0 ? 1 : -1;
|
|
2215
|
+
return {
|
|
2216
|
+
point: [cx + sign2 * hw, cy + sign2 * (hw / Math.abs(dx)) * dy],
|
|
2217
|
+
side: sign2 > 0 ? "right" : "left"
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
const sign = dy > 0 ? 1 : -1;
|
|
2221
|
+
return {
|
|
2222
|
+
point: [cx + sign * (hh / Math.abs(dy)) * dx, cy + sign * hh],
|
|
2223
|
+
side: sign > 0 ? "bottom" : "top"
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2125
2226
|
function nearestSide(box, target) {
|
|
2126
2227
|
const cx = box.x + box.w / 2;
|
|
2127
2228
|
const cy = box.y + box.h / 2;
|
|
@@ -2130,9 +2231,107 @@ function nearestSide(box, target) {
|
|
|
2130
2231
|
if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
|
|
2131
2232
|
return dy >= 0 ? "bottom" : "top";
|
|
2132
2233
|
}
|
|
2133
|
-
function
|
|
2234
|
+
function getNodeIntersection(intersectionBox, targetCenter) {
|
|
2235
|
+
const w = intersectionBox.w / 2;
|
|
2236
|
+
const h = intersectionBox.h / 2;
|
|
2237
|
+
const x2 = intersectionBox.x + w;
|
|
2238
|
+
const y2 = intersectionBox.y + h;
|
|
2239
|
+
if (w === 0 || h === 0) return [x2, y2];
|
|
2240
|
+
const x1 = targetCenter[0];
|
|
2241
|
+
const y1 = targetCenter[1];
|
|
2242
|
+
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
|
|
2243
|
+
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
|
|
2244
|
+
const denom = Math.abs(xx1) + Math.abs(yy1);
|
|
2245
|
+
if (denom === 0) {
|
|
2246
|
+
return [intersectionBox.x + intersectionBox.w, y2];
|
|
2247
|
+
}
|
|
2248
|
+
const a = 1 / denom;
|
|
2249
|
+
const xx3 = a * xx1;
|
|
2250
|
+
const yy3 = a * yy1;
|
|
2251
|
+
const x = w * (xx3 + yy3) + x2;
|
|
2252
|
+
const y = h * (-xx3 + yy3) + y2;
|
|
2253
|
+
return [x, y];
|
|
2254
|
+
}
|
|
2255
|
+
function getEdgePosition(box, intersectionPoint) {
|
|
2256
|
+
const nx = Math.round(box.x);
|
|
2257
|
+
const ny = Math.round(box.y);
|
|
2258
|
+
const px = Math.round(intersectionPoint[0]);
|
|
2259
|
+
const py = Math.round(intersectionPoint[1]);
|
|
2260
|
+
if (px <= nx + 1) return "left";
|
|
2261
|
+
if (px >= nx + box.w - 1) return "right";
|
|
2262
|
+
if (py <= ny + 1) return "top";
|
|
2263
|
+
if (py >= ny + box.h - 1) return "bottom";
|
|
2264
|
+
return "top";
|
|
2265
|
+
}
|
|
2266
|
+
function resolveEdgeEndpoint(sourceBox, targetBox, padding = 0) {
|
|
2267
|
+
const targetCenter = [targetBox.x + targetBox.w / 2, targetBox.y + targetBox.h / 2];
|
|
2268
|
+
const raw = getNodeIntersection(sourceBox, targetCenter);
|
|
2269
|
+
const side = getEdgePosition(sourceBox, raw);
|
|
2270
|
+
if (padding === 0) return { point: raw, side };
|
|
2271
|
+
let point = raw;
|
|
2272
|
+
switch (side) {
|
|
2273
|
+
case "right":
|
|
2274
|
+
point = [raw[0] + padding, raw[1]];
|
|
2275
|
+
break;
|
|
2276
|
+
case "left":
|
|
2277
|
+
point = [raw[0] - padding, raw[1]];
|
|
2278
|
+
break;
|
|
2279
|
+
case "bottom":
|
|
2280
|
+
point = [raw[0], raw[1] + padding];
|
|
2281
|
+
break;
|
|
2282
|
+
case "top":
|
|
2283
|
+
point = [raw[0], raw[1] - padding];
|
|
2284
|
+
break;
|
|
2285
|
+
}
|
|
2286
|
+
return { point, side };
|
|
2287
|
+
}
|
|
2288
|
+
function inferSide(box, anchor, opposite, tolerance = 1) {
|
|
2289
|
+
const onLeft = Math.abs(anchor[0] - box.x) <= tolerance;
|
|
2290
|
+
const onRight = Math.abs(anchor[0] - (box.x + box.w)) <= tolerance;
|
|
2291
|
+
const onTop = Math.abs(anchor[1] - box.y) <= tolerance;
|
|
2292
|
+
const onBottom = Math.abs(anchor[1] - (box.y + box.h)) <= tolerance;
|
|
2293
|
+
const horizontalHit = onLeft || onRight;
|
|
2294
|
+
const verticalHit = onTop || onBottom;
|
|
2295
|
+
if (horizontalHit && verticalHit) {
|
|
2296
|
+
const cx = box.x + box.w / 2;
|
|
2297
|
+
const cy = box.y + box.h / 2;
|
|
2298
|
+
const dx = opposite[0] - cx;
|
|
2299
|
+
const dy = opposite[1] - cy;
|
|
2300
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
2301
|
+
return { side: onRight ? "right" : "left", confident: true };
|
|
2302
|
+
}
|
|
2303
|
+
return { side: onBottom ? "bottom" : "top", confident: true };
|
|
2304
|
+
}
|
|
2305
|
+
if (onLeft) return { side: "left", confident: true };
|
|
2306
|
+
if (onRight) return { side: "right", confident: true };
|
|
2307
|
+
if (onTop) return { side: "top", confident: true };
|
|
2308
|
+
if (onBottom) return { side: "bottom", confident: true };
|
|
2309
|
+
return { side: nearestSide(box, opposite), confident: false };
|
|
2310
|
+
}
|
|
2311
|
+
function exitOnNearestSide(box, target, anchor) {
|
|
2134
2312
|
const cx = box.x + box.w / 2;
|
|
2135
2313
|
const cy = box.y + box.h / 2;
|
|
2314
|
+
if (anchor) {
|
|
2315
|
+
const tolerance = 1;
|
|
2316
|
+
const onLeft = Math.abs(anchor[0] - box.x) <= tolerance;
|
|
2317
|
+
const onRight = Math.abs(anchor[0] - (box.x + box.w)) <= tolerance;
|
|
2318
|
+
const onTop = Math.abs(anchor[1] - box.y) <= tolerance;
|
|
2319
|
+
const onBottom = Math.abs(anchor[1] - (box.y + box.h)) <= tolerance;
|
|
2320
|
+
const horizontalHit = onLeft || onRight;
|
|
2321
|
+
const verticalHit = onTop || onBottom;
|
|
2322
|
+
if (horizontalHit && verticalHit) {
|
|
2323
|
+
const dx2 = target[0] - cx;
|
|
2324
|
+
const dy2 = target[1] - cy;
|
|
2325
|
+
if (Math.abs(dx2) >= Math.abs(dy2)) {
|
|
2326
|
+
return onRight ? { point: [box.x + box.w, cy], side: "right" } : { point: [box.x, cy], side: "left" };
|
|
2327
|
+
}
|
|
2328
|
+
return onBottom ? { point: [cx, box.y + box.h], side: "bottom" } : { point: [cx, box.y], side: "top" };
|
|
2329
|
+
}
|
|
2330
|
+
if (onRight) return { point: [box.x + box.w, cy], side: "right" };
|
|
2331
|
+
if (onLeft) return { point: [box.x, cy], side: "left" };
|
|
2332
|
+
if (onBottom) return { point: [cx, box.y + box.h], side: "bottom" };
|
|
2333
|
+
if (onTop) return { point: [cx, box.y], side: "top" };
|
|
2334
|
+
}
|
|
2136
2335
|
const dx = target[0] - cx;
|
|
2137
2336
|
const dy = target[1] - cy;
|
|
2138
2337
|
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
@@ -3635,8 +3834,12 @@ function createCalendar(core, options) {
|
|
|
3635
3834
|
createTypedCore,
|
|
3636
3835
|
createVirtual,
|
|
3637
3836
|
detectConflicts,
|
|
3837
|
+
floatingAnchor,
|
|
3638
3838
|
freezeMiddleware,
|
|
3639
3839
|
getBezierPath,
|
|
3840
|
+
getEdgePosition,
|
|
3841
|
+
getNodeIntersection,
|
|
3842
|
+
inferSide,
|
|
3640
3843
|
isAvailable,
|
|
3641
3844
|
listReservedPrefixes,
|
|
3642
3845
|
loggerMiddleware,
|
|
@@ -3649,6 +3852,7 @@ function createCalendar(core, options) {
|
|
|
3649
3852
|
pointsToPath,
|
|
3650
3853
|
pointsToRoundedPath,
|
|
3651
3854
|
reservePrefix,
|
|
3855
|
+
resolveEdgeEndpoint,
|
|
3652
3856
|
resolveOperator,
|
|
3653
3857
|
routeOrthogonal,
|
|
3654
3858
|
validationMiddleware
|