@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.
Files changed (85) hide show
  1. package/NOTICE.md +43 -0
  2. package/README.md +153 -6
  3. package/dist/elkjs/index.cjs +9 -0
  4. package/dist/elkjs/index.d.cts +155 -0
  5. package/dist/elkjs/index.d.mts +51 -39
  6. package/dist/elkjs/index.mjs +3 -1484
  7. package/dist/elkjs/main.cjs +17 -0
  8. package/dist/elkjs/main.d.cts +10 -0
  9. package/dist/elkjs/main.d.mts +11 -0
  10. package/dist/elkjs/main.mjs +17 -0
  11. package/dist/elkjs/worker-api.cjs +3 -0
  12. package/dist/elkjs/worker-api.d.cts +3 -0
  13. package/dist/elkjs/worker-api.d.mts +3 -0
  14. package/dist/elkjs/worker-api.mjs +3 -0
  15. package/dist/elkjs/worker.cjs +86 -0
  16. package/dist/elkjs/worker.d.cts +17 -0
  17. package/dist/elkjs/worker.d.mts +17 -0
  18. package/dist/elkjs/worker.mjs +86 -0
  19. package/dist/elkjs-DaHMISnQ.mjs +1677 -0
  20. package/dist/elkjs-dMghiiWW.cjs +1706 -0
  21. package/dist/index-BEA_pzeT.d.cts +72 -0
  22. package/dist/{index-8xYkohbz.d.mts → index-D9dhxrRl.d.cts} +111 -5
  23. package/dist/index-DNTLRsDl.d.mts +72 -0
  24. package/dist/index-DNixtE3h.d.mts +1318 -0
  25. package/dist/index.cjs +215 -0
  26. package/dist/index.d.cts +78 -0
  27. package/dist/index.d.mts +4 -3
  28. package/dist/index.mjs +25 -25
  29. package/dist/layered/index.cjs +38 -0
  30. package/dist/layered/index.d.cts +3 -0
  31. package/dist/layered/index.d.mts +3 -2
  32. package/dist/layered/index.mjs +1 -1
  33. package/dist/layered-B2buJqMf.cjs +12009 -0
  34. package/dist/{layered-CoeH9LRO.mjs → layered-DGFTeQSl.mjs} +2323 -149
  35. package/dist/{spore-BtTSZbb6.mjs → layout-engine-Cs5tatA6.mjs} +159 -5
  36. package/dist/layout-engine-Dn1PiFwP.cjs +712 -0
  37. package/dist/public-types-9NCPoqbp.d.mts +109 -0
  38. package/dist/public-types-BcRZoJE3.d.cts +109 -0
  39. package/dist/routing/index.cjs +28 -0
  40. package/dist/routing/index.d.cts +3 -0
  41. package/dist/routing/index.d.mts +3 -0
  42. package/dist/routing/index.mjs +3 -0
  43. package/dist/routing-A2hrK9MP.mjs +2396 -0
  44. package/dist/routing-ujEP7Qh6.cjs +2606 -0
  45. package/dist/types-CT2akdK0.d.mts +133 -0
  46. package/dist/types-uxPlGRof.d.cts +133 -0
  47. package/dist/worker-client-BNjwT1AJ.mjs +92 -0
  48. package/dist/worker-client-BwGGRD5y.d.mts +19 -0
  49. package/dist/worker-client-D4oEvhAl.cjs +98 -0
  50. package/dist/worker-client-D4zNyPEI.d.cts +19 -0
  51. package/package.json +45 -10
  52. package/src/authoring/coordinates.ts +114 -0
  53. package/src/authoring/partial.ts +584 -0
  54. package/src/authoring/routing.ts +211 -0
  55. package/src/box.ts +22 -0
  56. package/src/constraints.ts +222 -0
  57. package/src/elkjs/index.ts +104 -39
  58. package/src/elkjs/main.ts +19 -0
  59. package/src/elkjs/public-types.ts +125 -0
  60. package/src/elkjs/routes.ts +147 -0
  61. package/src/elkjs/worker-api.ts +29 -0
  62. package/src/elkjs/worker-client.ts +138 -0
  63. package/src/elkjs/worker.ts +117 -0
  64. package/src/index.ts +19 -0
  65. package/src/internal/elkjs-compatibility.ts +17 -0
  66. package/src/internal/layout-engine.ts +126 -0
  67. package/src/layered/compound.ts +417 -0
  68. package/src/layered/index.ts +58 -105
  69. package/src/layered/types.ts +21 -0
  70. package/src/layout.ts +35 -28
  71. package/src/packing.ts +22 -0
  72. package/src/random.ts +75 -3
  73. package/src/routing/adapters.ts +111 -0
  74. package/src/routing/algorithms.ts +676 -0
  75. package/src/routing/coordination.ts +86 -0
  76. package/src/routing/engine.ts +473 -0
  77. package/src/routing/index.ts +68 -0
  78. package/src/routing/layout-cache.ts +65 -0
  79. package/src/routing/model.ts +218 -0
  80. package/src/routing/path.ts +324 -0
  81. package/src/routing/persistent.ts +117 -0
  82. package/src/routing/search.ts +276 -0
  83. package/src/routing/spatial.ts +78 -0
  84. package/src/routing/types.ts +146 -0
  85. package/src/types.ts +17 -2
@@ -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 { getRectanglePackingLayout } from "../packing";
11
- import { getRandomLayout } from "../random";
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
- ElkConstructorArguments,
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
- async layout<T extends ElkNode>(
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.layout(child, {
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.layout(temporaryChild, {
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
- algorithm === "sporeCompaction"
532
- ? getSporeCompactionLayout(graph_, {
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
- : algorithm === "sporeOverlap"
537
- ? getSporeOverlapRemovalLayout(graph_, {
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 === "rectpacking"
542
- ? getRectanglePackingLayout(graph_, {
543
- padding,
544
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
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
- : algorithm === "box"
554
- ? getBoxLayout(graph_, {
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
- : algorithm === "fixed"
568
- ? getFixedLayout(graph_, { direction: getDirection(layoutOptions) })
569
- : getLayeredLayout(graph_, {
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
- applyLayout(graph, laidOut, padding, layoutOptions);
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.layout(child, {
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: ReturnType<typeof getLayeredLayout>,
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
- normalizeElkGraphBounds(root, padding, layoutOptions);
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";