@statelyai/layout 0.0.4 → 0.0.5
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/elkjs/index.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-Bp8MkxWK.mjs → layered-BYhTymAk.mjs} +84 -0
- package/dist/{spore-ePWz2LtF.mjs → spore-B504wOFR.mjs} +1 -1
- package/package.json +2 -1
- package/src/layered/index.ts +17 -0
- package/src/layered/separate-exterior-labels.ts +139 -0
package/dist/elkjs/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-
|
|
2
|
-
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-
|
|
1
|
+
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-BYhTymAk.mjs";
|
|
2
|
+
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-B504wOFR.mjs";
|
|
3
3
|
import { createGraph } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/elkjs/index.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-
|
|
2
|
-
import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-
|
|
1
|
+
import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-BYhTymAk.mjs";
|
|
2
|
+
import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-B504wOFR.mjs";
|
|
3
3
|
import { getGraphIssues } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/layout.ts
|
package/dist/layered/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-
|
|
1
|
+
import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-BYhTymAk.mjs";
|
|
2
2
|
|
|
3
3
|
export { assignLayersByBreadthFirstModelOrder, assignLayersByDepthFirstModelOrder, assignLayersByLongestPath, assignLayersByLongestPathToSink, assignLayersInteractively, assignLayersWithCoffmanGraham, assignLayersWithMinWidth, assignLayersWithNetworkSimplex, assignLayersWithStretchWidth, breakCyclesByModelOrder, breakCyclesByStronglyConnectedConnectivity, breakCyclesByStronglyConnectedNodeType, breakCyclesGreedily, breakCyclesGreedilyByModelOrder, breakCyclesInteractively, breakCyclesWithDepthFirstSearch, breakCyclesWithModelOrderBreadthFirstSearch, breakCyclesWithModelOrderDepthFirstSearch, elkLayeredEnumValues, elkLayeredOptionDefinitions, fromElkLayeredOptionId, getLayeredLayout, layeredAlgorithm, minimizeCrossingsInteractively, minimizeCrossingsWithBarycenter, minimizeCrossingsWithMedian, minimizeCrossingsWithModelOrder, placeNodesInLayers, placeNodesInteractively, placeNodesWithBrandesKoepf, placeNodesWithLinearSegments, placeNodesWithNetworkSimplex, routeEdgesOrthogonally, routeEdgesWithPolylines, routeEdgesWithSplines, toElkLayeredOptions };
|
|
@@ -3699,6 +3699,77 @@ function getPolylineMidpoint(points) {
|
|
|
3699
3699
|
};
|
|
3700
3700
|
}
|
|
3701
3701
|
|
|
3702
|
+
//#endregion
|
|
3703
|
+
//#region src/layered/separate-exterior-labels.ts
|
|
3704
|
+
/** Separates movable inline labels and their exterior orthogonal route tracks. */
|
|
3705
|
+
function separateExteriorLabels({ edges, nodeRects, direction, spacing, settings }) {
|
|
3706
|
+
const horizontalFlow = direction === "left" || direction === "right";
|
|
3707
|
+
const nodeCrossStart = Math.min(...nodeRects.map((rect) => horizontalFlow ? rect.y : rect.x));
|
|
3708
|
+
const nodeCrossEnd = Math.max(...nodeRects.map((rect) => horizontalFlow ? rect.y + rect.height : rect.x + rect.width));
|
|
3709
|
+
const flowStart = (rect) => horizontalFlow ? rect.x : rect.y;
|
|
3710
|
+
const flowEnd = (rect) => horizontalFlow ? rect.x + rect.width : rect.y + rect.height;
|
|
3711
|
+
const crossStart = (rect) => horizontalFlow ? rect.y : rect.x;
|
|
3712
|
+
const crossEnd = (rect) => horizontalFlow ? rect.y + rect.height : rect.x + rect.width;
|
|
3713
|
+
const overlaps = (left, right) => flowStart(left) < flowEnd(right) && flowEnd(left) > flowStart(right) && crossStart(left) < crossEnd(right) && crossEnd(left) > crossStart(right);
|
|
3714
|
+
const labelRectByEdgeId = new Map(edges.filter((edge) => edge.width > 0 && edge.height > 0).map((edge) => [String(edge.id), {
|
|
3715
|
+
x: edge.x,
|
|
3716
|
+
y: edge.y,
|
|
3717
|
+
width: edge.width,
|
|
3718
|
+
height: edge.height
|
|
3719
|
+
}]));
|
|
3720
|
+
const movableLabels = edges.flatMap((edge) => {
|
|
3721
|
+
const labelRect = labelRectByEdgeId.get(String(edge.id));
|
|
3722
|
+
const labelSettings = settings(edge);
|
|
3723
|
+
if (!labelRect || !labelSettings.inline || labelSettings.placement !== "CENTER") return [];
|
|
3724
|
+
const exteriorTrack = edge.points.flatMap((point, index) => {
|
|
3725
|
+
const next = edge.points[index + 1];
|
|
3726
|
+
if (!next) return [];
|
|
3727
|
+
const isFlowSegment = horizontalFlow ? point.y === next.y && point.x !== next.x : point.x === next.x && point.y !== next.y;
|
|
3728
|
+
const trackCross = horizontalFlow ? point.y : point.x;
|
|
3729
|
+
return isFlowSegment && (trackCross < nodeCrossStart || trackCross > nodeCrossEnd) ? [{
|
|
3730
|
+
cross: trackCross,
|
|
3731
|
+
length: horizontalFlow ? Math.abs(next.x - point.x) : Math.abs(next.y - point.y)
|
|
3732
|
+
}] : [];
|
|
3733
|
+
}).sort((left, right) => right.length - left.length)[0];
|
|
3734
|
+
if (!exteriorTrack) return [];
|
|
3735
|
+
return [{
|
|
3736
|
+
edge,
|
|
3737
|
+
exteriorTrack,
|
|
3738
|
+
labelRect,
|
|
3739
|
+
lowSide: exteriorTrack.cross < (nodeCrossStart + nodeCrossEnd) / 2
|
|
3740
|
+
}];
|
|
3741
|
+
});
|
|
3742
|
+
movableLabels.sort((left, right) => {
|
|
3743
|
+
if (left.lowSide !== right.lowSide) return left.lowSide ? -1 : 1;
|
|
3744
|
+
return (left.lowSide ? crossStart(left.labelRect) - crossStart(right.labelRect) : crossStart(right.labelRect) - crossStart(left.labelRect)) || flowStart(left.labelRect) - flowStart(right.labelRect) || (String(left.edge.id) < String(right.edge.id) ? -1 : String(left.edge.id) > String(right.edge.id) ? 1 : 0);
|
|
3745
|
+
});
|
|
3746
|
+
for (const { edge, exteriorTrack, labelRect, lowSide } of movableLabels) {
|
|
3747
|
+
const obstacles = [...nodeRects, ...[...labelRectByEdgeId.entries()].flatMap(([edgeId, rect]) => edgeId === String(edge.id) ? [] : [rect])];
|
|
3748
|
+
let totalDelta = 0;
|
|
3749
|
+
while (true) {
|
|
3750
|
+
const blockers = obstacles.filter((rect) => overlaps(labelRect, rect));
|
|
3751
|
+
if (blockers.length === 0) break;
|
|
3752
|
+
const delta = (lowSide ? Math.min(...blockers.map(crossStart)) - spacing - (horizontalFlow ? edge.height : edge.width) : Math.max(...blockers.map(crossEnd)) + spacing) - crossStart(labelRect);
|
|
3753
|
+
totalDelta += delta;
|
|
3754
|
+
if (horizontalFlow) labelRect.y += delta;
|
|
3755
|
+
else labelRect.x += delta;
|
|
3756
|
+
}
|
|
3757
|
+
if (totalDelta === 0) continue;
|
|
3758
|
+
if (horizontalFlow) edge.y += totalDelta;
|
|
3759
|
+
else edge.x += totalDelta;
|
|
3760
|
+
edge.points = edge.points.map((point) => {
|
|
3761
|
+
if ((horizontalFlow ? point.y : point.x) !== exteriorTrack.cross) return point;
|
|
3762
|
+
return horizontalFlow ? {
|
|
3763
|
+
...point,
|
|
3764
|
+
y: point.y + totalDelta
|
|
3765
|
+
} : {
|
|
3766
|
+
...point,
|
|
3767
|
+
x: point.x + totalDelta
|
|
3768
|
+
};
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3702
3773
|
//#endregion
|
|
3703
3774
|
//#region src/layered/network-simplex.ts
|
|
3704
3775
|
function connectedEdges(node) {
|
|
@@ -9289,6 +9360,19 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
9289
9360
|
routing: edgeRouting === "POLYLINE" ? "polyline" : edgeRouting === "SPLINES" ? "splines" : "orthogonal"
|
|
9290
9361
|
};
|
|
9291
9362
|
});
|
|
9363
|
+
if (edgeRouting === "ORTHOGONAL") separateExteriorLabels({
|
|
9364
|
+
edges,
|
|
9365
|
+
nodeRects: feedbackNodeRects,
|
|
9366
|
+
direction,
|
|
9367
|
+
spacing: Number(options.settings?.["spacing.edgeEdge"] ?? 10),
|
|
9368
|
+
settings: (edge) => {
|
|
9369
|
+
const edgeSettings = options.edgeSettings?.(edge);
|
|
9370
|
+
return {
|
|
9371
|
+
inline: edgeSettings?.["edgeLabels.inline"] === true,
|
|
9372
|
+
placement: edgeSettings?.["edgeLabels.placement"] ?? "CENTER"
|
|
9373
|
+
};
|
|
9374
|
+
}
|
|
9375
|
+
});
|
|
9292
9376
|
return {
|
|
9293
9377
|
...graph,
|
|
9294
9378
|
direction,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-
|
|
1
|
+
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-BYhTymAk.mjs";
|
|
2
2
|
import { getNodeSize } from "@statelyai/graph/layout";
|
|
3
3
|
|
|
4
4
|
//#region src/fixed.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/layout",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Extensible, graph-native layout algorithms for @statelyai/graph",
|
|
5
5
|
"license": "EPL-2.0 OR GPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@changesets/cli": "3.0.1",
|
|
48
48
|
"@codemirror/lang-javascript": "^6.2.5",
|
|
49
49
|
"@codemirror/lint": "^6.9.7",
|
|
50
|
+
"@statelyai/layout-pre-exterior-label-separation": "npm:@statelyai/layout@0.0.4",
|
|
50
51
|
"@types/node": "^25.0.3",
|
|
51
52
|
"@types/react": "^19.2.18",
|
|
52
53
|
"@types/react-dom": "^19.2.4",
|
package/src/layered/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Graph, GraphEdge, GraphNode, Point, VisualGraph, VisualNode } from "@statelyai/graph";
|
|
2
2
|
import { UnsupportedLayoutError } from "../errors";
|
|
3
3
|
import type { LayoutAlgorithm, LayoutExecutionContext } from "../types";
|
|
4
|
+
import { separateExteriorLabels } from "./separate-exterior-labels";
|
|
4
5
|
import {
|
|
5
6
|
assignLayersByLongestPath,
|
|
6
7
|
assignLayersByLongestPathToSink,
|
|
@@ -2582,6 +2583,22 @@ function runLayeredPipeline<N, E, G, P>(
|
|
|
2582
2583
|
};
|
|
2583
2584
|
});
|
|
2584
2585
|
|
|
2586
|
+
if (edgeRouting === "ORTHOGONAL") {
|
|
2587
|
+
separateExteriorLabels({
|
|
2588
|
+
edges,
|
|
2589
|
+
nodeRects: feedbackNodeRects,
|
|
2590
|
+
direction,
|
|
2591
|
+
spacing: Number(options.settings?.["spacing.edgeEdge"] ?? 10),
|
|
2592
|
+
settings: (edge) => {
|
|
2593
|
+
const edgeSettings = options.edgeSettings?.(edge);
|
|
2594
|
+
return {
|
|
2595
|
+
inline: edgeSettings?.["edgeLabels.inline"] === true,
|
|
2596
|
+
placement: edgeSettings?.["edgeLabels.placement"] ?? "CENTER",
|
|
2597
|
+
};
|
|
2598
|
+
},
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2585
2602
|
return {
|
|
2586
2603
|
...graph,
|
|
2587
2604
|
direction,
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { EntityRect, Point } from "@statelyai/graph";
|
|
2
|
+
|
|
3
|
+
export interface ExteriorLabelEdge {
|
|
4
|
+
id: string;
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
points: Point[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ExteriorLabelSettings {
|
|
13
|
+
inline: boolean;
|
|
14
|
+
placement: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface SeparateExteriorLabelsInput<E extends ExteriorLabelEdge> {
|
|
18
|
+
edges: E[];
|
|
19
|
+
nodeRects: readonly EntityRect[];
|
|
20
|
+
direction: "up" | "down" | "left" | "right";
|
|
21
|
+
spacing: number;
|
|
22
|
+
settings: (edge: E) => ExteriorLabelSettings;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Separates movable inline labels and their exterior orthogonal route tracks. */
|
|
26
|
+
export function separateExteriorLabels<E extends ExteriorLabelEdge>({
|
|
27
|
+
edges,
|
|
28
|
+
nodeRects,
|
|
29
|
+
direction,
|
|
30
|
+
spacing,
|
|
31
|
+
settings,
|
|
32
|
+
}: SeparateExteriorLabelsInput<E>): void {
|
|
33
|
+
const horizontalFlow = direction === "left" || direction === "right";
|
|
34
|
+
const nodeCrossStart = Math.min(...nodeRects.map((rect) => (horizontalFlow ? rect.y : rect.x)));
|
|
35
|
+
const nodeCrossEnd = Math.max(
|
|
36
|
+
...nodeRects.map((rect) => (horizontalFlow ? rect.y + rect.height : rect.x + rect.width)),
|
|
37
|
+
);
|
|
38
|
+
const flowStart = (rect: EntityRect): number => (horizontalFlow ? rect.x : rect.y);
|
|
39
|
+
const flowEnd = (rect: EntityRect): number =>
|
|
40
|
+
horizontalFlow ? rect.x + rect.width : rect.y + rect.height;
|
|
41
|
+
const crossStart = (rect: EntityRect): number => (horizontalFlow ? rect.y : rect.x);
|
|
42
|
+
const crossEnd = (rect: EntityRect): number =>
|
|
43
|
+
horizontalFlow ? rect.y + rect.height : rect.x + rect.width;
|
|
44
|
+
const overlaps = (left: EntityRect, right: EntityRect): boolean =>
|
|
45
|
+
flowStart(left) < flowEnd(right) &&
|
|
46
|
+
flowEnd(left) > flowStart(right) &&
|
|
47
|
+
crossStart(left) < crossEnd(right) &&
|
|
48
|
+
crossEnd(left) > crossStart(right);
|
|
49
|
+
type MutableRect = { x: number; y: number; width: number; height: number };
|
|
50
|
+
const labelRectByEdgeId = new Map<string, MutableRect>(
|
|
51
|
+
edges
|
|
52
|
+
.filter((edge) => edge.width > 0 && edge.height > 0)
|
|
53
|
+
.map((edge) => [
|
|
54
|
+
String(edge.id),
|
|
55
|
+
{ x: edge.x, y: edge.y, width: edge.width, height: edge.height },
|
|
56
|
+
]),
|
|
57
|
+
);
|
|
58
|
+
const movableLabels = edges.flatMap((edge) => {
|
|
59
|
+
const labelRect = labelRectByEdgeId.get(String(edge.id));
|
|
60
|
+
const labelSettings = settings(edge);
|
|
61
|
+
if (!labelRect || !labelSettings.inline || labelSettings.placement !== "CENTER") return [];
|
|
62
|
+
const exteriorTrack = edge.points
|
|
63
|
+
.flatMap((point, index) => {
|
|
64
|
+
const next = edge.points[index + 1];
|
|
65
|
+
if (!next) return [];
|
|
66
|
+
const isFlowSegment = horizontalFlow
|
|
67
|
+
? point.y === next.y && point.x !== next.x
|
|
68
|
+
: point.x === next.x && point.y !== next.y;
|
|
69
|
+
const trackCross = horizontalFlow ? point.y : point.x;
|
|
70
|
+
return isFlowSegment && (trackCross < nodeCrossStart || trackCross > nodeCrossEnd)
|
|
71
|
+
? [
|
|
72
|
+
{
|
|
73
|
+
cross: trackCross,
|
|
74
|
+
length: horizontalFlow ? Math.abs(next.x - point.x) : Math.abs(next.y - point.y),
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
: [];
|
|
78
|
+
})
|
|
79
|
+
.sort((left, right) => right.length - left.length)[0];
|
|
80
|
+
if (!exteriorTrack) return [];
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
edge,
|
|
84
|
+
exteriorTrack,
|
|
85
|
+
labelRect,
|
|
86
|
+
lowSide: exteriorTrack.cross < (nodeCrossStart + nodeCrossEnd) / 2,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
movableLabels.sort((left, right) => {
|
|
92
|
+
if (left.lowSide !== right.lowSide) return left.lowSide ? -1 : 1;
|
|
93
|
+
const crossDifference = left.lowSide
|
|
94
|
+
? crossStart(left.labelRect) - crossStart(right.labelRect)
|
|
95
|
+
: crossStart(right.labelRect) - crossStart(left.labelRect);
|
|
96
|
+
return (
|
|
97
|
+
crossDifference ||
|
|
98
|
+
flowStart(left.labelRect) - flowStart(right.labelRect) ||
|
|
99
|
+
(String(left.edge.id) < String(right.edge.id)
|
|
100
|
+
? -1
|
|
101
|
+
: String(left.edge.id) > String(right.edge.id)
|
|
102
|
+
? 1
|
|
103
|
+
: 0)
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const { edge, exteriorTrack, labelRect, lowSide } of movableLabels) {
|
|
108
|
+
const obstacles = [
|
|
109
|
+
...nodeRects,
|
|
110
|
+
...[...labelRectByEdgeId.entries()].flatMap(([edgeId, rect]) =>
|
|
111
|
+
edgeId === String(edge.id) ? [] : [rect],
|
|
112
|
+
),
|
|
113
|
+
];
|
|
114
|
+
let totalDelta = 0;
|
|
115
|
+
while (true) {
|
|
116
|
+
const blockers = obstacles.filter((rect) => overlaps(labelRect, rect));
|
|
117
|
+
if (blockers.length === 0) break;
|
|
118
|
+
const desiredCross = lowSide
|
|
119
|
+
? Math.min(...blockers.map(crossStart)) -
|
|
120
|
+
spacing -
|
|
121
|
+
(horizontalFlow ? edge.height : edge.width)
|
|
122
|
+
: Math.max(...blockers.map(crossEnd)) + spacing;
|
|
123
|
+
const delta = desiredCross - crossStart(labelRect);
|
|
124
|
+
totalDelta += delta;
|
|
125
|
+
if (horizontalFlow) labelRect.y += delta;
|
|
126
|
+
else labelRect.x += delta;
|
|
127
|
+
}
|
|
128
|
+
if (totalDelta === 0) continue;
|
|
129
|
+
if (horizontalFlow) edge.y += totalDelta;
|
|
130
|
+
else edge.x += totalDelta;
|
|
131
|
+
edge.points = edge.points.map((point) => {
|
|
132
|
+
const pointCross = horizontalFlow ? point.y : point.x;
|
|
133
|
+
if (pointCross !== exteriorTrack.cross) return point;
|
|
134
|
+
return horizontalFlow
|
|
135
|
+
? { ...point, y: point.y + totalDelta }
|
|
136
|
+
: { ...point, x: point.x + totalDelta };
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|