@statelyai/layout 0.1.3 → 0.3.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/NOTICE.md +43 -0
- package/README.md +153 -6
- package/dist/elkjs/index.cjs +9 -0
- package/dist/elkjs/index.d.cts +155 -0
- package/dist/elkjs/index.d.mts +51 -39
- package/dist/elkjs/index.mjs +3 -1484
- package/dist/elkjs/main.cjs +17 -0
- package/dist/elkjs/main.d.cts +10 -0
- package/dist/elkjs/main.d.mts +11 -0
- package/dist/elkjs/main.mjs +17 -0
- package/dist/elkjs/worker-api.cjs +3 -0
- package/dist/elkjs/worker-api.d.cts +3 -0
- package/dist/elkjs/worker-api.d.mts +3 -0
- package/dist/elkjs/worker-api.mjs +3 -0
- package/dist/elkjs/worker.cjs +86 -0
- package/dist/elkjs/worker.d.cts +17 -0
- package/dist/elkjs/worker.d.mts +17 -0
- package/dist/elkjs/worker.mjs +86 -0
- package/dist/elkjs-DaHMISnQ.mjs +1677 -0
- package/dist/elkjs-dMghiiWW.cjs +1706 -0
- package/dist/index-BEA_pzeT.d.cts +72 -0
- package/dist/{index-8xYkohbz.d.mts → index-D9dhxrRl.d.cts} +111 -5
- package/dist/index-DNTLRsDl.d.mts +72 -0
- package/dist/index-DNixtE3h.d.mts +1318 -0
- package/dist/index.cjs +215 -0
- package/dist/index.d.cts +78 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +25 -25
- package/dist/layered/index.cjs +38 -0
- package/dist/layered/index.d.cts +3 -0
- package/dist/layered/index.d.mts +3 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/layered-B2buJqMf.cjs +12009 -0
- package/dist/{layered-CoeH9LRO.mjs → layered-DGFTeQSl.mjs} +2323 -149
- package/dist/{spore-BtTSZbb6.mjs → layout-engine-Cs5tatA6.mjs} +159 -5
- package/dist/layout-engine-Dn1PiFwP.cjs +712 -0
- package/dist/public-types-9NCPoqbp.d.mts +109 -0
- package/dist/public-types-BcRZoJE3.d.cts +109 -0
- package/dist/routing/index.cjs +28 -0
- package/dist/routing/index.d.cts +3 -0
- package/dist/routing/index.d.mts +3 -0
- package/dist/routing/index.mjs +3 -0
- package/dist/routing-A2hrK9MP.mjs +2396 -0
- package/dist/routing-ujEP7Qh6.cjs +2606 -0
- package/dist/types-CT2akdK0.d.mts +133 -0
- package/dist/types-uxPlGRof.d.cts +133 -0
- package/dist/worker-client-BNjwT1AJ.mjs +92 -0
- package/dist/worker-client-BwGGRD5y.d.mts +19 -0
- package/dist/worker-client-D4oEvhAl.cjs +98 -0
- package/dist/worker-client-D4zNyPEI.d.cts +19 -0
- package/package.json +45 -10
- package/src/authoring/coordinates.ts +114 -0
- package/src/authoring/partial.ts +584 -0
- package/src/authoring/routing.ts +211 -0
- package/src/box.ts +22 -0
- package/src/constraints.ts +222 -0
- package/src/elkjs/index.ts +104 -39
- package/src/elkjs/main.ts +19 -0
- package/src/elkjs/public-types.ts +125 -0
- package/src/elkjs/routes.ts +147 -0
- package/src/elkjs/worker-api.ts +29 -0
- package/src/elkjs/worker-client.ts +138 -0
- package/src/elkjs/worker.ts +117 -0
- package/src/index.ts +19 -0
- package/src/internal/elkjs-compatibility.ts +17 -0
- package/src/internal/layout-engine.ts +126 -0
- package/src/layered/compound.ts +417 -0
- package/src/layered/index.ts +58 -105
- package/src/layered/types.ts +21 -0
- package/src/layout.ts +35 -28
- package/src/packing.ts +22 -0
- package/src/random.ts +75 -3
- package/src/routing/adapters.ts +111 -0
- package/src/routing/algorithms.ts +676 -0
- package/src/routing/coordination.ts +86 -0
- package/src/routing/engine.ts +473 -0
- package/src/routing/index.ts +68 -0
- package/src/routing/layout-cache.ts +65 -0
- package/src/routing/model.ts +218 -0
- package/src/routing/path.ts +324 -0
- package/src/routing/persistent.ts +117 -0
- package/src/routing/search.ts +276 -0
- package/src/routing/spatial.ts +78 -0
- package/src/routing/types.ts +146 -0
- package/src/types.ts +17 -2
package/src/elkjs/index.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { createGraph, type Graph } from "@statelyai/graph";
|
|
2
|
-
import { getBoxLayout } from "../box";
|
|
3
|
-
import { getFixedLayout } from "../fixed";
|
|
4
|
-
import { getLayeredLayout } from "../layered";
|
|
1
|
+
import { createGraph, type Graph, type VisualGraph } from "@statelyai/graph";
|
|
5
2
|
import {
|
|
6
3
|
elkLayeredOptionDefinitions,
|
|
7
4
|
type ElkLayeredOptionValueByName,
|
|
8
5
|
type LayeredAdvancedOptions,
|
|
9
6
|
} from "../layered/elk-options";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { getSporeCompactionLayout, getSporeOverlapRemovalLayout } from "../spore";
|
|
7
|
+
import { executeElkjs0111Layout } from "../internal/layout-engine";
|
|
8
|
+
import { elkjs0111ResultPolicy, type Elkjs0111ResultPolicy } from "../internal/elkjs-compatibility";
|
|
13
9
|
import type {
|
|
14
10
|
ElkConstructorArguments,
|
|
15
11
|
ElkLayoutAlgorithmDescription,
|
|
@@ -25,6 +21,11 @@ import type {
|
|
|
25
21
|
ElkShape,
|
|
26
22
|
LaidOutElkNode,
|
|
27
23
|
} from "./types";
|
|
24
|
+
import type {
|
|
25
|
+
ElkLayoutArguments as PublicElkLayoutArguments,
|
|
26
|
+
ElkNode as PublicElkNode,
|
|
27
|
+
LaidOutElkNode as PublicLaidOutElkNode,
|
|
28
|
+
} from "./public-types";
|
|
28
29
|
|
|
29
30
|
function isLayoutEdgeLabel(label: ElkLabel): boolean {
|
|
30
31
|
return (
|
|
@@ -34,24 +35,27 @@ function isLayoutEdgeLabel(label: ElkLabel): boolean {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export type {
|
|
37
|
-
|
|
38
|
+
ELK,
|
|
39
|
+
ELKConstructorArguments,
|
|
38
40
|
ElkCommonDescription,
|
|
39
|
-
ElkGraphElement,
|
|
40
|
-
ElkId,
|
|
41
41
|
ElkEdge,
|
|
42
42
|
ElkEdgeSection,
|
|
43
|
+
ElkExtendedEdge,
|
|
44
|
+
ElkGraphElement,
|
|
43
45
|
ElkLabel,
|
|
44
46
|
ElkLayoutArguments,
|
|
45
47
|
ElkLayoutAlgorithmDescription,
|
|
46
48
|
ElkLayoutCategoryDescription,
|
|
47
49
|
ElkLayoutOptionDescription,
|
|
48
|
-
ElkLogging,
|
|
49
50
|
ElkNode,
|
|
50
51
|
ElkPoint,
|
|
51
52
|
ElkPort,
|
|
53
|
+
ElkPrimitiveEdge,
|
|
52
54
|
ElkShape,
|
|
55
|
+
LayoutOptions,
|
|
53
56
|
LaidOutElkNode,
|
|
54
|
-
} from "./types";
|
|
57
|
+
} from "./public-types";
|
|
58
|
+
export type { ElkId, ElkLogging } from "./types";
|
|
55
59
|
|
|
56
60
|
export default class ELK {
|
|
57
61
|
readonly #options: ElkConstructorArguments;
|
|
@@ -119,7 +123,19 @@ export default class ELK {
|
|
|
119
123
|
|
|
120
124
|
terminateWorker(): void {}
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
layout<T extends PublicElkNode>(
|
|
127
|
+
graph: T,
|
|
128
|
+
arguments_?: PublicElkLayoutArguments,
|
|
129
|
+
): Promise<PublicElkNode & PublicLaidOutElkNode<T>>;
|
|
130
|
+
layout<T extends ElkNode>(graph: T, arguments_?: ElkLayoutArguments): Promise<LaidOutElkNode<T>>;
|
|
131
|
+
layout<T extends ElkNode>(
|
|
132
|
+
graph: T,
|
|
133
|
+
arguments_: ElkLayoutArguments = {},
|
|
134
|
+
): Promise<LaidOutElkNode<T>> {
|
|
135
|
+
return this.#layout(graph, arguments_);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async #layout<T extends ElkNode>(
|
|
123
139
|
graph: T,
|
|
124
140
|
arguments_: ElkLayoutArguments = {},
|
|
125
141
|
): Promise<LaidOutElkNode<T>> {
|
|
@@ -246,7 +262,7 @@ export default class ELK {
|
|
|
246
262
|
}
|
|
247
263
|
for (const child of graph.children ?? []) {
|
|
248
264
|
if ((child.children?.length ?? 0) === 0) continue;
|
|
249
|
-
await this
|
|
265
|
+
await this.#layout(child, {
|
|
250
266
|
...arguments_,
|
|
251
267
|
layoutOptions: {
|
|
252
268
|
...arguments_.layoutOptions,
|
|
@@ -361,7 +377,7 @@ export default class ELK {
|
|
|
361
377
|
children: [...(child.children ?? []), ...proxyByKind.values()],
|
|
362
378
|
edges: temporaryEdges,
|
|
363
379
|
};
|
|
364
|
-
await this
|
|
380
|
+
await this.#layout(temporaryChild, {
|
|
365
381
|
...arguments_,
|
|
366
382
|
layoutOptions: {
|
|
367
383
|
...arguments_.layoutOptions,
|
|
@@ -527,31 +543,49 @@ export default class ELK {
|
|
|
527
543
|
getOption(layoutOptions, "padding"),
|
|
528
544
|
algorithm === "layered" ? 12 : 0,
|
|
529
545
|
);
|
|
530
|
-
const laidOut =
|
|
531
|
-
|
|
532
|
-
|
|
546
|
+
const laidOut = await (algorithm === "sporeCompaction"
|
|
547
|
+
? executeElkjs0111Layout({
|
|
548
|
+
algorithm: "sporeCompaction",
|
|
549
|
+
graph: graph_,
|
|
550
|
+
options: {
|
|
533
551
|
padding,
|
|
534
552
|
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
|
|
553
|
+
},
|
|
554
|
+
})
|
|
555
|
+
: algorithm === "sporeOverlap"
|
|
556
|
+
? executeElkjs0111Layout({
|
|
557
|
+
algorithm: "sporeOverlap",
|
|
558
|
+
graph: graph_,
|
|
559
|
+
options: {
|
|
538
560
|
padding,
|
|
539
561
|
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
562
|
+
},
|
|
563
|
+
})
|
|
564
|
+
: algorithm === "rectpacking"
|
|
565
|
+
? executeElkjs0111Layout({
|
|
566
|
+
algorithm: "rectpacking",
|
|
567
|
+
graph: graph_,
|
|
568
|
+
options: {
|
|
569
|
+
padding: getOption(layoutOptions, "padding") === undefined ? 15 : padding,
|
|
570
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode") ?? 15,
|
|
571
|
+
},
|
|
540
572
|
})
|
|
541
|
-
: algorithm === "
|
|
542
|
-
?
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
: algorithm === "random"
|
|
547
|
-
? getRandomLayout(graph_, {
|
|
573
|
+
: algorithm === "random"
|
|
574
|
+
? executeElkjs0111Layout({
|
|
575
|
+
algorithm: "random",
|
|
576
|
+
graph: graph_,
|
|
577
|
+
options: {
|
|
548
578
|
padding: getOption(layoutOptions, "padding") === undefined ? 15 : padding,
|
|
549
579
|
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
550
580
|
aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
|
|
551
581
|
seed: getNumberOption(layoutOptions, "randomSeed"),
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
|
|
582
|
+
},
|
|
583
|
+
})
|
|
584
|
+
: algorithm === "box"
|
|
585
|
+
? executeElkjs0111Layout({
|
|
586
|
+
algorithm: "box",
|
|
587
|
+
graph: graph_,
|
|
588
|
+
options: {
|
|
555
589
|
padding: getOption(layoutOptions, "padding") === undefined ? 15 : padding,
|
|
556
590
|
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
557
591
|
aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
|
|
@@ -563,10 +597,18 @@ export default class ELK {
|
|
|
563
597
|
);
|
|
564
598
|
return getNumberOption(child?.layoutOptions ?? {}, "priority");
|
|
565
599
|
},
|
|
600
|
+
},
|
|
601
|
+
})
|
|
602
|
+
: algorithm === "fixed"
|
|
603
|
+
? executeElkjs0111Layout({
|
|
604
|
+
algorithm: "fixed",
|
|
605
|
+
graph: graph_,
|
|
606
|
+
options: { direction: getDirection(layoutOptions) },
|
|
566
607
|
})
|
|
567
|
-
:
|
|
568
|
-
|
|
569
|
-
|
|
608
|
+
: executeElkjs0111Layout({
|
|
609
|
+
algorithm: "layered",
|
|
610
|
+
graph: graph_,
|
|
611
|
+
options: {
|
|
570
612
|
direction: getDirection(layoutOptions),
|
|
571
613
|
spacing: {
|
|
572
614
|
node:
|
|
@@ -638,7 +680,8 @@ export default class ELK {
|
|
|
638
680
|
),
|
|
639
681
|
} as ElkLayeredOptionValueByName;
|
|
640
682
|
},
|
|
641
|
-
}
|
|
683
|
+
},
|
|
684
|
+
}));
|
|
642
685
|
if (hierarchyRestorations.length > 0) {
|
|
643
686
|
const direction = getDirection(layoutOptions);
|
|
644
687
|
const horizontal = direction === "right" || direction === "left";
|
|
@@ -750,7 +793,16 @@ export default class ELK {
|
|
|
750
793
|
);
|
|
751
794
|
}
|
|
752
795
|
}
|
|
753
|
-
|
|
796
|
+
const resultPolicy = (
|
|
797
|
+
laidOut as typeof laidOut & {
|
|
798
|
+
[elkjs0111ResultPolicy]?: Elkjs0111ResultPolicy;
|
|
799
|
+
}
|
|
800
|
+
)[elkjs0111ResultPolicy];
|
|
801
|
+
applyLayout(graph, laidOut, padding, layoutOptions, resultPolicy);
|
|
802
|
+
if (resultPolicy?.providerBounds) {
|
|
803
|
+
graph.width = resultPolicy.providerBounds.width;
|
|
804
|
+
graph.height = resultPolicy.providerBounds.height;
|
|
805
|
+
}
|
|
754
806
|
for (const restoration of hierarchyRestorations) {
|
|
755
807
|
restoration.edge.sources = restoration.sources;
|
|
756
808
|
restoration.edge.targets = restoration.targets;
|
|
@@ -771,7 +823,7 @@ export default class ELK {
|
|
|
771
823
|
for (const child of graph.children ?? []) {
|
|
772
824
|
if ((child.children?.length ?? 0) === 0) continue;
|
|
773
825
|
const position = { x: child.x, y: child.y };
|
|
774
|
-
await this
|
|
826
|
+
await this.#layout(child, {
|
|
775
827
|
...arguments_,
|
|
776
828
|
logging: false,
|
|
777
829
|
measureExecutionTime: false,
|
|
@@ -1335,9 +1387,10 @@ function toSection(edge: ElkEdge, points: readonly ElkPoint[]) {
|
|
|
1335
1387
|
|
|
1336
1388
|
function applyLayout(
|
|
1337
1389
|
root: ElkNode,
|
|
1338
|
-
graph:
|
|
1390
|
+
graph: VisualGraph,
|
|
1339
1391
|
padding: { top: number; right: number; bottom: number; left: number },
|
|
1340
1392
|
layoutOptions: Readonly<Record<string, unknown>>,
|
|
1393
|
+
resultPolicy?: Elkjs0111ResultPolicy,
|
|
1341
1394
|
): void {
|
|
1342
1395
|
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
1343
1396
|
const edgeById = new Map(graph.edges.map((edge) => [edge.id, edge]));
|
|
@@ -1361,6 +1414,13 @@ function applyLayout(
|
|
|
1361
1414
|
placePortLabels(child, layoutOptions);
|
|
1362
1415
|
}
|
|
1363
1416
|
for (const edge of root.edges ?? []) {
|
|
1417
|
+
if (resultPolicy?.preserveEdgeSections) {
|
|
1418
|
+
for (const section of edge.sections ?? []) {
|
|
1419
|
+
section.incomingShape ??= edge.sources?.[0] ?? edge.source;
|
|
1420
|
+
section.outgoingShape ??= edge.targets?.[0] ?? edge.target;
|
|
1421
|
+
}
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1364
1424
|
const laidOutEdge = edgeById.get(String(edge.id));
|
|
1365
1425
|
if (!laidOutEdge) {
|
|
1366
1426
|
if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true) {
|
|
@@ -1417,7 +1477,9 @@ function applyLayout(
|
|
|
1417
1477
|
labelY += (label.height ?? 0) + labelLabelSpacing;
|
|
1418
1478
|
}
|
|
1419
1479
|
}
|
|
1420
|
-
|
|
1480
|
+
if (!resultPolicy?.skipBoundsNormalization) {
|
|
1481
|
+
normalizeElkGraphBounds(root, padding, layoutOptions);
|
|
1482
|
+
}
|
|
1421
1483
|
}
|
|
1422
1484
|
|
|
1423
1485
|
function normalizeElkGraphBounds(
|
|
@@ -1838,3 +1900,6 @@ export type {
|
|
|
1838
1900
|
StatechartLayoutScore,
|
|
1839
1901
|
StatechartLayoutPlan,
|
|
1840
1902
|
} from "./statechart";
|
|
1903
|
+
|
|
1904
|
+
export { getElkRoutes } from "./routes";
|
|
1905
|
+
export type { ElkRouteOptions } from "./routes";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Copyright (c) 2021 Kiel University and others.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from elkjs 0.11.1 lib/main.js.
|
|
5
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
6
|
+
*******************************************************************************/
|
|
7
|
+
import type { ELKConstructorArguments } from "./public-types";
|
|
8
|
+
import { Worker } from "./worker";
|
|
9
|
+
import { WorkerELK } from "./worker-client";
|
|
10
|
+
|
|
11
|
+
/** Node-compatible elkjs entry point with an in-process worker fallback. */
|
|
12
|
+
export default class ELK extends WorkerELK {
|
|
13
|
+
constructor(options: ELKConstructorArguments = {}) {
|
|
14
|
+
super({
|
|
15
|
+
...options,
|
|
16
|
+
workerFactory: options.workerFactory ?? ((url?: string) => new Worker(url)),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Copyright (c) 2019 TypeFox and others.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from elkjs 0.11.1 typings/elk-api.d.ts.
|
|
5
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
6
|
+
*******************************************************************************/
|
|
7
|
+
|
|
8
|
+
export interface LayoutOptions {
|
|
9
|
+
[key: string]: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ElkPoint {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ElkGraphElement {
|
|
18
|
+
id?: string;
|
|
19
|
+
labels?: ElkLabel[];
|
|
20
|
+
layoutOptions?: LayoutOptions;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ElkShape extends ElkGraphElement {
|
|
24
|
+
x?: number;
|
|
25
|
+
y?: number;
|
|
26
|
+
width?: number;
|
|
27
|
+
height?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ElkNode extends ElkShape {
|
|
31
|
+
id: string;
|
|
32
|
+
children?: ElkNode[];
|
|
33
|
+
ports?: ElkPort[];
|
|
34
|
+
edges?: ElkExtendedEdge[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ElkPort extends ElkShape {
|
|
38
|
+
id: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ElkLabel extends ElkShape {
|
|
42
|
+
text?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @deprecated Use ElkExtendedEdge directly. */
|
|
46
|
+
export interface ElkEdge extends ElkGraphElement {
|
|
47
|
+
id: string;
|
|
48
|
+
container?: string;
|
|
49
|
+
junctionPoints?: ElkPoint[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @deprecated Use ElkExtendedEdge instead. */
|
|
53
|
+
export interface ElkPrimitiveEdge extends ElkEdge {
|
|
54
|
+
source: string;
|
|
55
|
+
sourcePort?: string;
|
|
56
|
+
target: string;
|
|
57
|
+
targetPort?: string;
|
|
58
|
+
sourcePoint?: ElkPoint;
|
|
59
|
+
targetPoint?: ElkPoint;
|
|
60
|
+
bendPoints?: ElkPoint[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ElkExtendedEdge extends ElkEdge {
|
|
64
|
+
sources: string[];
|
|
65
|
+
targets: string[];
|
|
66
|
+
sections?: ElkEdgeSection[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ElkEdgeSection extends ElkGraphElement {
|
|
70
|
+
id: string;
|
|
71
|
+
startPoint: ElkPoint;
|
|
72
|
+
endPoint: ElkPoint;
|
|
73
|
+
bendPoints?: ElkPoint[];
|
|
74
|
+
incomingShape?: string;
|
|
75
|
+
outgoingShape?: string;
|
|
76
|
+
incomingSections?: string[];
|
|
77
|
+
outgoingSections?: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ElkLayoutArguments {
|
|
81
|
+
layoutOptions?: LayoutOptions;
|
|
82
|
+
logging?: boolean;
|
|
83
|
+
measureExecutionTime?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ElkCommonDescription {
|
|
87
|
+
id?: string;
|
|
88
|
+
name?: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface ElkLayoutAlgorithmDescription extends ElkCommonDescription {
|
|
93
|
+
category?: string;
|
|
94
|
+
knownOptions?: string[];
|
|
95
|
+
supportedFeatures?: string[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ElkLayoutOptionDescription extends ElkCommonDescription {
|
|
99
|
+
group?: string;
|
|
100
|
+
type?: string;
|
|
101
|
+
targets?: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface ElkLayoutCategoryDescription extends ElkCommonDescription {
|
|
105
|
+
knownLayouters?: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export type LaidOutElkNode<T extends ElkNode> = Omit<T, "children"> & {
|
|
109
|
+
children?: Array<NonNullable<T["children"]>[number] & ElkNode>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export interface ELK {
|
|
113
|
+
layout<T extends ElkNode>(graph: T, args?: ElkLayoutArguments): Promise<LaidOutElkNode<T>>;
|
|
114
|
+
knownLayoutAlgorithms(): Promise<ElkLayoutAlgorithmDescription[]>;
|
|
115
|
+
knownLayoutOptions(): Promise<ElkLayoutOptionDescription[]>;
|
|
116
|
+
knownLayoutCategories(): Promise<ElkLayoutCategoryDescription[]>;
|
|
117
|
+
terminateWorker(): void;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ELKConstructorArguments {
|
|
121
|
+
defaultLayoutOptions?: LayoutOptions;
|
|
122
|
+
algorithms?: string[];
|
|
123
|
+
workerUrl?: string;
|
|
124
|
+
workerFactory?: (url?: string) => Worker;
|
|
125
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { ElkExtendedEdge, ElkNode, ElkPoint } from "./public-types";
|
|
2
|
+
import { pathFromPoints } from "../routing/path";
|
|
3
|
+
import { pathFromSplinePoints } from "../routing/adapters";
|
|
4
|
+
import { freeze } from "../routing/model";
|
|
5
|
+
import type { Route, RouteEndpoint, RoutePath, RoutePoint, RouteSection } from "../routing/types";
|
|
6
|
+
|
|
7
|
+
export interface ElkRouteOptions {
|
|
8
|
+
/** ELK stores spline controls in bendPoints; the caller must identify that encoding. */
|
|
9
|
+
routing?: "ORTHOGONAL" | "POLYLINE" | "SPLINES";
|
|
10
|
+
}
|
|
11
|
+
/** Normalize any ELK layout's section geometry, preserving ports and hyperedge junctions. */
|
|
12
|
+
export function getElkRoutes(
|
|
13
|
+
graph: ElkNode,
|
|
14
|
+
options: ElkRouteOptions = {},
|
|
15
|
+
): ReadonlyMap<string, Route> {
|
|
16
|
+
const shapes = new Map<string, { ref: RouteEndpoint; center: RoutePoint }>(),
|
|
17
|
+
frames = new Map<string, RoutePoint>();
|
|
18
|
+
const edges: { edge: ElkExtendedEdge; offset: RoutePoint; routing: string }[] = [];
|
|
19
|
+
function visit(node: ElkNode, offset: RoutePoint, inherited: string) {
|
|
20
|
+
const position = { x: offset.x + (node.x ?? 0), y: offset.y + (node.y ?? 0) };
|
|
21
|
+
const routing =
|
|
22
|
+
node.layoutOptions?.["elk.edgeRouting"] ??
|
|
23
|
+
node.layoutOptions?.["org.eclipse.elk.edgeRouting"] ??
|
|
24
|
+
inherited;
|
|
25
|
+
frames.set(node.id, position);
|
|
26
|
+
shapes.set(node.id, {
|
|
27
|
+
ref: { kind: "node", nodeId: node.id },
|
|
28
|
+
center: { x: position.x + (node.width ?? 0) / 2, y: position.y + (node.height ?? 0) / 2 },
|
|
29
|
+
});
|
|
30
|
+
for (const port of node.ports ?? [])
|
|
31
|
+
shapes.set(port.id, {
|
|
32
|
+
ref: { kind: "node", nodeId: node.id, port: port.id },
|
|
33
|
+
center: {
|
|
34
|
+
x: position.x + (port.x ?? 0) + (port.width ?? 0) / 2,
|
|
35
|
+
y: position.y + (port.y ?? 0) + (port.height ?? 0) / 2,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
for (const edge of node.edges ?? [])
|
|
39
|
+
edges.push({
|
|
40
|
+
edge,
|
|
41
|
+
offset: position,
|
|
42
|
+
routing:
|
|
43
|
+
edge.layoutOptions?.["elk.edgeRouting"] ??
|
|
44
|
+
edge.layoutOptions?.["org.eclipse.elk.edgeRouting"] ??
|
|
45
|
+
routing,
|
|
46
|
+
});
|
|
47
|
+
for (const child of node.children ?? []) visit(child, position, routing);
|
|
48
|
+
}
|
|
49
|
+
visit(graph, { x: 0, y: 0 }, options.routing ?? "POLYLINE");
|
|
50
|
+
const result = new Map<string, Route>();
|
|
51
|
+
for (const { edge, offset: inheritedOffset, routing } of edges) {
|
|
52
|
+
const offset = edge.container
|
|
53
|
+
? (frames.get(edge.container) ?? inheritedOffset)
|
|
54
|
+
: inheritedOffset;
|
|
55
|
+
const point = (p: ElkPoint): RoutePoint => ({ x: p.x + offset.x, y: p.y + offset.y });
|
|
56
|
+
// Join section endpoints by topology, never by equal coordinates alone.
|
|
57
|
+
const parents = new Map<string, string>();
|
|
58
|
+
const root = (key: string): string => {
|
|
59
|
+
let current = key;
|
|
60
|
+
while (parents.has(current)) current = parents.get(current)!;
|
|
61
|
+
return current;
|
|
62
|
+
};
|
|
63
|
+
const join = (a: string, b: string) => {
|
|
64
|
+
const x = root(a),
|
|
65
|
+
y = root(b);
|
|
66
|
+
if (x !== y) parents.set(x < y ? y : x, x < y ? x : y);
|
|
67
|
+
};
|
|
68
|
+
for (const section of edge.sections ?? []) {
|
|
69
|
+
for (const id of section.outgoingSections ?? []) join(`${section.id}:end`, `${id}:start`);
|
|
70
|
+
for (const id of section.incomingSections ?? []) join(`${id}:end`, `${section.id}:start`);
|
|
71
|
+
}
|
|
72
|
+
const sections: RouteSection[] = [];
|
|
73
|
+
let malformed = false;
|
|
74
|
+
for (const section of edge.sections ?? []) {
|
|
75
|
+
const points = [section.startPoint, ...(section.bendPoints ?? []), section.endPoint].map(
|
|
76
|
+
point,
|
|
77
|
+
);
|
|
78
|
+
let path: RoutePath;
|
|
79
|
+
if (points.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) {
|
|
80
|
+
malformed = true;
|
|
81
|
+
for (let i = 0; i < points.length; i++)
|
|
82
|
+
if (!Number.isFinite(points[i]!.x) || !Number.isFinite(points[i]!.y))
|
|
83
|
+
points[i] = { x: offset.x + i * 20, y: offset.y };
|
|
84
|
+
}
|
|
85
|
+
if (routing === "SPLINES") {
|
|
86
|
+
if ((points.length - 1) % 3 || points.length < 4) {
|
|
87
|
+
malformed = true;
|
|
88
|
+
path = pathFromPoints([points[0]!, points.at(-1)!]);
|
|
89
|
+
} else path = pathFromSplinePoints(points);
|
|
90
|
+
} else path = pathFromPoints(points);
|
|
91
|
+
const fromShape =
|
|
92
|
+
section.incomingShape ??
|
|
93
|
+
(!section.incomingSections?.length && edge.sources.length === 1
|
|
94
|
+
? edge.sources[0]
|
|
95
|
+
: undefined);
|
|
96
|
+
const toShape =
|
|
97
|
+
section.outgoingShape ??
|
|
98
|
+
(!section.outgoingSections?.length && edge.targets.length === 1
|
|
99
|
+
? edge.targets[0]
|
|
100
|
+
: undefined);
|
|
101
|
+
sections.push({
|
|
102
|
+
id: section.id,
|
|
103
|
+
from: fromShape
|
|
104
|
+
? (shapes.get(fromShape)?.ref ?? { kind: "node", nodeId: fromShape })
|
|
105
|
+
: { kind: "junction", id: `${edge.id}:${root(`${section.id}:start`)}` },
|
|
106
|
+
to: toShape
|
|
107
|
+
? (shapes.get(toShape)?.ref ?? { kind: "node", nodeId: toShape })
|
|
108
|
+
: { kind: "junction", id: `${edge.id}:${root(`${section.id}:end`)}` },
|
|
109
|
+
path,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (!sections.length) {
|
|
113
|
+
malformed = true;
|
|
114
|
+
const source = shapes.get(edge.sources[0] ?? ""),
|
|
115
|
+
origin = source?.center ?? { x: 0, y: 0 };
|
|
116
|
+
const targets = [...edge.targets, ...edge.sources.slice(1)];
|
|
117
|
+
for (const [i, id] of (targets.length ? targets : [""]).entries()) {
|
|
118
|
+
const target = shapes.get(id);
|
|
119
|
+
sections.push({
|
|
120
|
+
id: `${edge.id}:fallback:${i}`,
|
|
121
|
+
from: source?.ref ?? { kind: "node", nodeId: edge.sources[0] ?? "" },
|
|
122
|
+
to: target?.ref ?? { kind: "node", nodeId: id },
|
|
123
|
+
path: pathFromPoints([origin, target?.center ?? { x: origin.x + 40, y: origin.y + 40 }]),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
result.set(
|
|
128
|
+
edge.id,
|
|
129
|
+
freeze({
|
|
130
|
+
edgeId: edge.id,
|
|
131
|
+
sections,
|
|
132
|
+
status: malformed ? "fallback" : "routed",
|
|
133
|
+
diagnostics: malformed
|
|
134
|
+
? [
|
|
135
|
+
{
|
|
136
|
+
code: "MISSING_GEOMETRY",
|
|
137
|
+
edgeId: edge.id,
|
|
138
|
+
message:
|
|
139
|
+
"ELK sections contain missing or invalid geometry; rendered fallback paths",
|
|
140
|
+
},
|
|
141
|
+
]
|
|
142
|
+
: [],
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*******************************************************************************
|
|
2
|
+
* Copyright (c) 2017 Kiel University and others.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from elkjs 0.11.1 lib/elk-api.js.
|
|
5
|
+
* SPDX-License-Identifier: EPL-2.0
|
|
6
|
+
*******************************************************************************/
|
|
7
|
+
export { WorkerELK as default } from "./worker-client";
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
ELK,
|
|
11
|
+
ELKConstructorArguments,
|
|
12
|
+
ElkCommonDescription,
|
|
13
|
+
ElkEdge,
|
|
14
|
+
ElkEdgeSection,
|
|
15
|
+
ElkExtendedEdge,
|
|
16
|
+
ElkGraphElement,
|
|
17
|
+
ElkLabel,
|
|
18
|
+
ElkLayoutArguments,
|
|
19
|
+
ElkLayoutAlgorithmDescription,
|
|
20
|
+
ElkLayoutCategoryDescription,
|
|
21
|
+
ElkLayoutOptionDescription,
|
|
22
|
+
ElkNode,
|
|
23
|
+
ElkPoint,
|
|
24
|
+
ElkPort,
|
|
25
|
+
ElkPrimitiveEdge,
|
|
26
|
+
ElkShape,
|
|
27
|
+
LayoutOptions,
|
|
28
|
+
LaidOutElkNode,
|
|
29
|
+
} from "./public-types";
|