@statelyai/layout 0.3.0 → 0.4.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.
Files changed (110) hide show
  1. package/NOTICE.md +4 -2
  2. package/README.md +150 -11
  3. package/dist/elkjs/index.cjs +2 -2
  4. package/dist/elkjs/index.d.cts +2 -0
  5. package/dist/elkjs/index.d.mts +2 -0
  6. package/dist/elkjs/index.mjs +2 -2
  7. package/dist/elkjs/main.cjs +1 -1
  8. package/dist/elkjs/main.mjs +1 -1
  9. package/dist/elkjs/worker.cjs +2 -2
  10. package/dist/elkjs/worker.mjs +2 -2
  11. package/dist/elkjs-BBfKN4Dw.mjs +3075 -0
  12. package/dist/elkjs-DNfXDPup.cjs +3104 -0
  13. package/dist/{index-BEA_pzeT.d.cts → index-B6cWNrMw.d.cts} +9 -1
  14. package/dist/{index-DNTLRsDl.d.mts → index-CJQLUKD9.d.mts} +9 -1
  15. package/dist/{index-DNixtE3h.d.mts → index-DT6lPMfS.d.cts} +85 -5
  16. package/dist/{index-D9dhxrRl.d.cts → index-F1U-fCTu.d.mts} +85 -5
  17. package/dist/index.cjs +6 -3
  18. package/dist/index.d.cts +3 -3
  19. package/dist/index.d.mts +3 -3
  20. package/dist/index.mjs +4 -4
  21. package/dist/layered/index.cjs +3 -1
  22. package/dist/layered/index.d.cts +2 -2
  23. package/dist/layered/index.d.mts +2 -2
  24. package/dist/layered/index.mjs +2 -2
  25. package/dist/{layered-B2buJqMf.cjs → layered-5RxQhryb.cjs} +11184 -2932
  26. package/dist/{layered-DGFTeQSl.mjs → layered-DSesbctm.mjs} +12059 -3903
  27. package/dist/{layout-engine-Dn1PiFwP.cjs → layout-engine-CLFySz3R.cjs} +5 -18
  28. package/dist/{layout-engine-Cs5tatA6.mjs → layout-engine-GpBIX6ng.mjs} +3 -10
  29. package/dist/routing/index.cjs +2 -1
  30. package/dist/routing/index.d.cts +2 -2
  31. package/dist/routing/index.d.mts +2 -2
  32. package/dist/routing/index.mjs +2 -2
  33. package/dist/{routing-A2hrK9MP.mjs → routing-CR8flv8b.mjs} +977 -593
  34. package/dist/{routing-ujEP7Qh6.cjs → routing-QIdGGFBe.cjs} +986 -596
  35. package/package.json +8 -2
  36. package/src/authoring/coordinates.ts +28 -8
  37. package/src/authoring/partial.ts +7 -2
  38. package/src/authoring/routing.ts +4 -1
  39. package/src/constraints.ts +174 -6
  40. package/src/elkjs/index.ts +1476 -449
  41. package/src/elkjs/layout-quality.ts +267 -0
  42. package/src/elkjs/opposing-tracks.ts +161 -0
  43. package/src/elkjs/orthogonal-junctions.ts +174 -0
  44. package/src/elkjs/statechart.ts +9 -3
  45. package/src/elkjs/types.ts +2 -0
  46. package/src/index.ts +2 -0
  47. package/src/internal/elkjs-compatibility.ts +4 -1
  48. package/src/internal/layered-trace.ts +48 -0
  49. package/src/internal/layout-engine.ts +2 -1
  50. package/src/java-random.ts +6 -0
  51. package/src/layered/bk-node-placement.ts +403 -92
  52. package/src/layered/center-labels.ts +398 -0
  53. package/src/layered/compaction-bounds.ts +26 -0
  54. package/src/layered/compaction-errors.ts +2 -0
  55. package/src/layered/compaction-scanline.ts +63 -0
  56. package/src/layered/compound-boundaries.ts +99 -0
  57. package/src/layered/compound.ts +35 -7
  58. package/src/layered/constraint-orientation.ts +141 -0
  59. package/src/layered/crossing-constraints.ts +243 -0
  60. package/src/layered/crossing-counter.ts +233 -0
  61. package/src/layered/crossing-graph.ts +401 -0
  62. package/src/layered/cycle-random.ts +22 -0
  63. package/src/layered/elk-port-lists.ts +243 -0
  64. package/src/layered/end-labels.ts +124 -0
  65. package/src/layered/external-port-dummy.ts +137 -0
  66. package/src/layered/feedback-chains.ts +114 -0
  67. package/src/layered/fixed-self-loop.ts +150 -0
  68. package/src/layered/greedy-switch-decider.ts +356 -0
  69. package/src/layered/grouped-compaction.ts +787 -0
  70. package/src/layered/gwt-sort.ts +53 -0
  71. package/src/layered/hierarchical-port-phases.ts +242 -0
  72. package/src/layered/hierarchical-port-restoration.ts +233 -0
  73. package/src/layered/hierarchical-port-routing.ts +179 -0
  74. package/src/layered/hierarchy-crossing.ts +121 -0
  75. package/src/layered/hierarchy-sweepiness.ts +79 -0
  76. package/src/layered/hints.ts +371 -0
  77. package/src/layered/hyperedge-dummy-merger.ts +195 -0
  78. package/src/layered/index.ts +875 -161
  79. package/src/layered/initial-model-order.ts +404 -0
  80. package/src/layered/inverted-ports.ts +207 -0
  81. package/src/layered/label-dummy-switcher.ts +200 -0
  82. package/src/layered/long-edges.ts +135 -44
  83. package/src/layered/loop-envelopes.ts +184 -0
  84. package/src/layered/native-routing.ts +275 -0
  85. package/src/layered/network-simplex.ts +11 -15
  86. package/src/layered/node-margins.ts +59 -0
  87. package/src/layered/north-south-ports.ts +271 -0
  88. package/src/layered/opposing-tracks.ts +1044 -0
  89. package/src/layered/orthogonal-cycle-order.ts +106 -0
  90. package/src/layered/orthogonal-hypersegments.ts +154 -0
  91. package/src/layered/orthogonal-junctions.ts +202 -0
  92. package/src/layered/orthogonal-segments.ts +243 -0
  93. package/src/layered/placement-orientation.ts +22 -0
  94. package/src/layered/port-distributor.ts +192 -0
  95. package/src/layered/port-edge-order.ts +20 -0
  96. package/src/layered/replace-routing.ts +45 -0
  97. package/src/layered/routing-coordinates.ts +32 -0
  98. package/src/layered/separate-exterior-labels.ts +24 -6
  99. package/src/layered/spacing.ts +34 -12
  100. package/src/layered/splitter-order.ts +106 -0
  101. package/src/layered/strategies.ts +2124 -886
  102. package/src/layered/types.ts +13 -0
  103. package/src/layered/weighted-compaction.ts +84 -0
  104. package/src/routing/adapters.ts +32 -0
  105. package/src/routing/algorithms.ts +433 -21
  106. package/src/routing/coordination.ts +8 -3
  107. package/src/routing/index.ts +1 -0
  108. package/src/routing/search.ts +93 -11
  109. package/dist/elkjs-DaHMISnQ.mjs +0 -1677
  110. package/dist/elkjs-dMghiiWW.cjs +0 -1706
@@ -1,1706 +0,0 @@
1
- const require_routing = require('./routing-ujEP7Qh6.cjs');
2
- const require_layered = require('./layered-B2buJqMf.cjs');
3
- const require_layout_engine = require('./layout-engine-Dn1PiFwP.cjs');
4
- let _statelyai_graph = require("@statelyai/graph");
5
-
6
- //#region src/elkjs/statechart.ts
7
- function option(node, suffix) {
8
- const settings = {
9
- ...node.properties,
10
- ...node.layoutOptions
11
- };
12
- return settings[suffix] ?? settings[`elk.${suffix}`] ?? settings[`org.eclipse.elk.${suffix}`];
13
- }
14
- const endpoint$1 = (edge, source) => source ? edge.sources?.[0] ?? edge.source : edge.targets?.[0] ?? edge.target;
15
- /** Compile semantic hints into settings implemented by this package's ELK adapter. */
16
- function compileStatechartLayout(input, scopes) {
17
- const graph = structuredClone(input);
18
- const paths = Object.create(null);
19
- const commonExits = Object.create(null);
20
- const containers = /* @__PURE__ */ new Map();
21
- const edges = [];
22
- const collect = (node) => {
23
- if (node.id === void 0 || containers.has(String(node.id))) throw new Error("Statechart nodes require unique IDs");
24
- containers.set(String(node.id), node);
25
- edges.push(...node.edges ?? []);
26
- node.children?.forEach(collect);
27
- };
28
- collect(graph);
29
- for (const [id, scope] of Object.entries(scopes)) {
30
- const container = containers.get(id);
31
- if (!container) throw new Error(`Unknown statechart container: ${id}`);
32
- const children = container.children ?? [];
33
- const ids = new Set(children.map((node) => node.id));
34
- const owner = /* @__PURE__ */ new Map();
35
- const own = (node, childId) => {
36
- owner.set(node.id, childId);
37
- node.ports?.forEach((port) => owner.set(port.id, childId));
38
- node.children?.forEach((descendant) => own(descendant, childId));
39
- };
40
- children.forEach((child) => own(child, child.id));
41
- const localEdges = edges.filter((edge) => owner.has(endpoint$1(edge, true)) && owner.has(endpoint$1(edge, false)));
42
- const successors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
43
- const predecessors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
44
- for (const edge of localEdges) {
45
- const source = owner.get(endpoint$1(edge, true));
46
- const target = owner.get(endpoint$1(edge, false));
47
- if (source === target) continue;
48
- successors.get(source).add(target);
49
- predecessors.get(target).add(source);
50
- }
51
- const exits = children.filter((node) => successors.get(node.id).size === 0 && predecessors.get(node.id).size >= 2).map((node) => node.id);
52
- commonExits[id] = exits;
53
- const path = [...scope.preferredPath ?? (scope.initialNodeId === void 0 ? [] : [scope.initialNodeId])];
54
- if (new Set(path).size !== path.length || path.some((nodeId) => !ids.has(nodeId))) throw new Error(`Path must contain distinct direct children of ${id}`);
55
- if (scope.initialNodeId !== void 0 && (!ids.has(scope.initialNodeId) || path[0] !== scope.initialNodeId)) throw new Error(`Path must start at initial child of ${id}`);
56
- if (scope.preferredPath) {
57
- for (let i = 1; i < path.length; i++) if (!successors.get(path[i - 1])?.has(path[i])) throw new Error(`Disconnected preferred path in ${id}`);
58
- } else while (path.length) {
59
- const next = [...successors.get(path.at(-1))].filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId));
60
- if (next.length !== 1) break;
61
- path.push(next[0]);
62
- }
63
- paths[id] = path;
64
- container.children = [
65
- ...path,
66
- ...children.map((node) => node.id).filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId)),
67
- ...exits.filter((nodeId) => !path.includes(nodeId))
68
- ].map((nodeId) => children.find((node) => node.id === nodeId));
69
- container.layoutOptions = {
70
- ...path.length > 1 && option(container, "layered.cycleBreaking.strategy") === void 0 ? { "elk.layered.cycleBreaking.strategy": "MODEL_ORDER" } : {},
71
- ...path.length > 1 && option(container, "layered.feedbackEdges") === void 0 ? { "elk.layered.feedbackEdges": true } : {},
72
- ...container.layoutOptions,
73
- ...scope.direction ? { direction: scope.direction } : {}
74
- };
75
- }
76
- return {
77
- graph,
78
- paths,
79
- commonExits
80
- };
81
- }
82
- const intersects = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
83
- const cross = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
84
- /** Diagnostic objective, not a proof of geometric validity. Coordinates are container-relative. */
85
- function scoreStatechartLayout(graph, paths = {}) {
86
- const score = {
87
- invalid: 0,
88
- overlaps: 0,
89
- pathOrder: 0,
90
- crossings: 0,
91
- bends: 0,
92
- routeLength: 0
93
- };
94
- const segments = [];
95
- const boxes = [];
96
- const rect = (shape, x, y) => {
97
- if ([
98
- shape.x,
99
- shape.y,
100
- shape.width,
101
- shape.height
102
- ].some((value) => value === void 0 || !Number.isFinite(value)) || (shape.width ?? 0) < 0 || (shape.height ?? 0) < 0) score.invalid++;
103
- return {
104
- x: x + (shape.x ?? 0),
105
- y: y + (shape.y ?? 0),
106
- width: shape.width ?? 0,
107
- height: shape.height ?? 0
108
- };
109
- };
110
- const visit = (node, x, y, ancestors) => {
111
- const direction = String(option(node, "direction") ?? "RIGHT").toUpperCase();
112
- const horizontal = direction === "RIGHT" || direction === "LEFT";
113
- const sign = direction === "LEFT" || direction === "UP" ? -1 : 1;
114
- const path = paths[String(node.id)] ?? [];
115
- for (let i = 1; i < path.length; i++) {
116
- const a = node.children?.find((child) => child.id === path[i - 1]);
117
- const b = node.children?.find((child) => child.id === path[i]);
118
- if (!a || !b || sign * (horizontal ? b.x - a.x : b.y - a.y) <= 0) score.pathOrder++;
119
- }
120
- for (const child of node.children ?? []) {
121
- if (String(option(child, "noLayout")).toLowerCase() === "true") continue;
122
- const box = rect(child, x, y);
123
- if ((child.x ?? 0) < 0 || (child.y ?? 0) < 0 || (child.x ?? 0) + (child.width ?? 0) > (node.width ?? Infinity) + .001 || (child.y ?? 0) + (child.height ?? 0) > (node.height ?? Infinity) + .001) score.invalid++;
124
- boxes.push({
125
- rect: box,
126
- ancestors,
127
- node: child
128
- });
129
- visit(child, box.x, box.y, [...ancestors, child]);
130
- }
131
- for (const edge of node.edges ?? []) {
132
- if (String(option(edge, "noLayout")).toLowerCase() === "true") continue;
133
- if (!edge.sections?.length) score.invalid++;
134
- for (const label of edge.labels ?? []) {
135
- if (String(option(label, "noLayout")).toLowerCase() === "true") continue;
136
- boxes.push({
137
- rect: rect(label, x, y),
138
- ancestors
139
- });
140
- }
141
- for (const section of edge.sections ?? []) {
142
- const points = [
143
- section.startPoint,
144
- ...section.bendPoints ?? [],
145
- section.endPoint
146
- ];
147
- score.bends += section.bendPoints?.length ?? 0;
148
- for (const point of points) if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) score.invalid++;
149
- for (let i = 1; i < points.length; i++) {
150
- const a = {
151
- x: x + points[i - 1].x,
152
- y: y + points[i - 1].y
153
- };
154
- const b = {
155
- x: x + points[i].x,
156
- y: y + points[i].y
157
- };
158
- score.routeLength += Math.hypot(b.x - a.x, b.y - a.y);
159
- segments.push({
160
- a,
161
- b,
162
- edge
163
- });
164
- }
165
- }
166
- }
167
- };
168
- visit(graph, 0, 0, [graph]);
169
- for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) {
170
- const a = boxes[i], b = boxes[j];
171
- if (a.node && b.ancestors.includes(a.node) || b.node && a.ancestors.includes(b.node)) continue;
172
- if (intersects(a.rect, b.rect)) score.overlaps++;
173
- }
174
- for (let i = 0; i < segments.length; i++) for (let j = i + 1; j < segments.length; j++) {
175
- const a = segments[i], b = segments[j];
176
- if (a.edge === b.edge) continue;
177
- if (cross(a.a, a.b, b.a) * cross(a.a, a.b, b.b) < 0 && cross(b.a, b.b, a.a) * cross(b.a, b.b, a.b) < 0) score.crossings++;
178
- }
179
- if (!Number.isFinite(score.routeLength)) score.routeLength = Number.MAX_VALUE;
180
- return score;
181
- }
182
- const objectives = [
183
- "invalid",
184
- "overlaps",
185
- "pathOrder",
186
- "crossings",
187
- "bends",
188
- "routeLength"
189
- ];
190
- function better(a, b) {
191
- for (const key of objectives) if (a[key] !== b[key]) return a[key] < b[key];
192
- return false;
193
- }
194
- /** Each attempt starts from original input. No post-layout geometry edits. */
195
- async function layoutStatechart(input, options) {
196
- const maxAttempts = options.maxAttempts ?? 3;
197
- if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) throw new RangeError("maxAttempts must be an integer from 1 to 3");
198
- const plan = compileStatechartLayout(input, options.scopes);
199
- const attempts = [];
200
- let selected;
201
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
202
- const candidate = structuredClone(attempt === 0 ? input : plan.graph);
203
- const configure = (node) => {
204
- const scope = Object.hasOwn(options.scopes, String(node.id)) ? options.scopes[String(node.id)] : void 0;
205
- if (scope?.direction) node.layoutOptions = {
206
- ...node.layoutOptions,
207
- direction: scope.direction
208
- };
209
- if (attempt === 2 && scope) node.layoutOptions = {
210
- ...node.layoutOptions,
211
- "spacing.nodeNode": Math.max(40, Number(option(node, "spacing.nodeNode") ?? 20) * 1.5),
212
- "layered.spacing.nodeNodeBetweenLayers": Math.max(60, Number(option(node, "layered.spacing.nodeNodeBetweenLayers") ?? 20) * 1.5)
213
- };
214
- if (attempt === 2 && scope) {
215
- const groups = /* @__PURE__ */ new Map();
216
- for (const edge of node.edges ?? []) {
217
- if (!edge.labels?.length) continue;
218
- const key = JSON.stringify([endpoint$1(edge, true), endpoint$1(edge, false)]);
219
- const group = groups.get(key) ?? [];
220
- group.push(edge);
221
- groups.set(key, group);
222
- }
223
- for (const group of groups.values()) for (let i = 1; i < Math.min(group.length, 3); i++) {
224
- const edge = group[i];
225
- if (option(edge, "edgeLabels.placement") === void 0 && edge.labels.every((label) => option(label, "edgeLabels.placement") === void 0)) {
226
- const placement = i === 1 ? "HEAD" : "TAIL";
227
- edge.layoutOptions = {
228
- ...edge.layoutOptions,
229
- "elk.edgeLabels.placement": placement
230
- };
231
- edge.labels = edge.labels.map((label) => ({
232
- ...label,
233
- layoutOptions: {
234
- ...label.layoutOptions,
235
- "elk.edgeLabels.placement": placement
236
- }
237
- }));
238
- }
239
- }
240
- }
241
- node.children?.forEach(configure);
242
- };
243
- configure(candidate);
244
- const graph = await new ELK().layout(candidate);
245
- const score = scoreStatechartLayout(graph, plan.paths);
246
- attempts.push({
247
- kind: attempt === 0 ? "baseline" : attempt === 1 ? "policy" : "spacing",
248
- score
249
- });
250
- if (!selected || better(score, selected.score)) selected = {
251
- graph,
252
- score,
253
- attempt
254
- };
255
- }
256
- return {
257
- ...selected,
258
- attempts,
259
- paths: plan.paths,
260
- commonExits: plan.commonExits
261
- };
262
- }
263
-
264
- //#endregion
265
- //#region src/elkjs/routes.ts
266
- /** Normalize any ELK layout's section geometry, preserving ports and hyperedge junctions. */
267
- function getElkRoutes(graph, options = {}) {
268
- const shapes = /* @__PURE__ */ new Map(), frames = /* @__PURE__ */ new Map();
269
- const edges = [];
270
- function visit(node, offset, inherited) {
271
- const position = {
272
- x: offset.x + (node.x ?? 0),
273
- y: offset.y + (node.y ?? 0)
274
- };
275
- const routing = node.layoutOptions?.["elk.edgeRouting"] ?? node.layoutOptions?.["org.eclipse.elk.edgeRouting"] ?? inherited;
276
- frames.set(node.id, position);
277
- shapes.set(node.id, {
278
- ref: {
279
- kind: "node",
280
- nodeId: node.id
281
- },
282
- center: {
283
- x: position.x + (node.width ?? 0) / 2,
284
- y: position.y + (node.height ?? 0) / 2
285
- }
286
- });
287
- for (const port of node.ports ?? []) shapes.set(port.id, {
288
- ref: {
289
- kind: "node",
290
- nodeId: node.id,
291
- port: port.id
292
- },
293
- center: {
294
- x: position.x + (port.x ?? 0) + (port.width ?? 0) / 2,
295
- y: position.y + (port.y ?? 0) + (port.height ?? 0) / 2
296
- }
297
- });
298
- for (const edge of node.edges ?? []) edges.push({
299
- edge,
300
- offset: position,
301
- routing: edge.layoutOptions?.["elk.edgeRouting"] ?? edge.layoutOptions?.["org.eclipse.elk.edgeRouting"] ?? routing
302
- });
303
- for (const child of node.children ?? []) visit(child, position, routing);
304
- }
305
- visit(graph, {
306
- x: 0,
307
- y: 0
308
- }, options.routing ?? "POLYLINE");
309
- const result = /* @__PURE__ */ new Map();
310
- for (const { edge, offset: inheritedOffset, routing } of edges) {
311
- const offset = edge.container ? frames.get(edge.container) ?? inheritedOffset : inheritedOffset;
312
- const point = (p) => ({
313
- x: p.x + offset.x,
314
- y: p.y + offset.y
315
- });
316
- const parents = /* @__PURE__ */ new Map();
317
- const root = (key) => {
318
- let current = key;
319
- while (parents.has(current)) current = parents.get(current);
320
- return current;
321
- };
322
- const join = (a, b) => {
323
- const x = root(a), y = root(b);
324
- if (x !== y) parents.set(x < y ? y : x, x < y ? x : y);
325
- };
326
- for (const section of edge.sections ?? []) {
327
- for (const id of section.outgoingSections ?? []) join(`${section.id}:end`, `${id}:start`);
328
- for (const id of section.incomingSections ?? []) join(`${id}:end`, `${section.id}:start`);
329
- }
330
- const sections = [];
331
- let malformed = false;
332
- for (const section of edge.sections ?? []) {
333
- const points = [
334
- section.startPoint,
335
- ...section.bendPoints ?? [],
336
- section.endPoint
337
- ].map(point);
338
- let path;
339
- if (points.some((p) => !Number.isFinite(p.x) || !Number.isFinite(p.y))) {
340
- malformed = true;
341
- for (let i = 0; i < points.length; i++) if (!Number.isFinite(points[i].x) || !Number.isFinite(points[i].y)) points[i] = {
342
- x: offset.x + i * 20,
343
- y: offset.y
344
- };
345
- }
346
- if (routing === "SPLINES") if ((points.length - 1) % 3 || points.length < 4) {
347
- malformed = true;
348
- path = require_routing.pathFromPoints([points[0], points.at(-1)]);
349
- } else path = require_routing.pathFromSplinePoints(points);
350
- else path = require_routing.pathFromPoints(points);
351
- const fromShape = section.incomingShape ?? (!section.incomingSections?.length && edge.sources.length === 1 ? edge.sources[0] : void 0);
352
- const toShape = section.outgoingShape ?? (!section.outgoingSections?.length && edge.targets.length === 1 ? edge.targets[0] : void 0);
353
- sections.push({
354
- id: section.id,
355
- from: fromShape ? shapes.get(fromShape)?.ref ?? {
356
- kind: "node",
357
- nodeId: fromShape
358
- } : {
359
- kind: "junction",
360
- id: `${edge.id}:${root(`${section.id}:start`)}`
361
- },
362
- to: toShape ? shapes.get(toShape)?.ref ?? {
363
- kind: "node",
364
- nodeId: toShape
365
- } : {
366
- kind: "junction",
367
- id: `${edge.id}:${root(`${section.id}:end`)}`
368
- },
369
- path
370
- });
371
- }
372
- if (!sections.length) {
373
- malformed = true;
374
- const source = shapes.get(edge.sources[0] ?? ""), origin = source?.center ?? {
375
- x: 0,
376
- y: 0
377
- };
378
- const targets = [...edge.targets, ...edge.sources.slice(1)];
379
- for (const [i, id] of (targets.length ? targets : [""]).entries()) {
380
- const target = shapes.get(id);
381
- sections.push({
382
- id: `${edge.id}:fallback:${i}`,
383
- from: source?.ref ?? {
384
- kind: "node",
385
- nodeId: edge.sources[0] ?? ""
386
- },
387
- to: target?.ref ?? {
388
- kind: "node",
389
- nodeId: id
390
- },
391
- path: require_routing.pathFromPoints([origin, target?.center ?? {
392
- x: origin.x + 40,
393
- y: origin.y + 40
394
- }])
395
- });
396
- }
397
- }
398
- result.set(edge.id, require_routing.freeze({
399
- edgeId: edge.id,
400
- sections,
401
- status: malformed ? "fallback" : "routed",
402
- diagnostics: malformed ? [{
403
- code: "MISSING_GEOMETRY",
404
- edgeId: edge.id,
405
- message: "ELK sections contain missing or invalid geometry; rendered fallback paths"
406
- }] : []
407
- }));
408
- }
409
- return result;
410
- }
411
-
412
- //#endregion
413
- //#region src/elkjs/index.ts
414
- function isLayoutEdgeLabel(label) {
415
- return getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true && (Boolean(label.text) || label.width !== void 0 || label.height !== void 0);
416
- }
417
- var ELK = class {
418
- #options;
419
- #algorithmIds;
420
- constructor(options = {}) {
421
- this.#options = options;
422
- this.#algorithmIds = new Set([
423
- "box",
424
- "fixed",
425
- "random",
426
- "rectpacking",
427
- "sporeCompaction",
428
- "sporeOverlap",
429
- ...(options.algorithms ?? ["layered"]).filter((id) => id === "layered")
430
- ]);
431
- }
432
- async knownLayoutAlgorithms() {
433
- return [...this.#algorithmIds].map((id) => ({
434
- id: id === "layered" ? "org.eclipse.elk.layered" : id,
435
- name: {
436
- layered: "Layered",
437
- box: "Box",
438
- fixed: "Fixed",
439
- random: "Random",
440
- rectpacking: "Rectangle Packing",
441
- sporeCompaction: "SPOrE Compaction",
442
- sporeOverlap: "SPOrE Overlap Removal"
443
- }[id],
444
- category: id === "layered" ? "layered" : "other",
445
- knownOptions: id === "layered" ? require_layered.elkLayeredOptionDefinitions.map((definition) => definition.elkId) : id === "box" ? [
446
- "padding",
447
- "spacing.nodeNode",
448
- "aspectRatio",
449
- "box.packingMode"
450
- ] : id === "random" ? [
451
- "padding",
452
- "spacing.nodeNode",
453
- "aspectRatio",
454
- "randomSeed"
455
- ] : ["position", "bendPoints"]
456
- }));
457
- }
458
- async knownLayoutOptions() {
459
- return [{
460
- id: "org.eclipse.elk.algorithm",
461
- name: "Layout Algorithm",
462
- type: "STRING"
463
- }, ...require_layered.elkLayeredOptionDefinitions.map((definition) => ({
464
- id: definition.elkId,
465
- name: definition.name,
466
- type: definition.type,
467
- targets: [...definition.targets]
468
- }))];
469
- }
470
- async knownLayoutCategories() {
471
- return [{
472
- id: "layered",
473
- name: "Layered",
474
- knownLayouters: this.#algorithmIds.has("layered") ? ["layered"] : []
475
- }, {
476
- id: "other",
477
- name: "Other",
478
- knownLayouters: [
479
- "box",
480
- "fixed",
481
- "random"
482
- ]
483
- }];
484
- }
485
- terminateWorker() {}
486
- layout(graph, arguments_ = {}) {
487
- return this.#layout(graph, arguments_);
488
- }
489
- async #layout(graph, arguments_ = {}) {
490
- const startedAt = performance.now();
491
- if (graph === void 0 || graph === null) throw new TypeError("Missing mandatory parameter: graph");
492
- if (typeof graph.id !== "string" && !(typeof graph.id === "number" && Number.isInteger(graph.id))) throw new TypeError("Graph id must be a string or integer");
493
- delete graph.logging;
494
- const layoutOptions = {
495
- ...this.#options.defaultLayoutOptions,
496
- ...arguments_.layoutOptions,
497
- ...graph.properties,
498
- ...graph.layoutOptions
499
- };
500
- getOption(layoutOptions, "debugMode");
501
- getOption(layoutOptions, "interactiveLayout");
502
- getOption(layoutOptions, "layered.generatePositionAndLayerIds");
503
- getOption(layoutOptions, "topdown.scaleFactor");
504
- getOption(layoutOptions, "contentAlignment");
505
- for (const child of graph.children ?? []) {
506
- const childOptions = child.layoutOptions ?? {};
507
- getOption(childOptions, "layered.layering.layerId");
508
- getOption(childOptions, "layered.crossingMinimization.positionId");
509
- getOption(childOptions, "layered.layering.layerChoiceConstraint");
510
- getOption(childOptions, "layered.crossingMinimization.positionChoiceConstraint");
511
- getOption(childOptions, "layered.crossingMinimization.inLayerPredOf");
512
- getOption(childOptions, "layered.crossingMinimization.inLayerSuccOf");
513
- getOption(childOptions, "topdown.scaleFactor");
514
- getOption(childOptions, "layered.considerModelOrder.groupModelOrder.componentGroupId");
515
- for (const port of child.ports ?? []) getOption(port.layoutOptions ?? {}, "layered.considerModelOrder.groupModelOrder.componentGroupId");
516
- }
517
- for (const edge of graph.edges ?? []) getOption(edge.layoutOptions ?? {}, "layered.considerModelOrder.groupModelOrder.componentGroupId");
518
- const requestedAlgorithm = String(getOption(layoutOptions, "algorithm") ?? "layered");
519
- const algorithm = requestedAlgorithm.replace(/^(?:org\.eclipse\.)?elk\./, "");
520
- if (algorithm !== "layered" && algorithm !== "box" && algorithm !== "fixed" && algorithm !== "random" && algorithm !== "rectpacking" && algorithm !== "sporeCompaction" && algorithm !== "sporeOverlap") throw new Error(`org.eclipse.elk.core.UnsupportedConfigurationException: Layout algorithm '${requestedAlgorithm}' not found`);
521
- const hasHierarchy = (graph.children ?? []).some((child) => (child.children?.length ?? 0) > 0);
522
- const insideSelfLoopBaseHeightByNodeId = /* @__PURE__ */ new Map();
523
- const hierarchyRestorations = [];
524
- const syntheticPortIds = /* @__PURE__ */ new Set();
525
- const authoredPortsByCompound = /* @__PURE__ */ new Map();
526
- const authoredOptionsByCompound = /* @__PURE__ */ new Map();
527
- const hierarchyBoundaryCountByEdge = /* @__PURE__ */ new Map();
528
- const originalHierarchyEndpoints = new Map((graph.edges ?? []).map((edge) => [edge, {
529
- sourceId: String(edge.sources?.[0] ?? edge.source),
530
- targetId: String(edge.targets?.[0] ?? edge.target),
531
- sources: edge.sources,
532
- targets: edge.targets,
533
- source: edge.source,
534
- target: edge.target
535
- }]));
536
- let hasHierarchyCrossingEdges = false;
537
- const hierarchyHandling = getOption(layoutOptions, "hierarchyHandling");
538
- const topdownLayout = getBooleanOption(layoutOptions, "topdownLayout") === true;
539
- const separateHierarchy = hasHierarchy && hierarchyHandling !== void 0 && hierarchyHandling !== "INCLUDE_CHILDREN";
540
- if (hasHierarchy && topdownLayout && hierarchyHandling === "INCLUDE_CHILDREN") throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Topdown layout cannot be used together with hierarchy handling.");
541
- if (separateHierarchy) {
542
- const hasCrossHierarchyEdge = (container) => {
543
- const directEndpointIds = /* @__PURE__ */ new Set();
544
- for (const child of container.children ?? []) {
545
- directEndpointIds.add(String(child.id));
546
- for (const port of child.ports ?? []) directEndpointIds.add(String(port.id));
547
- }
548
- if ((container.edges ?? []).some((edge) => {
549
- const sourceId = String(edge.sources?.[0] ?? edge.source);
550
- const targetId = String(edge.targets?.[0] ?? edge.target);
551
- return !directEndpointIds.has(sourceId) || !directEndpointIds.has(targetId);
552
- })) return true;
553
- return (container.children ?? []).some((child) => hasCrossHierarchyEdge(child));
554
- };
555
- if (hasCrossHierarchyEdge(graph)) throw new Error("org.eclipse.elk.core.UnsupportedGraphException: Hierarchical edges require INCLUDE_CHILDREN");
556
- for (const child of graph.children ?? []) {
557
- if ((child.children?.length ?? 0) === 0) continue;
558
- await this.#layout(child, {
559
- ...arguments_,
560
- layoutOptions: {
561
- ...arguments_.layoutOptions,
562
- ...child.layoutOptions,
563
- hierarchyHandling
564
- },
565
- logging: false,
566
- measureExecutionTime: false
567
- });
568
- }
569
- }
570
- if (hasHierarchy && topdownLayout) {
571
- if (getOption(layoutOptions, "topdown.nodeType") === void 0) throw new Error(`${String(graph.id)} has not been assigned a top-down node type.`);
572
- for (const child of graph.children ?? []) {
573
- if ((child.children?.length ?? 0) === 0) continue;
574
- const childOptions = {
575
- ...layoutOptions,
576
- ...child.layoutOptions
577
- };
578
- const childPadding = parsePadding(getOption(childOptions, "padding"), 12);
579
- const parallelNode = getOption(childOptions, "topdown.nodeType") === "PARALLEL_NODE";
580
- const width = getNumberOption(childOptions, "topdown.hierarchicalNodeWidth") ?? (parallelNode ? 150 : 0);
581
- const aspectRatio = getNumberOption(childOptions, "topdown.hierarchicalNodeAspectRatio") ?? (parallelNode ? 1.414 : 1 / Math.sqrt(2));
582
- child.width = Math.max(child.width ?? 0, width + childPadding.left + childPadding.right);
583
- child.height = Math.max(child.height ?? 0, width / aspectRatio + childPadding.top + childPadding.bottom);
584
- }
585
- } else if (hasHierarchy && !separateHierarchy) {
586
- getNumberOption(layoutOptions, "layered.crossingMinimization.hierarchicalSweepiness");
587
- for (const child of graph.children ?? []) {
588
- if ((child.children?.length ?? 0) === 0) continue;
589
- const descendantById = /* @__PURE__ */ new Map();
590
- const descendantOwnerByEndpointId = /* @__PURE__ */ new Map();
591
- const collectDescendants = (node) => {
592
- for (const descendant of node.children ?? []) {
593
- descendantById.set(String(descendant.id), descendant);
594
- descendantOwnerByEndpointId.set(String(descendant.id), descendant);
595
- for (const port of descendant.ports ?? []) descendantOwnerByEndpointId.set(String(port.id), descendant);
596
- collectDescendants(descendant);
597
- }
598
- };
599
- collectDescendants(child);
600
- const internalEdges = (graph.edges ?? []).filter((edge) => {
601
- const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
602
- return descendantOwnerByEndpointId.has(sourceId) && descendantOwnerByEndpointId.has(targetId);
603
- });
604
- const crossingEdges = (graph.edges ?? []).filter((edge) => {
605
- const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
606
- return descendantOwnerByEndpointId.has(sourceId) !== descendantOwnerByEndpointId.has(targetId);
607
- });
608
- for (const edge of crossingEdges) hierarchyBoundaryCountByEdge.set(edge, (hierarchyBoundaryCountByEdge.get(edge) ?? 0) + 1);
609
- hasHierarchyCrossingEdges ||= crossingEdges.length > 0;
610
- getBooleanOption(layoutOptions, "layered.mergeHierarchyEdges");
611
- const proxyByKind = /* @__PURE__ */ new Map();
612
- const proxyFor = (kind) => {
613
- const key = kind;
614
- let proxy = proxyByKind.get(key);
615
- if (!proxy) {
616
- proxy = {
617
- id: `__native_hierarchy_${String(child.id)}_${key.replace(/[^a-zA-Z0-9]/g, "_")}`,
618
- width: 0,
619
- height: 0
620
- };
621
- proxyByKind.set(key, proxy);
622
- }
623
- return proxy;
624
- };
625
- const temporaryEdges = [
626
- ...child.edges ?? [],
627
- ...internalEdges.filter((edge) => !(child.edges ?? []).some((candidate) => String(candidate.id) === String(edge.id))),
628
- ...crossingEdges.map((edge) => {
629
- const { sourceId, targetId } = originalHierarchyEndpoints.get(edge);
630
- const sourceInside = descendantOwnerByEndpointId.has(sourceId);
631
- const proxy = proxyFor(sourceInside ? "output" : "input");
632
- return {
633
- ...edge,
634
- id: `__native_hierarchy_edge_${String(child.id)}_${String(edge.id)}`,
635
- sources: [sourceInside ? sourceId : proxy.id],
636
- targets: [sourceInside ? proxy.id : targetId],
637
- source: void 0,
638
- target: void 0,
639
- sections: void 0
640
- };
641
- })
642
- ];
643
- const temporaryChild = {
644
- ...child,
645
- children: [...child.children ?? [], ...proxyByKind.values()],
646
- edges: temporaryEdges
647
- };
648
- await this.#layout(temporaryChild, {
649
- ...arguments_,
650
- layoutOptions: {
651
- ...arguments_.layoutOptions,
652
- hierarchyHandling: "INCLUDE_CHILDREN"
653
- },
654
- logging: false,
655
- measureExecutionTime: false
656
- });
657
- const childPadding = parsePadding(getOption({
658
- ...layoutOptions,
659
- ...child.layoutOptions
660
- }, "padding"), 12);
661
- if (proxyByKind.has("input")) {
662
- const direction = getDirection(layoutOptions);
663
- const horizontal = direction === "right" || direction === "left";
664
- if (direction === "right" || direction === "down") {
665
- const minimumFlow = Math.min(...(child.children ?? []).map((node) => horizontal ? node.x ?? 0 : node.y ?? 0));
666
- const delta = (horizontal ? childPadding.left : childPadding.top) - minimumFlow;
667
- for (const node of child.children ?? []) if (horizontal) node.x = (node.x ?? 0) + delta;
668
- else node.y = (node.y ?? 0) + delta;
669
- for (const edge of temporaryEdges) for (const section of edge.sections ?? []) for (const point of [
670
- section.startPoint,
671
- ...section.bendPoints ?? [],
672
- section.endPoint
673
- ]) if (horizontal) point.x += delta;
674
- else point.y += delta;
675
- }
676
- }
677
- for (const internalEdge of internalEdges) {
678
- const temporary = temporaryEdges.find((candidate) => String(candidate.id) === String(internalEdge.id));
679
- if (temporary?.sections) internalEdge.sections = temporary.sections;
680
- }
681
- child.width = Math.max(0, ...(child.children ?? []).map((node) => (node.x ?? 0) + (node.width ?? 0))) + childPadding.right;
682
- child.height = Math.max(0, ...(child.children ?? []).map((node) => (node.y ?? 0) + (node.height ?? 0))) + childPadding.bottom;
683
- const relativeRect = (id) => {
684
- const owner = descendantOwnerByEndpointId.get(id);
685
- if (!owner) return void 0;
686
- const path = [];
687
- const visit = (parent) => {
688
- for (const candidate of parent.children ?? []) {
689
- path.push(candidate);
690
- if (candidate === owner || visit(candidate)) return true;
691
- path.pop();
692
- }
693
- return false;
694
- };
695
- if (!visit(child)) return void 0;
696
- const port = owner.ports?.find((candidate) => String(candidate.id) === id);
697
- const ownerX = path.reduce((sum, node) => sum + (node.x ?? 0), 0);
698
- const ownerY = path.reduce((sum, node) => sum + (node.y ?? 0), 0);
699
- if (port) return {
700
- x: ownerX + (port.x ?? 0) + (port.width ?? 0) / 2,
701
- y: ownerY + (port.y ?? 0) + (port.height ?? 0) / 2,
702
- width: 0,
703
- height: 0
704
- };
705
- return {
706
- x: ownerX,
707
- y: ownerY,
708
- width: path.at(-1)?.width ?? 0,
709
- height: path.at(-1)?.height ?? 0
710
- };
711
- };
712
- authoredPortsByCompound.set(child, child.ports);
713
- authoredOptionsByCompound.set(child, child.layoutOptions);
714
- const ports = [...child.ports ?? []];
715
- for (const edge of crossingEdges) {
716
- const original = originalHierarchyEndpoints.get(edge);
717
- const { sourceId, targetId } = original;
718
- const sourceInside = descendantOwnerByEndpointId.has(sourceId);
719
- const rect = relativeRect(sourceInside ? sourceId : targetId);
720
- if (!rect) continue;
721
- const portId = `__native_hierarchy_port_${String(child.id)}_${String(edge.id)}`;
722
- syntheticPortIds.add(portId);
723
- const direction = getDirection(layoutOptions);
724
- const useFarSide = sourceInside === (direction === "right" || direction === "down");
725
- ports.push({
726
- id: portId,
727
- width: 0,
728
- height: 0,
729
- x: direction === "right" || direction === "left" ? (rect.x ?? 0) + (useFarSide ? rect.width ?? 0 : 0) : (rect.x ?? 0) + (rect.width ?? 0) / 2,
730
- y: direction === "down" || direction === "up" ? (rect.y ?? 0) + (useFarSide ? rect.height ?? 0 : 0) : (rect.y ?? 0) + (rect.height ?? 0) / 2
731
- });
732
- if (!hierarchyRestorations.some((restoration) => restoration.edge === edge)) hierarchyRestorations.push({
733
- edge,
734
- ...original
735
- });
736
- if (sourceInside) edge.sources = [portId];
737
- else edge.targets = [portId];
738
- edge.source = void 0;
739
- edge.target = void 0;
740
- }
741
- child.ports = ports;
742
- child.layoutOptions = {
743
- ...child.layoutOptions,
744
- "elk.portConstraints": "FIXED_POS"
745
- };
746
- const insideLoopCount = (graph.edges ?? []).filter((edge) => isInsideSelfLoop(graph, edge, String(child.id))).length;
747
- if (insideLoopCount > 0) {
748
- const baseHeight = child.height ?? 0;
749
- insideSelfLoopBaseHeightByNodeId.set(String(child.id), baseHeight);
750
- child.height = baseHeight + insideLoopCount * 11;
751
- }
752
- }
753
- }
754
- applyNodeMicroLayout(graph, layoutOptions);
755
- const graph_ = toGraph(graph, layoutOptions);
756
- const layerConstraintByNodeId = new Map((graph.children ?? []).map((node) => [String(node.id), String(getOption(node.layoutOptions ?? {}, "layered.layering.layerConstraint") ?? getOption(node.layoutOptions ?? {}, "layerConstraint") ?? "NONE")]));
757
- if (algorithm === "layered" && graph_.edges.some((edge) => {
758
- if (edge.sourceId === edge.targetId) return false;
759
- const sourceConstraint = layerConstraintByNodeId.get(edge.sourceId);
760
- const targetConstraint = layerConstraintByNodeId.get(edge.targetId);
761
- return (sourceConstraint === "FIRST" || sourceConstraint === "FIRST_SEPARATE") && (targetConstraint === "FIRST" || targetConstraint === "FIRST_SEPARATE");
762
- })) throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Layer constraints conflict");
763
- const padding = parsePadding(getOption(layoutOptions, "padding"), algorithm === "layered" ? 12 : 0);
764
- const laidOut = await (algorithm === "sporeCompaction" ? require_layout_engine.executeElkjs0111Layout({
765
- algorithm: "sporeCompaction",
766
- graph: graph_,
767
- options: {
768
- padding,
769
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
770
- }
771
- }) : algorithm === "sporeOverlap" ? require_layout_engine.executeElkjs0111Layout({
772
- algorithm: "sporeOverlap",
773
- graph: graph_,
774
- options: {
775
- padding,
776
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
777
- }
778
- }) : algorithm === "rectpacking" ? require_layout_engine.executeElkjs0111Layout({
779
- algorithm: "rectpacking",
780
- graph: graph_,
781
- options: {
782
- padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
783
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode") ?? 15
784
- }
785
- }) : algorithm === "random" ? require_layout_engine.executeElkjs0111Layout({
786
- algorithm: "random",
787
- graph: graph_,
788
- options: {
789
- padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
790
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
791
- aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
792
- seed: getNumberOption(layoutOptions, "randomSeed")
793
- }
794
- }) : algorithm === "box" ? require_layout_engine.executeElkjs0111Layout({
795
- algorithm: "box",
796
- graph: graph_,
797
- options: {
798
- padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
799
- spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
800
- aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
801
- interactive: getBooleanOption(layoutOptions, "interactive"),
802
- expandNodes: getBooleanOption(layoutOptions, "expandNodes"),
803
- priority: (node) => {
804
- const child = graph.children?.find((candidate) => String(candidate.id) === node.id);
805
- return getNumberOption(child?.layoutOptions ?? {}, "priority");
806
- }
807
- }
808
- }) : algorithm === "fixed" ? require_layout_engine.executeElkjs0111Layout({
809
- algorithm: "fixed",
810
- graph: graph_,
811
- options: { direction: getDirection(layoutOptions) }
812
- }) : require_layout_engine.executeElkjs0111Layout({
813
- algorithm: "layered",
814
- graph: graph_,
815
- options: {
816
- direction: getDirection(layoutOptions),
817
- spacing: {
818
- node: getNumberOption(layoutOptions, "spacing.nodeNode") ?? getNumberOption(layoutOptions, "layered.spacing.baseValue"),
819
- layer: (getNumberOption(layoutOptions, "layered.spacing.nodeNodeBetweenLayers") ?? getNumberOption(layoutOptions, "layered.spacing.baseValue") ?? 20) + (hasHierarchyCrossingEdges ? 5 * Math.max(...hierarchyBoundaryCountByEdge.values()) : 0)
820
- },
821
- padding,
822
- constraints: { layer: () => void 0 },
823
- settings: {
824
- ...getLayeredSettings(layoutOptions),
825
- ...hierarchyHandling === "INCLUDE_CHILDREN" && getOption(layoutOptions, "layered.crossingMinimization.greedySwitchHierarchical.type") !== void 0 ? { "crossingMinimization.greedySwitch.type": String(getOption(layoutOptions, "layered.crossingMinimization.greedySwitchHierarchical.type")) } : {}
826
- },
827
- nodeSettings: (node) => {
828
- const child = graph.children?.find((candidate) => String(candidate.id) === node.id);
829
- return getElementLayeredSettings(child?.layoutOptions ?? {});
830
- },
831
- edgeSettings: (edge) => {
832
- const elkEdge = graph.edges?.find((candidate) => String(candidate.id) === edge.id);
833
- return {
834
- ...getElementLayeredSettings(elkEdge?.layoutOptions ?? {}),
835
- ...getElementLayeredSettings(elkEdge?.labels?.[0]?.layoutOptions ?? {})
836
- };
837
- },
838
- portSettings: (port, node) => {
839
- const elkPort = (graph.children?.find((candidate) => String(candidate.id) === node.id))?.ports?.find((candidate) => String(candidate.id) === port.name);
840
- return {
841
- ...getElementLayeredSettings(elkPort?.layoutOptions ?? {}),
842
- "port.labelWidth": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.width ?? 0)),
843
- "port.labelHeight": Math.max(0, ...(elkPort?.labels ?? []).map((label) => label.height ?? 0))
844
- };
845
- }
846
- }
847
- }));
848
- if (hierarchyRestorations.length > 0) {
849
- const direction = getDirection(layoutOptions);
850
- const horizontal = direction === "right" || direction === "left";
851
- const cross$1 = (point) => horizontal ? point.y : point.x;
852
- const nodeSpacing = getNumberOption(layoutOptions, "spacing.nodeNode") ?? 20;
853
- const shiftsByOutsideId = /* @__PURE__ */ new Map();
854
- for (const restoration of hierarchyRestorations) {
855
- const route = laidOut.edges.find((edge) => edge.id === String(restoration.edge.id))?.points;
856
- if (!route || route.length < 2) continue;
857
- const originalSourceId = String(restoration.sources?.[0] ?? restoration.source);
858
- const originalTargetId = String(restoration.targets?.[0] ?? restoration.target);
859
- const sourceInside = !laidOut.nodes.some((node) => node.id === originalSourceId);
860
- const outsideId = sourceInside ? originalTargetId : originalSourceId;
861
- const delta = sourceInside ? cross$1(route[0]) - cross$1(route.at(-1)) : cross$1(route.at(-1)) - cross$1(route[0]);
862
- const candidates = shiftsByOutsideId.get(outsideId) ?? [];
863
- candidates.push(delta);
864
- shiftsByOutsideId.set(outsideId, candidates);
865
- }
866
- for (const [outsideId, candidates] of shiftsByOutsideId) {
867
- const delta = [...candidates].sort((left, right) => Math.abs(left) - Math.abs(right))[0];
868
- const node = laidOut.nodes.find((candidate) => candidate.id === outsideId);
869
- if (!node || Math.abs(delta) < 1e-9 || Math.abs(delta) > nodeSpacing) continue;
870
- if (horizontal) node.y = (node.y ?? 0) + delta;
871
- else node.x = (node.x ?? 0) + delta;
872
- for (const edge of laidOut.edges) {
873
- const points = edge.points;
874
- if (!points || points.length === 0) continue;
875
- if (edge.sourceId === outsideId) if (horizontal) points[0].y += delta;
876
- else points[0].x += delta;
877
- if (edge.targetId === outsideId) if (horizontal) points.at(-1).y += delta;
878
- else points.at(-1).x += delta;
879
- }
880
- }
881
- const modelOrder = new Map((graph.children ?? []).map((node, index) => [String(node.id), index]));
882
- const flowLayers = /* @__PURE__ */ new Map();
883
- for (const node of laidOut.nodes) {
884
- const flow = horizontal ? node.x ?? 0 : node.y ?? 0;
885
- const layer = flowLayers.get(flow) ?? [];
886
- layer.push(node);
887
- flowLayers.set(flow, layer);
888
- }
889
- for (const layer of flowLayers.values()) {
890
- layer.sort((left, right) => (modelOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (modelOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER));
891
- let crossEnd = Number.NEGATIVE_INFINITY;
892
- for (const node of layer) {
893
- const authoredCross = horizontal ? node.y ?? 0 : node.x ?? 0;
894
- const compactedCross = Math.max(authoredCross, crossEnd === Number.NEGATIVE_INFINITY ? authoredCross : crossEnd + nodeSpacing);
895
- const delta = compactedCross - authoredCross;
896
- if (horizontal) node.y = compactedCross;
897
- else node.x = compactedCross;
898
- if (Math.abs(delta) > 1e-9) for (const edge of laidOut.edges) {
899
- const points = edge.points;
900
- if (!points || points.length === 0) continue;
901
- if (edge.sourceId === node.id) if (horizontal) points[0].y += delta;
902
- else points[0].x += delta;
903
- if (edge.targetId === node.id) if (horizontal) points.at(-1).y += delta;
904
- else points.at(-1).x += delta;
905
- }
906
- crossEnd = compactedCross + (horizontal ? node.height : node.width);
907
- }
908
- }
909
- const edgeNodeSpacing = getNumberOption(layoutOptions, "spacing.edgeNodeBetweenLayers") ?? 10;
910
- for (const restoration of hierarchyRestorations) {
911
- const nativeEdge = laidOut.edges.find((edge) => edge.id === String(restoration.edge.id));
912
- const route = nativeEdge?.points;
913
- if (!route || route.length < 2) continue;
914
- const start = route[0];
915
- const end = route.at(-1);
916
- if (nativeEdge.sourceId === nativeEdge.targetId) continue;
917
- if (Math.abs(cross$1(start) - cross$1(end)) < 1e-9) {
918
- route.splice(1, route.length - 2);
919
- continue;
920
- }
921
- const originalSourceId = String(restoration.sources?.[0] ?? restoration.source);
922
- const sourceInside = !laidOut.nodes.some((node) => node.id === originalSourceId);
923
- const flowForward = direction === "right" || direction === "down";
924
- const track = sourceInside ? (horizontal ? end.x : end.y) - (flowForward ? edgeNodeSpacing : -edgeNodeSpacing) : (horizontal ? start.x : start.y) + (flowForward ? edgeNodeSpacing : -edgeNodeSpacing);
925
- route.splice(1, route.length - 2, horizontal ? {
926
- x: track,
927
- y: start.y
928
- } : {
929
- x: start.x,
930
- y: track
931
- }, horizontal ? {
932
- x: track,
933
- y: end.y
934
- } : {
935
- x: end.x,
936
- y: track
937
- });
938
- }
939
- }
940
- const resultPolicy = laidOut[require_layout_engine.elkjs0111ResultPolicy];
941
- applyLayout(graph, laidOut, padding, layoutOptions, resultPolicy);
942
- if (resultPolicy?.providerBounds) {
943
- graph.width = resultPolicy.providerBounds.width;
944
- graph.height = resultPolicy.providerBounds.height;
945
- }
946
- for (const restoration of hierarchyRestorations) {
947
- restoration.edge.sources = restoration.sources;
948
- restoration.edge.targets = restoration.targets;
949
- restoration.edge.source = restoration.source;
950
- restoration.edge.target = restoration.target;
951
- for (const section of restoration.edge.sections ?? []) {
952
- if (section.incomingShape != null && syntheticPortIds.has(String(section.incomingShape))) section.incomingShape = restoration.sources?.[0] ?? restoration.source;
953
- if (section.outgoingShape != null && syntheticPortIds.has(String(section.outgoingShape))) section.outgoingShape = restoration.targets?.[0] ?? restoration.target;
954
- }
955
- }
956
- for (const [compound, ports] of authoredPortsByCompound) compound.ports = ports;
957
- for (const [compound, options_] of authoredOptionsByCompound) compound.layoutOptions = options_;
958
- if (hasHierarchy && topdownLayout) for (const child of graph.children ?? []) {
959
- if ((child.children?.length ?? 0) === 0) continue;
960
- const position = {
961
- x: child.x,
962
- y: child.y
963
- };
964
- await this.#layout(child, {
965
- ...arguments_,
966
- logging: false,
967
- measureExecutionTime: false
968
- });
969
- child.x = position.x;
970
- child.y = position.y;
971
- }
972
- applyInsideSelfLoops(graph, insideSelfLoopBaseHeightByNodeId);
973
- if (arguments_.logging || arguments_.measureExecutionTime) graph.logging = {
974
- name: "Native TypeScript layout",
975
- children: [{ name: String(algorithm) }],
976
- ...arguments_.measureExecutionTime ? { executionTime: (performance.now() - startedAt) / 1e3 } : {}
977
- };
978
- return graph;
979
- }
980
- };
981
- function parsePadding(value, fallback = 0) {
982
- if (typeof value === "number") return {
983
- top: value,
984
- right: value,
985
- bottom: value,
986
- left: value
987
- };
988
- if (typeof value !== "string") return {
989
- top: fallback,
990
- right: fallback,
991
- bottom: fallback,
992
- left: fallback
993
- };
994
- const padding = {
995
- top: 0,
996
- right: 0,
997
- bottom: 0,
998
- left: 0
999
- };
1000
- for (const match of value.matchAll(/(top|right|bottom|left)\s*=\s*(-?\d+(?:\.\d+)?)/g)) {
1001
- const side = match[1];
1002
- padding[side] = Number(match[2]);
1003
- }
1004
- return padding;
1005
- }
1006
- function parseVector(value) {
1007
- if (typeof value === "object" && value !== null && "x" in value && "y" in value) return {
1008
- x: Number(value.x),
1009
- y: Number(value.y)
1010
- };
1011
- if (typeof value !== "string") return void 0;
1012
- const match = value.match(/^\s*\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)\s*$/);
1013
- return match ? {
1014
- x: Number(match[1]),
1015
- y: Number(match[2])
1016
- } : void 0;
1017
- }
1018
- function applyNodeMicroLayout(graph, globalOptions) {
1019
- const labelPadding = parsePadding(getOption(globalOptions, "nodeLabels.padding"), 5);
1020
- for (const node of graph.children ?? []) {
1021
- const constraints = String(getOption(node.layoutOptions ?? {}, "nodeSize.constraints") ?? "");
1022
- if (!constraints) continue;
1023
- const configuredSizeOptions = getOption(node.layoutOptions ?? {}, "nodeSize.options");
1024
- const sizeOptions = new Set(configuredSizeOptions === void 0 ? ["DEFAULT_MINIMUM_SIZE"] : String(configuredSizeOptions).split(/[\s,;]+/).filter(Boolean));
1025
- const effectivelyFixedPortLabelSize = constraints.includes("PORT_LABELS") && !constraints.includes("NODE_LABELS") && !constraints.includes("MINIMUM_SIZE");
1026
- const preserveComputedCompoundSize = (node.children?.length ?? 0) > 0;
1027
- let width = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.width ?? 0 : 0;
1028
- let height = effectivelyFixedPortLabelSize || preserveComputedCompoundSize ? node.height ?? 0 : 0;
1029
- let insideHorizontalInset = 0;
1030
- let insideVerticalInset = 0;
1031
- const insideLabelCells = /* @__PURE__ */ new Map();
1032
- if (constraints.includes("PORTS")) {
1033
- const spacing = getNumberOption(globalOptions, "spacing.portPort") ?? 10;
1034
- const sideCounts = {
1035
- NORTH: 0,
1036
- EAST: 0,
1037
- SOUTH: 0,
1038
- WEST: 0
1039
- };
1040
- for (const port of node.ports ?? []) {
1041
- const side = String(getOption(port.layoutOptions ?? {}, "port.side") ?? "EAST");
1042
- if (side in sideCounts) sideCounts[side]++;
1043
- }
1044
- const nodeId = String(node.id);
1045
- for (const edge of graph.edges ?? []) {
1046
- const source = String(edge.sources?.[0] ?? edge.source);
1047
- const target = String(edge.targets?.[0] ?? edge.target);
1048
- if (source === nodeId) sideCounts.EAST++;
1049
- if (target === nodeId) sideCounts.WEST++;
1050
- }
1051
- width = Math.max(width, (Math.max(sideCounts.NORTH, sideCounts.SOUTH) + 1) * spacing);
1052
- height = Math.max(height, (Math.max(sideCounts.EAST, sideCounts.WEST) + 1) * spacing);
1053
- if (!preserveComputedCompoundSize && sideCounts.NORTH === 0 && sideCounts.SOUTH === 0) width = 0;
1054
- if (!preserveComputedCompoundSize && sideCounts.EAST === 0 && sideCounts.WEST === 0) height = 0;
1055
- }
1056
- if (constraints.includes("NODE_LABELS")) {
1057
- for (const label of node.labels ?? []) {
1058
- if (!label.text) continue;
1059
- const placement = String(getOption({
1060
- ...globalOptions,
1061
- ...node.layoutOptions,
1062
- ...label.layoutOptions
1063
- }, "nodeLabels.placement") ?? "");
1064
- if (!placement) continue;
1065
- const labelWidth = label.width ?? 0;
1066
- const labelHeight = label.height ?? 0;
1067
- if (placement.includes("OUTSIDE")) {
1068
- if (!sizeOptions.has("OUTSIDE_NODE_LABELS_OVERHANG")) width = Math.max(width, labelWidth);
1069
- } else {
1070
- width = Math.max(width, labelWidth + labelPadding.left + labelPadding.right);
1071
- const verticalInset = labelHeight * (placement.includes("V_CENTER") ? 1 : 2) + labelPadding.top + labelPadding.bottom;
1072
- height = Math.max(height, verticalInset);
1073
- insideHorizontalInset = Math.max(insideHorizontalInset, labelPadding.left + labelPadding.right);
1074
- insideVerticalInset = Math.max(insideVerticalInset, verticalInset);
1075
- const key = `${placement.includes("V_CENTER") ? "center" : placement.includes("V_BOTTOM") ? "bottom" : "top"}:${placement.includes("H_CENTER") ? "center" : placement.includes("H_RIGHT") ? "right" : "left"}`;
1076
- const cell = insideLabelCells.get(key) ?? {
1077
- width: 0,
1078
- height: 0
1079
- };
1080
- cell.width = Math.max(cell.width, labelWidth);
1081
- cell.height += labelHeight;
1082
- insideLabelCells.set(key, cell);
1083
- }
1084
- }
1085
- if (insideLabelCells.size > 0) {
1086
- const rows = [
1087
- "top",
1088
- "center",
1089
- "bottom"
1090
- ];
1091
- const columns = [
1092
- "left",
1093
- "center",
1094
- "right"
1095
- ];
1096
- const cellWidth = (row, column) => insideLabelCells.get(`${row}:${column}`)?.width ?? 0;
1097
- const forceTabular = sizeOptions.has("FORCE_TABULAR_NODE_LABELS");
1098
- const asymmetrical = sizeOptions.has("ASYMMETRICAL");
1099
- const globalColumns = columns.map((column) => Math.max(...rows.map((row) => cellWidth(row, column))));
1100
- const labelGridWidth = forceTabular ? globalColumns.reduce((sum, value) => sum + value, 0) : Math.max(...rows.map((row) => {
1101
- const left = cellWidth(row, "left");
1102
- const center = cellWidth(row, "center");
1103
- const right = cellWidth(row, "right");
1104
- return asymmetrical ? left + center + right : 2 * Math.max(left, right) + center;
1105
- }));
1106
- const labelGridHeight = rows.reduce((sum, row) => sum + Math.max(...columns.map((column) => insideLabelCells.get(`${row}:${column}`)?.height ?? 0)), 0);
1107
- width = Math.max(width, labelGridWidth + labelPadding.left + labelPadding.right);
1108
- height = Math.max(height, labelGridHeight + labelPadding.top + labelPadding.bottom);
1109
- }
1110
- }
1111
- if (constraints.includes("MINIMUM_SIZE")) {
1112
- const configuredMinimum = parseVector(getOption(node.layoutOptions ?? {}, "nodeSize.minimum"));
1113
- const minimum = {
1114
- x: configuredMinimum?.x && configuredMinimum.x > 0 ? configuredMinimum.x : sizeOptions.has("DEFAULT_MINIMUM_SIZE") ? 20 : 0,
1115
- y: configuredMinimum?.y && configuredMinimum.y > 0 ? configuredMinimum.y : sizeOptions.has("DEFAULT_MINIMUM_SIZE") ? 20 : 0
1116
- };
1117
- if (sizeOptions.has("MINIMUM_SIZE_ACCOUNTS_FOR_PADDING")) {
1118
- width = Math.max(width, minimum.x + insideHorizontalInset);
1119
- height = Math.max(height, minimum.y + insideVerticalInset);
1120
- } else {
1121
- width = Math.max(width, minimum.x);
1122
- height = Math.max(height, minimum.y);
1123
- }
1124
- }
1125
- sizeOptions.has("COMPUTE_PADDING");
1126
- node.width = width;
1127
- node.height = height;
1128
- }
1129
- }
1130
- function getOption(options, suffix) {
1131
- const exactKeys = [
1132
- suffix,
1133
- `elk.${suffix}`,
1134
- `org.eclipse.elk.${suffix}`
1135
- ];
1136
- for (const key of exactKeys) if (options[key] !== void 0) return options[key];
1137
- }
1138
- const ergonomicallyMappedLayeredSettings = new Set([
1139
- "direction",
1140
- "padding",
1141
- "spacing.node",
1142
- "spacing.layer"
1143
- ]);
1144
- function coerceLayeredOptionValue(value, type) {
1145
- if (type === "BOOLEAN") {
1146
- if (typeof value === "boolean") return value;
1147
- if (typeof value === "string") return value.toLowerCase() === "true";
1148
- }
1149
- if (type === "DOUBLE" || type === "INT") {
1150
- if (typeof value === "number") return value;
1151
- if (typeof value === "string" && value.trim() !== "") return Number(value);
1152
- }
1153
- return value;
1154
- }
1155
- function parseIndividualSpacing(value) {
1156
- if (typeof value === "object" && value !== null) return value;
1157
- if (typeof value !== "string") return value;
1158
- const result = {};
1159
- for (const entry of value.split(/;,;|;/)) {
1160
- const match = entry.match(/^\s*(?:org\.eclipse\.elk\.)?(?:layered\.)?([^:]+)\s*:\s*(-?\d+(?:\.\d+)?)\s*$/);
1161
- if (!match) continue;
1162
- const sourceName = match[1];
1163
- const definition = require_layered.elkLayeredOptionDefinitions.find((candidate) => {
1164
- const suffix = candidate.elkId.replace(/^org\.eclipse\.elk\./, "");
1165
- return candidate.name === sourceName || suffix === sourceName;
1166
- });
1167
- if (definition) result[definition.name] = Number(match[2]);
1168
- }
1169
- return result;
1170
- }
1171
- function parseMargin(value) {
1172
- if (typeof value === "object" && value !== null) return value;
1173
- if (typeof value !== "string") return value;
1174
- return parsePadding(value, 0);
1175
- }
1176
- function getElementLayeredSettings(options) {
1177
- const settings = {};
1178
- for (const definition of require_layered.elkLayeredOptionDefinitions) {
1179
- const suffix = definition.elkId.replace(/^org\.eclipse\.elk\./, "");
1180
- const value = [
1181
- definition.name,
1182
- suffix,
1183
- `elk.${suffix}`,
1184
- definition.elkId
1185
- ].map((key) => options[key]).find((candidate) => candidate !== void 0);
1186
- if (value === void 0) continue;
1187
- const vectorMatch = definition.name === "port.anchor" && typeof value === "string" ? value.match(/^\s*\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)\s*$/) : void 0;
1188
- settings[definition.name] = vectorMatch ? {
1189
- x: Number(vectorMatch[1]),
1190
- y: Number(vectorMatch[2])
1191
- } : definition.name === "spacing.individual" ? parseIndividualSpacing(value) : definition.name === "spacing.portsSurrounding" ? parseMargin(value) : coerceLayeredOptionValue(value, definition.type);
1192
- }
1193
- return settings;
1194
- }
1195
- function getLayeredSettings(options) {
1196
- const settings = getElementLayeredSettings(options);
1197
- const baseValue = settings["spacing.baseValue"];
1198
- if (baseValue !== void 0) for (const [name, defaultValue] of [
1199
- ["spacing.componentComponent", 20],
1200
- ["spacing.edgeEdge", 10],
1201
- ["spacing.edgeLabel", 2],
1202
- ["spacing.edgeNode", 10],
1203
- ["spacing.labelLabel", 0],
1204
- ["spacing.labelNode", 5],
1205
- ["spacing.labelPortHorizontal", 1],
1206
- ["spacing.labelPortVertical", 1],
1207
- ["spacing.nodeSelfLoop", 10],
1208
- ["spacing.portPort", 10],
1209
- ["spacing.edgeEdgeBetweenLayers", 10],
1210
- ["spacing.edgeNodeBetweenLayers", 10]
1211
- ]) settings[name] ??= baseValue * defaultValue / 20;
1212
- for (const name of ergonomicallyMappedLayeredSettings) delete settings[name];
1213
- return settings;
1214
- }
1215
- function getNumberOption(options, suffix) {
1216
- const value = getOption(options, suffix);
1217
- if (typeof value === "number" && Number.isFinite(value)) return value;
1218
- if (typeof value === "string" && value.trim() !== "") {
1219
- const parsed = Number(value);
1220
- if (Number.isFinite(parsed)) return parsed;
1221
- }
1222
- }
1223
- function getBooleanOption(options, suffix) {
1224
- const value = getOption(options, suffix);
1225
- if (typeof value === "boolean") return value;
1226
- if (typeof value === "string") {
1227
- if (value.toLowerCase() === "true") return true;
1228
- if (value.toLowerCase() === "false") return false;
1229
- }
1230
- }
1231
- function getDirection(options) {
1232
- const direction = String(getOption(options, "direction") ?? "RIGHT").toLowerCase();
1233
- return direction === "up" || direction === "left" || direction === "down" ? direction : "right";
1234
- }
1235
- function endpoint(value, portOwnerById) {
1236
- const id = String(value);
1237
- const ownerId = portOwnerById.get(id);
1238
- return ownerId === void 0 ? { nodeId: id } : {
1239
- nodeId: ownerId,
1240
- port: id
1241
- };
1242
- }
1243
- function isInsideSelfLoop(root, edge, expectedNodeId) {
1244
- const source = String(edge.sources?.[0] ?? edge.source);
1245
- if (source !== String(edge.targets?.[0] ?? edge.target) || expectedNodeId !== void 0 && source !== expectedNodeId) return false;
1246
- const node = root.children?.find((child) => String(child.id) === source);
1247
- return getBooleanOption(node?.layoutOptions ?? {}, "insideSelfLoops.activate") === true && getBooleanOption(edge.layoutOptions ?? {}, "insideSelfLoops.yo") === true;
1248
- }
1249
- function applyInsideSelfLoops(root, baseHeightByNodeId) {
1250
- const indexByNodeId = /* @__PURE__ */ new Map();
1251
- for (const edge of root.edges ?? []) {
1252
- if (!isInsideSelfLoop(root, edge)) continue;
1253
- const nodeId = String(edge.sources?.[0] ?? edge.source);
1254
- const node = root.children?.find((child) => String(child.id) === nodeId);
1255
- const baseHeight = baseHeightByNodeId.get(nodeId);
1256
- if (!node || baseHeight === void 0) continue;
1257
- const index = indexByNodeId.get(nodeId) ?? 0;
1258
- indexByNodeId.set(nodeId, index + 1);
1259
- const y = (node.y ?? 0) + baseHeight - 2 + index * 11;
1260
- edge.sections = [{
1261
- id: `${String(edge.id)}_s0`,
1262
- startPoint: {
1263
- x: node.x ?? 0,
1264
- y
1265
- },
1266
- endPoint: {
1267
- x: (node.x ?? 0) + (node.width ?? 0),
1268
- y
1269
- },
1270
- incomingShape: edge.sources?.[0] ?? edge.source,
1271
- outgoingShape: edge.targets?.[0] ?? edge.target
1272
- }];
1273
- }
1274
- }
1275
- function toGraph(root, globalOptions = {}) {
1276
- const children = (root.children ?? []).filter((child) => getBooleanOption(child.layoutOptions ?? {}, "noLayout") !== true);
1277
- const nodeIds = new Set(children.map((child) => String(child.id)));
1278
- const portOwnerById = /* @__PURE__ */ new Map();
1279
- for (const child of children) for (const port of child.ports ?? []) if (port.id !== void 0) portOwnerById.set(String(port.id), String(child.id));
1280
- const sourcePortIds = new Set((root.edges ?? []).flatMap((edge) => (edge.sources ?? (edge.source === void 0 ? [] : [edge.source])).map(String)));
1281
- const targetPortIds = new Set((root.edges ?? []).flatMap((edge) => (edge.targets ?? (edge.target === void 0 ? [] : [edge.target])).map(String)));
1282
- const sourcePortDegree = /* @__PURE__ */ new Map();
1283
- const targetPortDegree = /* @__PURE__ */ new Map();
1284
- const edgeModelOrderByPortId = /* @__PURE__ */ new Map();
1285
- for (const [edgeIndex, edge] of (root.edges ?? []).entries()) {
1286
- for (const source of edge.sources ?? (edge.source === void 0 ? [] : [edge.source])) {
1287
- const id = String(source);
1288
- sourcePortDegree.set(id, (sourcePortDegree.get(id) ?? 0) + 1);
1289
- if (portOwnerById.has(id) && !edgeModelOrderByPortId.has(id)) edgeModelOrderByPortId.set(id, edgeIndex);
1290
- }
1291
- for (const target of edge.targets ?? (edge.target === void 0 ? [] : [edge.target])) {
1292
- const id = String(target);
1293
- targetPortDegree.set(id, (targetPortDegree.get(id) ?? 0) + 1);
1294
- if (portOwnerById.has(id) && !edgeModelOrderByPortId.has(id)) edgeModelOrderByPortId.set(id, edgeIndex);
1295
- }
1296
- }
1297
- const orderedPorts = (child) => {
1298
- const ports = [...child.ports ?? []];
1299
- if (String(getOption(child.layoutOptions ?? {}, "portConstraints")) !== "FIXED_SIDE") return ports;
1300
- const side = (port) => String(getOption(port.layoutOptions ?? {}, "port.side"));
1301
- const sideOrder = [
1302
- "NORTH",
1303
- "EAST",
1304
- "SOUTH",
1305
- "WEST"
1306
- ];
1307
- ports.sort((left, right) => {
1308
- const leftSide = side(left);
1309
- const rightSide = side(right);
1310
- const sideDifference = sideOrder.indexOf(leftSide) - sideOrder.indexOf(rightSide);
1311
- if (sideDifference !== 0) return sideDifference;
1312
- if (String(getOption(globalOptions, "layered.portSortingStrategy")) === "PORT_DEGREE") {
1313
- if (leftSide === "EAST") return (sourcePortDegree.get(String(right.id)) ?? 0) - (sourcePortDegree.get(String(left.id)) ?? 0);
1314
- if (leftSide === "WEST") return (targetPortDegree.get(String(left.id)) ?? 0) - (targetPortDegree.get(String(right.id)) ?? 0);
1315
- }
1316
- const modelOrderStrategy = String(getOption(globalOptions, "layered.considerModelOrder.strategy") ?? "NONE");
1317
- const usePortModelOrder = getBooleanOption(globalOptions, "layered.considerModelOrder.portModelOrder") === true;
1318
- if (modelOrderStrategy !== "NONE" && !usePortModelOrder) {
1319
- const edgeOrderDifference = (edgeModelOrderByPortId.get(String(left.id)) ?? Infinity) - (edgeModelOrderByPortId.get(String(right.id)) ?? Infinity);
1320
- if (edgeOrderDifference !== 0) return edgeOrderDifference;
1321
- }
1322
- return (leftSide === "WEST" ? -1 : 1) * ((child.ports?.indexOf(left) ?? 0) - (child.ports?.indexOf(right) ?? 0));
1323
- });
1324
- return ports;
1325
- };
1326
- return (0, _statelyai_graph.createGraph)({
1327
- id: String(root.id),
1328
- nodes: children.map((child) => ({
1329
- id: String(child.id),
1330
- x: child.x,
1331
- y: child.y,
1332
- ...parsePosition(getOption(child.layoutOptions ?? {}, "position")),
1333
- width: child.width,
1334
- height: child.height,
1335
- label: child.labels?.[0]?.text,
1336
- ports: orderedPorts(child).filter((port) => getBooleanOption(port.layoutOptions ?? {}, "noLayout") !== true).map((port) => ({
1337
- name: String(port.id),
1338
- direction: sourcePortIds.has(String(port.id)) ? targetPortIds.has(String(port.id)) ? "inout" : "out" : targetPortIds.has(String(port.id)) ? "in" : "inout",
1339
- x: port.x,
1340
- y: port.y,
1341
- width: port.width,
1342
- height: port.height
1343
- }))
1344
- })),
1345
- edges: (root.edges ?? []).flatMap((edge) => {
1346
- if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true) return [];
1347
- if (isInsideSelfLoop(root, edge)) return [];
1348
- const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
1349
- const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
1350
- const labels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
1351
- const labelLabelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
1352
- const labelWidth = Math.max(0, ...labels.map((label) => label.width ?? 0));
1353
- const labelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelLabelSpacing;
1354
- return nodeIds.has(source.nodeId) && nodeIds.has(target.nodeId) ? [{
1355
- id: String(edge.id),
1356
- sourceId: source.nodeId,
1357
- targetId: target.nodeId,
1358
- sourcePort: source.port,
1359
- targetPort: target.port,
1360
- label: edge.labels?.[0]?.text,
1361
- width: labelWidth,
1362
- height: labelHeight,
1363
- points: parsePoints(getOption(edge.layoutOptions ?? {}, "bendPoints"))
1364
- }] : [];
1365
- })
1366
- });
1367
- }
1368
- function parsePoints(value) {
1369
- if (typeof value !== "string") return void 0;
1370
- const points = [...value.matchAll(/\{\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\}/g)].map((match) => ({
1371
- x: Number(match[1]),
1372
- y: Number(match[2])
1373
- }));
1374
- return points.length > 0 ? points : void 0;
1375
- }
1376
- function parsePosition(value) {
1377
- if (typeof value !== "string") return void 0;
1378
- const match = value.match(/\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/);
1379
- return match ? {
1380
- x: Number(match[1]),
1381
- y: Number(match[2])
1382
- } : void 0;
1383
- }
1384
- function toSection(edge, points) {
1385
- const startPoint = points[0];
1386
- const endPoint = points.at(-1);
1387
- if (!startPoint || !endPoint) return void 0;
1388
- return {
1389
- id: `${String(edge.id)}_s0`,
1390
- startPoint: { ...startPoint },
1391
- endPoint: { ...endPoint },
1392
- incomingShape: edge.sources?.[0] ?? edge.source,
1393
- outgoingShape: edge.targets?.[0] ?? edge.target,
1394
- ...points.length > 2 ? { bendPoints: points.slice(1, -1).map((point) => ({ ...point })) } : {}
1395
- };
1396
- }
1397
- function applyLayout(root, graph, padding, layoutOptions, resultPolicy) {
1398
- const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
1399
- const edgeById = new Map(graph.edges.map((edge) => [edge.id, edge]));
1400
- for (const child of root.children ?? []) {
1401
- const node = nodeById.get(String(child.id));
1402
- if (!node) continue;
1403
- child.x = node.x;
1404
- child.y = node.y;
1405
- child.width = node.width;
1406
- child.height = node.height;
1407
- placeNodeLabels(child, layoutOptions);
1408
- for (const port of child.ports ?? []) {
1409
- if (getBooleanOption(port.layoutOptions ?? {}, "noLayout") === true) continue;
1410
- const laidOutPort = node.ports?.find((candidate) => candidate.name === String(port.id));
1411
- if (!laidOutPort) continue;
1412
- port.x = laidOutPort.x;
1413
- port.y = laidOutPort.y;
1414
- port.width = laidOutPort.width;
1415
- port.height = laidOutPort.height;
1416
- }
1417
- placePortLabels(child, layoutOptions);
1418
- }
1419
- for (const edge of root.edges ?? []) {
1420
- if (resultPolicy?.preserveEdgeSections) {
1421
- for (const section$1 of edge.sections ?? []) {
1422
- section$1.incomingShape ??= edge.sources?.[0] ?? edge.source;
1423
- section$1.outgoingShape ??= edge.targets?.[0] ?? edge.target;
1424
- }
1425
- continue;
1426
- }
1427
- const laidOutEdge = edgeById.get(String(edge.id));
1428
- if (!laidOutEdge) {
1429
- if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true) {
1430
- for (const section$2 of edge.sections ?? []) {
1431
- section$2.incomingShape ??= edge.sources?.[0] ?? edge.source;
1432
- section$2.outgoingShape ??= edge.targets?.[0] ?? edge.target;
1433
- }
1434
- continue;
1435
- }
1436
- const section$1 = getParentEdgeSection(root, edge);
1437
- if (section$1) edge.sections = [section$1];
1438
- continue;
1439
- }
1440
- const section = toSection(edge, laidOutEdge.points ?? []);
1441
- edge.sections = section ? [section] : [];
1442
- const target = root.children?.find((child) => String(child.id) === String(edge.targets?.[0] ?? edge.target));
1443
- if (getBooleanOption(target?.layoutOptions ?? {}, "hypernode") === true && (section?.bendPoints?.length ?? 0) > 0) edge.junctionPoints = [section.bendPoints.at(-1)];
1444
- let labelY = laidOutEdge.y ?? 0;
1445
- const points = laidOutEdge.points ?? [];
1446
- const firstPoint = points[0] ?? {
1447
- x: laidOutEdge.x ?? 0,
1448
- y: laidOutEdge.y ?? 0
1449
- };
1450
- const lastPoint = points.at(-1) ?? firstPoint;
1451
- const midpointX = (laidOutEdge.x ?? 0) + (laidOutEdge.width ?? 0) / 2;
1452
- const edgeLabelSpacing = getNumberOption(layoutOptions, "spacing.edgeLabel") ?? 2;
1453
- const labelLabelSpacing = getNumberOption(layoutOptions, "spacing.labelLabel") ?? 0;
1454
- for (const label of edge.labels ?? []) {
1455
- if (getBooleanOption(label.layoutOptions ?? {}, "noLayout") === true) {
1456
- label.x ??= 0;
1457
- label.y ??= 0;
1458
- continue;
1459
- }
1460
- if (!isLayoutEdgeLabel(label)) {
1461
- label.x ??= 0;
1462
- label.y ??= 0;
1463
- continue;
1464
- }
1465
- const placement = String(getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER");
1466
- const width = label.width ?? 0;
1467
- label.x = placement === "TAIL" ? firstPoint.x + edgeLabelSpacing : placement === "HEAD" ? lastPoint.x - width - edgeLabelSpacing : midpointX - width / 2;
1468
- label.y = labelY;
1469
- labelY += (label.height ?? 0) + labelLabelSpacing;
1470
- }
1471
- }
1472
- if (!resultPolicy?.skipBoundsNormalization) normalizeElkGraphBounds(root, padding, layoutOptions);
1473
- }
1474
- function normalizeElkGraphBounds(root, padding, layoutOptions = {}) {
1475
- const authoredWidth = root.width;
1476
- const authoredHeight = root.height;
1477
- const fixedGraphSize = getBooleanOption(layoutOptions, "nodeSize.fixedGraphSize") === true;
1478
- const layoutChildren = (root.children ?? []).filter((node) => getBooleanOption(node.layoutOptions ?? {}, "noLayout") !== true);
1479
- let minimumX = Number.POSITIVE_INFINITY;
1480
- let minimumY = Number.POSITIVE_INFINITY;
1481
- for (const node of layoutChildren) {
1482
- minimumX = Math.min(minimumX, node.x ?? 0, ...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0)), ...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0)), ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0))));
1483
- minimumY = Math.min(minimumY, node.y ?? 0, ...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0)), ...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0)), ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0))));
1484
- }
1485
- for (const edge of root.edges ?? []) {
1486
- const laidOutLabels = (edge.labels ?? []).filter(isLayoutEdgeLabel);
1487
- minimumX = Math.min(minimumX, ...laidOutLabels.map((label) => label.x ?? 0));
1488
- minimumY = Math.min(minimumY, ...laidOutLabels.map((label) => label.y ?? 0));
1489
- if (getBooleanOption(edge.layoutOptions ?? {}, "noLayout") !== true) {
1490
- const points = (edge.sections ?? []).flatMap((section) => [
1491
- section.startPoint,
1492
- ...section.bendPoints ?? [],
1493
- section.endPoint
1494
- ]);
1495
- minimumX = Math.min(minimumX, ...points.map((point) => point.x));
1496
- minimumY = Math.min(minimumY, ...points.map((point) => point.y));
1497
- }
1498
- }
1499
- const shiftX = Number.isFinite(minimumX) ? Math.max(0, padding.left - minimumX) : 0;
1500
- const shiftY = Number.isFinite(minimumY) ? Math.max(0, padding.top - minimumY) : 0;
1501
- if (shiftX !== 0 || shiftY !== 0) {
1502
- for (const node of layoutChildren) {
1503
- node.x = (node.x ?? 0) + shiftX;
1504
- node.y = (node.y ?? 0) + shiftY;
1505
- }
1506
- for (const edge of root.edges ?? []) {
1507
- for (const section of edge.sections ?? []) for (const point of [
1508
- section.startPoint,
1509
- ...section.bendPoints ?? [],
1510
- section.endPoint
1511
- ]) {
1512
- point.x += shiftX;
1513
- point.y += shiftY;
1514
- }
1515
- for (const label of (edge.labels ?? []).filter(isLayoutEdgeLabel)) {
1516
- label.x = (label.x ?? 0) + shiftX;
1517
- label.y = (label.y ?? 0) + shiftY;
1518
- }
1519
- }
1520
- }
1521
- const maximumNodeX = Math.max(0, ...layoutChildren.map((node) => (node.x ?? 0) + (node.width ?? 0)));
1522
- const maximumNodeY = Math.max(0, ...layoutChildren.map((node) => (node.y ?? 0) + (node.height ?? 0)));
1523
- const layoutEdgePoints = (root.edges ?? []).flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
1524
- section.startPoint,
1525
- ...section.bendPoints ?? [],
1526
- section.endPoint
1527
- ]));
1528
- const wrappingStrategy = String(getOption(layoutOptions, "layered.wrapping.strategy") ?? "OFF");
1529
- const direction = getDirection(layoutOptions);
1530
- const laidOutChildById = new Map((root.children ?? []).map((child) => [String(child.id), child]));
1531
- const childByEndpointId = new Map(laidOutChildById);
1532
- for (const child of root.children ?? []) for (const port of child.ports ?? []) childByEndpointId.set(String(port.id), child);
1533
- const wrappedEdgeCount = (root.edges ?? []).filter((edge) => {
1534
- const source = childByEndpointId.get(String(edge.sources?.[0] ?? edge.source));
1535
- const target = childByEndpointId.get(String(edge.targets?.[0] ?? edge.target));
1536
- if (!source || !target) return false;
1537
- return direction === "right" ? (source.x ?? 0) > (target.x ?? 0) : direction === "left" ? (source.x ?? 0) < (target.x ?? 0) : direction === "down" ? (source.y ?? 0) > (target.y ?? 0) : (source.y ?? 0) < (target.y ?? 0);
1538
- }).length;
1539
- const hasWrappedEdge = wrappedEdgeCount > 0;
1540
- const addBoundaryPixel = wrappingStrategy !== "MULTI_EDGE" && !(wrappingStrategy === "SINGLE_EDGE" && hasWrappedEdge) && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") === "NONE" && getBooleanOption(layoutOptions, "layered.feedbackEdges") !== true && String(getOption(layoutOptions, "layered.layering.nodePromotion.strategy") ?? "NONE") !== "MODEL_ORDER_LEFT_TO_RIGHT" && !(root.edges ?? []).some((edge) => edge.sources?.[0] === edge.targets?.[0]) && !(root.children ?? []).some((child) => (child.children?.length ?? 0) > 0);
1541
- const edgeBoundsExtraX = addBoundaryPixel && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.x)) >= maximumNodeX - 1e-9 ? 1 : 0;
1542
- const edgeBoundsExtraY = addBoundaryPixel && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) >= maximumNodeY - 1e-9 ? 1 : 0;
1543
- const singleMultiEdgeCutBoundsExtraY = wrappingStrategy === "MULTI_EDGE" && wrappedEdgeCount === 1 && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) >= maximumNodeY - 1e-9 ? 1 : 0;
1544
- const postCompactionBoundsExtraX = !addBoundaryPixel && !hasWrappedEdge && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") !== "NONE" && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.x)) > maximumNodeX + 1e-9 ? .04 : 0;
1545
- const postCompactionBoundsExtraY = !addBoundaryPixel && !hasWrappedEdge && String(getOption(layoutOptions, "layered.compaction.postCompaction.strategy") ?? "NONE") !== "NONE" && layoutEdgePoints.length > 0 && Math.max(...layoutEdgePoints.map((point) => point.y)) > maximumNodeY + 1e-9 ? .04 : 0;
1546
- const calculatedWidth = Math.max(0, ...layoutChildren.flatMap((node) => [
1547
- (node.x ?? 0) + (node.width ?? 0),
1548
- ...(node.labels ?? []).map((label) => (node.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)),
1549
- ...(node.ports ?? []).map((port) => (node.x ?? 0) + (port.x ?? 0) + (port.width ?? 0)),
1550
- ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.x ?? 0) + (port.x ?? 0) + (label.x ?? 0) + (label.width ?? 0)))
1551
- ]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.x ?? 0) + (label.width ?? 0))), ...(root.edges ?? []).flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
1552
- section.startPoint.x,
1553
- ...(section.bendPoints ?? []).map((point) => point.x),
1554
- section.endPoint.x
1555
- ]))) + padding.right + edgeBoundsExtraX + postCompactionBoundsExtraX + (getBooleanOption(layoutOptions, "layered.feedbackEdges") === true && (getDirection(layoutOptions) === "down" || getDirection(layoutOptions) === "up") ? 1 : 0);
1556
- const calculatedHeight = Math.max(0, ...layoutChildren.flatMap((node) => [
1557
- (node.y ?? 0) + (node.height ?? 0),
1558
- ...(node.labels ?? []).map((label) => (node.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)),
1559
- ...(node.ports ?? []).map((port) => (node.y ?? 0) + (port.y ?? 0) + (port.height ?? 0)),
1560
- ...(node.ports ?? []).flatMap((port) => (port.labels ?? []).map((label) => (node.y ?? 0) + (port.y ?? 0) + (label.y ?? 0) + (label.height ?? 0)))
1561
- ]), ...(root.edges ?? []).flatMap((edge) => (edge.labels ?? []).filter(isLayoutEdgeLabel).map((label) => (label.y ?? 0) + (label.height ?? 0) + (String(getOption(label.layoutOptions ?? {}, "edgeLabels.placement") ?? "CENTER") === "CENTER" ? 1 : 0))), ...(root.edges ?? []).flatMap((edge) => getBooleanOption(edge.layoutOptions ?? {}, "noLayout") === true ? [] : (edge.sections ?? []).flatMap((section) => [
1562
- section.startPoint.y,
1563
- ...(section.bendPoints ?? []).map((point) => point.y),
1564
- section.endPoint.y
1565
- ]))) + padding.bottom + edgeBoundsExtraY + singleMultiEdgeCutBoundsExtraY + postCompactionBoundsExtraY + (getBooleanOption(layoutOptions, "layered.feedbackEdges") === true && (getDirection(layoutOptions) === "right" || getDirection(layoutOptions) === "left") ? 1 : 0);
1566
- root.width = fixedGraphSize ? authoredWidth ?? 0 : calculatedWidth;
1567
- root.height = fixedGraphSize ? authoredHeight ?? 0 : calculatedHeight;
1568
- }
1569
- function getParentEdgeSection(root, edge) {
1570
- const sourceId = String(edge.sources?.[0] ?? edge.source);
1571
- const targetId = String(edge.targets?.[0] ?? edge.target);
1572
- const rootId = String(root.id);
1573
- const source = root.children?.find((child) => String(child.id) === sourceId);
1574
- const target = root.children?.find((child) => String(child.id) === targetId);
1575
- if (source && targetId === rootId) {
1576
- const startPoint = {
1577
- x: (source.x ?? 0) + (source.width ?? 0) / 2,
1578
- y: (source.y ?? 0) + (source.height ?? 0)
1579
- };
1580
- return {
1581
- id: `${String(edge.id)}_s0`,
1582
- startPoint,
1583
- endPoint: {
1584
- x: startPoint.x,
1585
- y: 0
1586
- }
1587
- };
1588
- }
1589
- if (sourceId === rootId && target) {
1590
- const endPoint = {
1591
- x: (target.x ?? 0) + (target.width ?? 0) / 2,
1592
- y: target.y ?? 0
1593
- };
1594
- return {
1595
- id: `${String(edge.id)}_s0`,
1596
- startPoint: {
1597
- x: endPoint.x,
1598
- y: 0
1599
- },
1600
- endPoint
1601
- };
1602
- }
1603
- }
1604
- function placeNodeLabels(node, globalOptions) {
1605
- const labelPadding = parsePadding(getOption(globalOptions, "nodeLabels.padding"), 5);
1606
- for (const label of node.labels ?? []) {
1607
- if (!label.text) continue;
1608
- if (getBooleanOption(label.layoutOptions ?? {}, "noLayout") === true) continue;
1609
- const placement = String(getOption({
1610
- ...globalOptions,
1611
- ...node.layoutOptions,
1612
- ...label.layoutOptions
1613
- }, "nodeLabels.placement") ?? "");
1614
- if (!placement) continue;
1615
- const width = label.width ?? 0;
1616
- const height = label.height ?? 0;
1617
- const nodeWidth = node.width ?? 0;
1618
- const nodeHeight = node.height ?? 0;
1619
- label.x = placement.includes("H_CENTER") ? (nodeWidth - width + labelPadding.left - labelPadding.right) / 2 : placement.includes("H_RIGHT") ? nodeWidth - width - labelPadding.right : labelPadding.left;
1620
- if (placement.includes("OUTSIDE") && placement.includes("V_TOP")) label.y = -height - Number(getOption(globalOptions, "spacing.labelNode") ?? 5);
1621
- else if (placement.includes("OUTSIDE") && placement.includes("V_BOTTOM")) label.y = nodeHeight + Number(getOption(globalOptions, "spacing.labelNode") ?? 5);
1622
- else if (placement.includes("V_CENTER")) label.y = (nodeHeight - height + labelPadding.top - labelPadding.bottom) / 2;
1623
- else if (placement.includes("V_BOTTOM")) label.y = nodeHeight - height - labelPadding.bottom;
1624
- else label.y = labelPadding.top;
1625
- }
1626
- }
1627
- function placePortLabels(node, globalOptions) {
1628
- const placement = String(getOption({
1629
- ...globalOptions,
1630
- ...node.layoutOptions
1631
- }, "portLabels.placement") ?? "OUTSIDE");
1632
- const inside = placement.includes("INSIDE");
1633
- const alwaysOtherSide = placement.includes("ALWAYS_OTHER_SAME_SIDE");
1634
- const spaceEfficient = placement.includes("SPACE_EFFICIENT") || String(getOption(node.layoutOptions ?? {}, "nodeSize.options") ?? "").includes("SPACE_EFFICIENT_PORT_LABELS");
1635
- const horizontalSpacing = getNumberOption(globalOptions, "spacing.labelPortHorizontal") ?? 1;
1636
- const verticalSpacing = getNumberOption(globalOptions, "spacing.labelPortVertical") ?? 1;
1637
- const labelSpacing = getNumberOption(globalOptions, "spacing.labelLabel") ?? 0;
1638
- const treatAsGroup = getBooleanOption(node.layoutOptions ?? {}, "portLabels.treatAsGroup") ?? false;
1639
- const placeNextToPort = placement.includes("NEXT_TO_PORT_IF_POSSIBLE") || getBooleanOption(node.layoutOptions ?? {}, "portLabels.nextToPortIfPossible") === true;
1640
- const nodeWidth = node.width ?? 0;
1641
- for (const port of node.ports ?? []) {
1642
- if (getBooleanOption(port.layoutOptions ?? {}, "noLayout") === true) continue;
1643
- const portWidth = port.width ?? 0;
1644
- const portHeight = port.height ?? 0;
1645
- const side = (port.x ?? 0) < 0 ? "WEST" : (port.x ?? 0) >= nodeWidth ? "EAST" : (port.y ?? 0) < 0 ? "NORTH" : "SOUTH";
1646
- const portsOnSide = (node.ports ?? []).filter((candidate) => {
1647
- return ((candidate.x ?? 0) < 0 ? "WEST" : (candidate.x ?? 0) >= nodeWidth ? "EAST" : (candidate.y ?? 0) < 0 ? "NORTH" : "SOUTH") === side;
1648
- });
1649
- const labels = (port.labels ?? []).filter((label) => Boolean(label.text) && getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true);
1650
- const totalLabelHeight = labels.reduce((sum, label) => sum + (label.height ?? 0), 0) + Math.max(0, labels.length - 1) * labelSpacing;
1651
- let stackedY = labels.length > 1 ? inside || placeNextToPort ? treatAsGroup ? (portHeight - totalLabelHeight) / 2 : (portHeight - (labels[0]?.height ?? 0)) / 2 : portHeight + verticalSpacing : void 0;
1652
- for (const label of labels) {
1653
- if (!label.text) continue;
1654
- const width = label.width ?? 0;
1655
- const height = label.height ?? 0;
1656
- if (side === "EAST") {
1657
- label.x = inside ? -width - horizontalSpacing : portWidth + horizontalSpacing;
1658
- label.y = stackedY ?? (inside || placeNextToPort ? (portHeight - height) / 2 : alwaysOtherSide ? -height - verticalSpacing : portHeight + verticalSpacing);
1659
- } else if (side === "WEST") {
1660
- label.x = inside ? portWidth + horizontalSpacing : -width - horizontalSpacing;
1661
- label.y = stackedY ?? (inside || placeNextToPort ? (portHeight - height) / 2 : alwaysOtherSide ? -height - verticalSpacing : portHeight + verticalSpacing);
1662
- } else if (side === "NORTH") {
1663
- label.x = inside || placeNextToPort ? (portWidth - width) / 2 : alwaysOtherSide ? -width - horizontalSpacing : portWidth + horizontalSpacing;
1664
- label.y = inside ? portHeight + verticalSpacing : -height - verticalSpacing;
1665
- } else {
1666
- label.x = inside || placeNextToPort ? (portWidth - width) / 2 : alwaysOtherSide ? -width - horizontalSpacing : portWidth + horizontalSpacing;
1667
- label.y = inside ? -height - verticalSpacing : portHeight + verticalSpacing;
1668
- }
1669
- if (spaceEfficient && portsOnSide[0] === port && portsOnSide.length >= 2 && !placeNextToPort) if (side === "EAST" || side === "WEST") label.y = -height - verticalSpacing;
1670
- else label.x = -width - horizontalSpacing;
1671
- if (stackedY !== void 0) stackedY += height + labelSpacing;
1672
- }
1673
- }
1674
- }
1675
-
1676
- //#endregion
1677
- Object.defineProperty(exports, 'ELK', {
1678
- enumerable: true,
1679
- get: function () {
1680
- return ELK;
1681
- }
1682
- });
1683
- Object.defineProperty(exports, 'compileStatechartLayout', {
1684
- enumerable: true,
1685
- get: function () {
1686
- return compileStatechartLayout;
1687
- }
1688
- });
1689
- Object.defineProperty(exports, 'getElkRoutes', {
1690
- enumerable: true,
1691
- get: function () {
1692
- return getElkRoutes;
1693
- }
1694
- });
1695
- Object.defineProperty(exports, 'layoutStatechart', {
1696
- enumerable: true,
1697
- get: function () {
1698
- return layoutStatechart;
1699
- }
1700
- });
1701
- Object.defineProperty(exports, 'scoreStatechartLayout', {
1702
- enumerable: true,
1703
- get: function () {
1704
- return scoreStatechartLayout;
1705
- }
1706
- });