@statelyai/layout 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +43 -0
- package/README.md +82 -4
- package/dist/elkjs/index.cjs +8 -0
- package/dist/elkjs/index.d.cts +146 -0
- package/dist/elkjs/index.d.mts +42 -39
- package/dist/elkjs/index.mjs +2 -1483
- package/dist/elkjs/main.cjs +17 -0
- package/dist/elkjs/main.d.cts +10 -0
- package/dist/elkjs/main.d.mts +11 -0
- package/dist/elkjs/main.mjs +17 -0
- package/dist/elkjs/worker-api.cjs +3 -0
- package/dist/elkjs/worker-api.d.cts +3 -0
- package/dist/elkjs/worker-api.d.mts +3 -0
- package/dist/elkjs/worker-api.mjs +3 -0
- package/dist/elkjs/worker.cjs +86 -0
- package/dist/elkjs/worker.d.cts +17 -0
- package/dist/elkjs/worker.d.mts +17 -0
- package/dist/elkjs/worker.mjs +86 -0
- package/dist/elkjs-BVnDwnBQ.cjs +1551 -0
- package/dist/elkjs-CFlfkEBz.mjs +1528 -0
- package/dist/{index-8xYkohbz.d.mts → index-BSH2bloA.d.mts} +77 -3
- package/dist/index-CG712jzV.d.cts +1286 -0
- package/dist/index.cjs +188 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +24 -25
- package/dist/layered/index.cjs +38 -0
- package/dist/layered/index.d.cts +2 -0
- package/dist/layered/index.d.mts +1 -1
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-COIyXvwb.mjs → layered-Bz2nWmVp.mjs} +2298 -45
- package/dist/layered-Cf9Rn8l7.cjs +12061 -0
- package/dist/layout-engine-BNex9jxg.cjs +712 -0
- package/dist/{spore-BeKUA1Mb.mjs → layout-engine-WbC0YjjT.mjs} +159 -5
- package/dist/public-types-B8rAALJq.d.mts +109 -0
- package/dist/public-types-CVB0hXGk.d.cts +109 -0
- package/dist/worker-client-D3HMK33r.cjs +98 -0
- package/dist/worker-client-DAT_z2OR.d.mts +19 -0
- package/dist/worker-client-DVhklF6A.d.cts +19 -0
- package/dist/worker-client-DkwS4sHx.mjs +92 -0
- package/package.json +39 -9
- package/src/authoring/coordinates.ts +106 -0
- package/src/authoring/partial.ts +584 -0
- package/src/authoring/routing.ts +211 -0
- package/src/box.ts +22 -0
- package/src/constraints.ts +222 -0
- package/src/elkjs/index.ts +100 -37
- package/src/elkjs/main.ts +19 -0
- package/src/elkjs/public-types.ts +125 -0
- package/src/elkjs/worker-api.ts +29 -0
- package/src/elkjs/worker-client.ts +138 -0
- package/src/elkjs/worker.ts +117 -0
- package/src/index.ts +13 -0
- package/src/internal/elkjs-compatibility.ts +17 -0
- package/src/internal/layout-engine.ts +126 -0
- package/src/layered/index.ts +31 -3
- package/src/layered/strategies.ts +68 -3
- package/src/layout.ts +35 -28
- package/src/packing.ts +22 -0
- package/src/random.ts +75 -3
- package/src/types.ts +17 -2
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
const require_layered = require('./layered-Cf9Rn8l7.cjs');
|
|
2
|
+
let _statelyai_graph_layout = require("@statelyai/graph/layout");
|
|
3
|
+
|
|
4
|
+
//#region src/fixed.ts
|
|
5
|
+
function defaultRoute(source, target) {
|
|
6
|
+
return [{
|
|
7
|
+
x: source.x + source.width / 2,
|
|
8
|
+
y: source.y + source.height / 2
|
|
9
|
+
}, {
|
|
10
|
+
x: target.x + target.width / 2,
|
|
11
|
+
y: target.y + target.height / 2
|
|
12
|
+
}];
|
|
13
|
+
}
|
|
14
|
+
/** Preserve authored positions and routes while completing visual geometry. */
|
|
15
|
+
function getFixedLayout(graph, options = {}) {
|
|
16
|
+
const direction = options.direction ?? graph.direction ?? "down";
|
|
17
|
+
const nodes = graph.nodes.map((node) => {
|
|
18
|
+
const size = (0, _statelyai_graph_layout.getNodeSize)(node, options);
|
|
19
|
+
const rect = {
|
|
20
|
+
x: node.x ?? 0,
|
|
21
|
+
y: node.y ?? 0,
|
|
22
|
+
...size
|
|
23
|
+
};
|
|
24
|
+
const ports = require_layered.placePorts(node.ports, rect, direction);
|
|
25
|
+
return {
|
|
26
|
+
...node,
|
|
27
|
+
...rect,
|
|
28
|
+
...ports === void 0 ? {} : { ports }
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
32
|
+
const edges = graph.edges.map((edge) => {
|
|
33
|
+
const source = nodeById.get(edge.sourceId);
|
|
34
|
+
const target = nodeById.get(edge.targetId);
|
|
35
|
+
const points = edge.points ? [...edge.points] : source && target ? defaultRoute(source, target) : [];
|
|
36
|
+
const midpoint = require_layered.getPolylineMidpoint(points);
|
|
37
|
+
const width = edge.width ?? 0;
|
|
38
|
+
const height = edge.height ?? 0;
|
|
39
|
+
return {
|
|
40
|
+
...edge,
|
|
41
|
+
x: edge.x ?? midpoint.x - width / 2,
|
|
42
|
+
y: edge.y ?? midpoint.y - height / 2,
|
|
43
|
+
width,
|
|
44
|
+
height,
|
|
45
|
+
points,
|
|
46
|
+
routing: edge.routing ?? "polyline"
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
...graph,
|
|
51
|
+
direction,
|
|
52
|
+
nodes,
|
|
53
|
+
edges
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const fixedAlgorithm = {
|
|
57
|
+
id: "fixed",
|
|
58
|
+
capabilities: {
|
|
59
|
+
full: true,
|
|
60
|
+
incremental: false,
|
|
61
|
+
partial: false,
|
|
62
|
+
routeOnly: false,
|
|
63
|
+
hierarchy: true,
|
|
64
|
+
ports: true
|
|
65
|
+
},
|
|
66
|
+
layout(graph, options) {
|
|
67
|
+
return getFixedLayout(graph, options ?? {});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/internal/elkjs-compatibility.ts
|
|
73
|
+
const elkjs0111ResultPolicy = Symbol("elkjs0111ResultPolicy");
|
|
74
|
+
function setElkjs0111ResultPolicy(graph, policy) {
|
|
75
|
+
Object.defineProperty(graph, elkjs0111ResultPolicy, { value: policy });
|
|
76
|
+
return graph;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//#endregion
|
|
80
|
+
//#region src/box.ts
|
|
81
|
+
function getPadding$3(value) {
|
|
82
|
+
if (typeof value === "number") return {
|
|
83
|
+
top: value,
|
|
84
|
+
right: value,
|
|
85
|
+
bottom: value,
|
|
86
|
+
left: value
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
top: value?.top ?? 15,
|
|
90
|
+
right: value?.right ?? 15,
|
|
91
|
+
bottom: value?.bottom ?? 15,
|
|
92
|
+
left: value?.left ?? 15
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function standardDeviation(values, mean) {
|
|
96
|
+
if (values.length < 2) return 0;
|
|
97
|
+
const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0);
|
|
98
|
+
return Math.sqrt(variance / (values.length - 1));
|
|
99
|
+
}
|
|
100
|
+
/** ELK Box SIMPLE packing translated onto `@statelyai/graph`. */
|
|
101
|
+
function getBoxLayout(graph, options = {}) {
|
|
102
|
+
const spacing = Math.fround(options.spacing ?? 15);
|
|
103
|
+
const padding = getPadding$3(options.padding);
|
|
104
|
+
const aspectRatio = (options.aspectRatio ?? 1.3) > 0 ? options.aspectRatio ?? 1.3 : 1.3;
|
|
105
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, (0, _statelyai_graph_layout.getNodeSize)(node, options)]));
|
|
106
|
+
const sortedNodes = [...graph.nodes].sort((first, second) => {
|
|
107
|
+
const priorityDifference = (options.priority?.(second) ?? 0) - (options.priority?.(first) ?? 0);
|
|
108
|
+
if (priorityDifference !== 0) return priorityDifference;
|
|
109
|
+
if (options.interactive) {
|
|
110
|
+
const yDifference = (first.y ?? 0) - (second.y ?? 0);
|
|
111
|
+
if (yDifference !== 0) return yDifference;
|
|
112
|
+
const xDifference = (first.x ?? 0) - (second.x ?? 0);
|
|
113
|
+
if (xDifference !== 0) return xDifference;
|
|
114
|
+
}
|
|
115
|
+
const firstSize = sizes.get(first.id) ?? {
|
|
116
|
+
width: 0,
|
|
117
|
+
height: 0
|
|
118
|
+
};
|
|
119
|
+
const secondSize = sizes.get(second.id) ?? {
|
|
120
|
+
width: 0,
|
|
121
|
+
height: 0
|
|
122
|
+
};
|
|
123
|
+
return firstSize.width * firstSize.height - secondSize.width * secondSize.height;
|
|
124
|
+
});
|
|
125
|
+
const areas = sortedNodes.map((node) => {
|
|
126
|
+
const size = sizes.get(node.id) ?? {
|
|
127
|
+
width: 0,
|
|
128
|
+
height: 0
|
|
129
|
+
};
|
|
130
|
+
return size.width * size.height;
|
|
131
|
+
});
|
|
132
|
+
let totalArea = areas.reduce((sum, area) => sum + area, 0);
|
|
133
|
+
const mean = areas.length === 0 ? 0 : totalArea / areas.length;
|
|
134
|
+
totalArea += areas.length * standardDeviation(areas, mean);
|
|
135
|
+
totalArea += Math.sqrt(totalArea) * (padding.bottom + padding.top);
|
|
136
|
+
totalArea += Math.sqrt(totalArea) * padding.right;
|
|
137
|
+
const widestNode = Math.max(0, ...sortedNodes.map((node) => sizes.get(node.id)?.width ?? 0));
|
|
138
|
+
const maximumRowWidth = Math.max(widestNode, Math.sqrt(totalArea * aspectRatio)) + padding.left;
|
|
139
|
+
let x = padding.left;
|
|
140
|
+
let y = padding.top;
|
|
141
|
+
let rowHeight = 0;
|
|
142
|
+
let rowStart = 0;
|
|
143
|
+
const rows = [];
|
|
144
|
+
const nodes = [];
|
|
145
|
+
for (const node of sortedNodes) {
|
|
146
|
+
const size = sizes.get(node.id) ?? {
|
|
147
|
+
width: 0,
|
|
148
|
+
height: 0
|
|
149
|
+
};
|
|
150
|
+
if (x + size.width > maximumRowWidth) {
|
|
151
|
+
rows.push({
|
|
152
|
+
start: rowStart,
|
|
153
|
+
end: nodes.length,
|
|
154
|
+
height: rowHeight
|
|
155
|
+
});
|
|
156
|
+
rowStart = nodes.length;
|
|
157
|
+
x = padding.left;
|
|
158
|
+
y += rowHeight + spacing;
|
|
159
|
+
rowHeight = 0;
|
|
160
|
+
}
|
|
161
|
+
nodes.push({
|
|
162
|
+
...node,
|
|
163
|
+
x,
|
|
164
|
+
y,
|
|
165
|
+
...size
|
|
166
|
+
});
|
|
167
|
+
x += size.width + spacing;
|
|
168
|
+
rowHeight = Math.max(rowHeight, size.height);
|
|
169
|
+
}
|
|
170
|
+
rows.push({
|
|
171
|
+
start: rowStart,
|
|
172
|
+
end: nodes.length,
|
|
173
|
+
height: rowHeight
|
|
174
|
+
});
|
|
175
|
+
if (options.expandNodes) {
|
|
176
|
+
const broadestRow = Math.max(padding.left + padding.right, ...nodes.map((node) => node.x + node.width + padding.right));
|
|
177
|
+
for (const row of rows) for (let index = row.start; index < row.end; index++) {
|
|
178
|
+
const node = nodes[index];
|
|
179
|
+
if (!node) continue;
|
|
180
|
+
node.height = row.height;
|
|
181
|
+
if (index === row.end - 1) node.width = broadestRow - node.x - padding.right;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const placedById = new Map(nodes.map((node) => [node.id, node]));
|
|
185
|
+
return getFixedLayout({
|
|
186
|
+
...graph,
|
|
187
|
+
nodes: graph.nodes.map((node) => placedById.get(node.id) ?? node)
|
|
188
|
+
}, { direction: options.direction ?? graph.direction });
|
|
189
|
+
}
|
|
190
|
+
const boxAlgorithm = {
|
|
191
|
+
id: "box",
|
|
192
|
+
capabilities: {
|
|
193
|
+
full: true,
|
|
194
|
+
incremental: false,
|
|
195
|
+
partial: false,
|
|
196
|
+
routeOnly: false,
|
|
197
|
+
hierarchy: false,
|
|
198
|
+
ports: true
|
|
199
|
+
},
|
|
200
|
+
layout(graph, options) {
|
|
201
|
+
return getBoxLayout(graph, options ?? {});
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
/** Pinned behavior used only by the elkjs 0.11.1 compatibility adapter. */
|
|
205
|
+
const elkjs0111BoxAlgorithm = {
|
|
206
|
+
...boxAlgorithm,
|
|
207
|
+
layout(graph, options = {}) {
|
|
208
|
+
const result = getBoxLayout(graph, options);
|
|
209
|
+
const padding = getPadding$3(options.padding);
|
|
210
|
+
return setElkjs0111ResultPolicy(result, {
|
|
211
|
+
providerBounds: result.nodes.length === 0 ? {
|
|
212
|
+
width: 0,
|
|
213
|
+
height: 0
|
|
214
|
+
} : {
|
|
215
|
+
width: Math.max(...result.nodes.map((node) => node.x + node.width)) + padding.right,
|
|
216
|
+
height: Math.max(...result.nodes.map((node) => node.y + node.height)) + padding.bottom
|
|
217
|
+
},
|
|
218
|
+
preserveEdgeSections: true,
|
|
219
|
+
skipBoundsNormalization: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/packing.ts
|
|
226
|
+
function getPadding$2(value) {
|
|
227
|
+
if (typeof value === "number") return {
|
|
228
|
+
top: value,
|
|
229
|
+
right: value,
|
|
230
|
+
bottom: value,
|
|
231
|
+
left: value
|
|
232
|
+
};
|
|
233
|
+
return {
|
|
234
|
+
top: value?.top ?? 0,
|
|
235
|
+
right: value?.right ?? 0,
|
|
236
|
+
bottom: value?.bottom ?? 0,
|
|
237
|
+
left: value?.left ?? 0
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** Deterministic shelf-based rectangle packing for `@statelyai/graph`. */
|
|
241
|
+
function getRectanglePackingLayout(graph, options = {}) {
|
|
242
|
+
const spacing = options.spacing ?? 20;
|
|
243
|
+
const padding = getPadding$2(options.padding);
|
|
244
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, (0, _statelyai_graph_layout.getNodeSize)(node, options)]));
|
|
245
|
+
const totalArea = [...sizes.values()].reduce((area, size) => area + (size.width + spacing) * (size.height + spacing), 0);
|
|
246
|
+
const targetWidth = options.targetWidth ?? Math.max(1, Math.sqrt(totalArea) * 1.5);
|
|
247
|
+
let x = padding.left;
|
|
248
|
+
let y = padding.top;
|
|
249
|
+
let rowHeight = 0;
|
|
250
|
+
const nodes = [];
|
|
251
|
+
for (const node of graph.nodes) {
|
|
252
|
+
const size = sizes.get(node.id) ?? {
|
|
253
|
+
width: 0,
|
|
254
|
+
height: 0
|
|
255
|
+
};
|
|
256
|
+
if (x > padding.left && x + size.width > padding.left + targetWidth) {
|
|
257
|
+
x = padding.left;
|
|
258
|
+
y += rowHeight + spacing;
|
|
259
|
+
rowHeight = 0;
|
|
260
|
+
}
|
|
261
|
+
nodes.push({
|
|
262
|
+
...node,
|
|
263
|
+
x,
|
|
264
|
+
y,
|
|
265
|
+
...size
|
|
266
|
+
});
|
|
267
|
+
x += size.width + spacing;
|
|
268
|
+
rowHeight = Math.max(rowHeight, size.height);
|
|
269
|
+
}
|
|
270
|
+
return getFixedLayout({
|
|
271
|
+
...graph,
|
|
272
|
+
nodes
|
|
273
|
+
}, { direction: options.direction ?? graph.direction });
|
|
274
|
+
}
|
|
275
|
+
const rectanglePackingAlgorithm = {
|
|
276
|
+
id: "rectpacking",
|
|
277
|
+
capabilities: {
|
|
278
|
+
full: true,
|
|
279
|
+
incremental: false,
|
|
280
|
+
partial: false,
|
|
281
|
+
routeOnly: false,
|
|
282
|
+
hierarchy: false,
|
|
283
|
+
ports: true
|
|
284
|
+
},
|
|
285
|
+
layout(graph, options) {
|
|
286
|
+
return getRectanglePackingLayout(graph, options ?? {});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
/** Pinned output policy used only by the elkjs 0.11.1 compatibility adapter. */
|
|
290
|
+
const elkjs0111RectanglePackingAlgorithm = {
|
|
291
|
+
...rectanglePackingAlgorithm,
|
|
292
|
+
layout(graph, options = {}) {
|
|
293
|
+
const result = getRectanglePackingLayout(graph, options);
|
|
294
|
+
const padding = getPadding$2(options.padding);
|
|
295
|
+
return setElkjs0111ResultPolicy(result, {
|
|
296
|
+
providerBounds: result.nodes.length === 0 ? {
|
|
297
|
+
width: 0,
|
|
298
|
+
height: 0
|
|
299
|
+
} : {
|
|
300
|
+
width: Math.max(...result.nodes.map((node) => node.x + node.width)) + padding.right,
|
|
301
|
+
height: Math.max(...result.nodes.map((node) => node.y + node.height)) + padding.bottom
|
|
302
|
+
},
|
|
303
|
+
preserveEdgeSections: true,
|
|
304
|
+
skipBoundsNormalization: true
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/random.ts
|
|
311
|
+
const nativeBehavior = {
|
|
312
|
+
elkjs0111EdgeTargetBug: false,
|
|
313
|
+
preserveProviderBounds: false
|
|
314
|
+
};
|
|
315
|
+
const elkjs0111Behavior = {
|
|
316
|
+
elkjs0111EdgeTargetBug: true,
|
|
317
|
+
preserveProviderBounds: true
|
|
318
|
+
};
|
|
319
|
+
function getPadding$1(value) {
|
|
320
|
+
if (typeof value === "number") return {
|
|
321
|
+
top: value,
|
|
322
|
+
right: value,
|
|
323
|
+
bottom: value,
|
|
324
|
+
left: value
|
|
325
|
+
};
|
|
326
|
+
return {
|
|
327
|
+
top: value?.top ?? 15,
|
|
328
|
+
right: value?.right ?? 15,
|
|
329
|
+
bottom: value?.bottom ?? 15,
|
|
330
|
+
left: value?.left ?? 15
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function borderPoint(source, target) {
|
|
334
|
+
const sourceCenter = {
|
|
335
|
+
x: source.x + source.width / 2,
|
|
336
|
+
y: source.y + source.height / 2
|
|
337
|
+
};
|
|
338
|
+
const targetCenter = {
|
|
339
|
+
x: target.x + target.width / 2,
|
|
340
|
+
y: target.y + target.height / 2
|
|
341
|
+
};
|
|
342
|
+
const dx = targetCenter.x - sourceCenter.x;
|
|
343
|
+
const dy = targetCenter.y - sourceCenter.y;
|
|
344
|
+
if (dx === 0 && dy === 0) return {
|
|
345
|
+
x: source.x + source.width,
|
|
346
|
+
y: sourceCenter.y
|
|
347
|
+
};
|
|
348
|
+
const scale = Math.min(dx === 0 ? Infinity : source.width / 2 / Math.abs(dx), dy === 0 ? Infinity : source.height / 2 / Math.abs(dy));
|
|
349
|
+
return {
|
|
350
|
+
x: sourceCenter.x + dx * scale,
|
|
351
|
+
y: sourceCenter.y + dy * scale
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
/** Seeded random distribution using Java's 48-bit `Random` sequence. */
|
|
355
|
+
function getRandomLayout(graph, options = {}) {
|
|
356
|
+
return getRandomLayoutWithBehavior(graph, options, nativeBehavior);
|
|
357
|
+
}
|
|
358
|
+
function getRandomLayoutWithBehavior(graph, options, behavior) {
|
|
359
|
+
if (graph.nodes.length === 0) return getFixedLayout(graph, options);
|
|
360
|
+
const random = new require_layered.JavaRandom(options.seed && options.seed !== 0 ? options.seed : Date.now());
|
|
361
|
+
const aspectRatio = Math.fround(options.aspectRatio ?? 1.6);
|
|
362
|
+
const spacing = Math.fround(options.spacing ?? 15);
|
|
363
|
+
const padding = getPadding$1(options.padding);
|
|
364
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, (0, _statelyai_graph_layout.getNodeSize)(node, options)]));
|
|
365
|
+
const nodeArea = [...sizes.values()].reduce((sum, size) => sum + size.width * size.height, 0);
|
|
366
|
+
const maximumWidth = Math.max(...[...sizes.values()].map((size) => size.width));
|
|
367
|
+
const maximumHeight = Math.max(...[...sizes.values()].map((size) => size.height));
|
|
368
|
+
const edgeFactor = 1 + graph.edges.length;
|
|
369
|
+
const drawArea = nodeArea + 2 * spacing * spacing * edgeFactor * graph.nodes.length;
|
|
370
|
+
const areaRoot = Math.sqrt(drawArea);
|
|
371
|
+
const drawWidth = Math.max(areaRoot * aspectRatio, maximumWidth);
|
|
372
|
+
const drawHeight = Math.max(areaRoot / aspectRatio, maximumHeight);
|
|
373
|
+
const nodes = graph.nodes.map((node) => {
|
|
374
|
+
const size = sizes.get(node.id) ?? {
|
|
375
|
+
width: 0,
|
|
376
|
+
height: 0
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
...node,
|
|
380
|
+
x: padding.left + random.nextDouble() * (drawWidth - size.width),
|
|
381
|
+
y: padding.left + random.nextDouble() * (drawHeight - size.height),
|
|
382
|
+
...size
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
386
|
+
const totalWidth = drawWidth + padding.left + padding.right;
|
|
387
|
+
const totalHeight = drawHeight + padding.top + padding.bottom;
|
|
388
|
+
const layoutEdge = (edge) => {
|
|
389
|
+
const source = nodeById.get(edge.sourceId);
|
|
390
|
+
const target = nodeById.get(edge.targetId);
|
|
391
|
+
if (!source || !target) return edge;
|
|
392
|
+
if (behavior.elkjs0111EdgeTargetBug) {
|
|
393
|
+
const start$1 = {
|
|
394
|
+
x: source.x + source.width,
|
|
395
|
+
y: source.y + source.height / 2
|
|
396
|
+
};
|
|
397
|
+
const end$1 = {
|
|
398
|
+
x: source.x + source.width / 2,
|
|
399
|
+
y: source.y + source.height
|
|
400
|
+
};
|
|
401
|
+
const bendCount$1 = random.nextInt(5) + 1;
|
|
402
|
+
const xDifference = end$1.x - start$1.x;
|
|
403
|
+
const yDifference = end$1.y - start$1.y;
|
|
404
|
+
const maximumDeviation$1 = Math.hypot(xDifference, yDifference) * Math.fround(.2);
|
|
405
|
+
const xIncrement = xDifference / (bendCount$1 + 1);
|
|
406
|
+
const yIncrement = yDifference / (bendCount$1 + 1);
|
|
407
|
+
let x = start$1.x;
|
|
408
|
+
let y = start$1.y;
|
|
409
|
+
const points$1 = [start$1];
|
|
410
|
+
for (let index = 0; index < bendCount$1; index++) {
|
|
411
|
+
x += xIncrement;
|
|
412
|
+
y += yIncrement;
|
|
413
|
+
let randomX = x + random.nextFloat() * maximumDeviation$1 - maximumDeviation$1 / 2;
|
|
414
|
+
if (randomX < 0) randomX = 1;
|
|
415
|
+
else if (randomX > totalWidth) randomX = totalWidth - 1;
|
|
416
|
+
let randomY = y + random.nextFloat() * maximumDeviation$1 - maximumDeviation$1 / 2;
|
|
417
|
+
if (randomY < 0) randomY = 1;
|
|
418
|
+
else if (randomY > totalHeight) randomY = totalHeight - 1;
|
|
419
|
+
points$1.push({
|
|
420
|
+
x: randomX,
|
|
421
|
+
y: randomY
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
points$1.push(end$1);
|
|
425
|
+
return {
|
|
426
|
+
...edge,
|
|
427
|
+
points: points$1,
|
|
428
|
+
routing: "polyline"
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const start = borderPoint(source, target);
|
|
432
|
+
const end = borderPoint(target, source);
|
|
433
|
+
const bendCount = random.nextInt(5) + (source === target ? 1 : 0);
|
|
434
|
+
const maximumDeviation = Math.hypot(end.x - start.x, end.y - start.y) * .2;
|
|
435
|
+
const points = [start];
|
|
436
|
+
for (let index = 1; index <= bendCount; index++) {
|
|
437
|
+
const progress = index / (bendCount + 1);
|
|
438
|
+
points.push({
|
|
439
|
+
x: Math.min(totalWidth - 1, Math.max(1, start.x + (end.x - start.x) * progress + random.nextFloat() * maximumDeviation - maximumDeviation / 2)),
|
|
440
|
+
y: Math.min(totalHeight - 1, Math.max(1, start.y + (end.y - start.y) * progress + random.nextFloat() * maximumDeviation - maximumDeviation / 2))
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
points.push(end);
|
|
444
|
+
return {
|
|
445
|
+
...edge,
|
|
446
|
+
points,
|
|
447
|
+
routing: "polyline"
|
|
448
|
+
};
|
|
449
|
+
};
|
|
450
|
+
const edges = behavior.elkjs0111EdgeTargetBug ? graph.nodes.flatMap((node) => graph.edges.filter((edge) => edge.sourceId === node.id).map(layoutEdge)) : graph.edges.map(layoutEdge);
|
|
451
|
+
const result = getFixedLayout({
|
|
452
|
+
...graph,
|
|
453
|
+
nodes,
|
|
454
|
+
edges
|
|
455
|
+
}, { direction: options.direction ?? graph.direction });
|
|
456
|
+
if (behavior.preserveProviderBounds) setElkjs0111ResultPolicy(result, { providerBounds: {
|
|
457
|
+
width: drawWidth + 2 * (padding.left + padding.right),
|
|
458
|
+
height: drawHeight + 2 * (padding.top + padding.bottom)
|
|
459
|
+
} });
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
const randomAlgorithm = {
|
|
463
|
+
id: "random",
|
|
464
|
+
capabilities: {
|
|
465
|
+
full: true,
|
|
466
|
+
incremental: false,
|
|
467
|
+
partial: false,
|
|
468
|
+
routeOnly: false,
|
|
469
|
+
hierarchy: false,
|
|
470
|
+
ports: false
|
|
471
|
+
},
|
|
472
|
+
layout(graph, options) {
|
|
473
|
+
return getRandomLayout(graph, options ?? {});
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
/** Pinned behavior used only by the elkjs 0.11.1 compatibility adapter. */
|
|
477
|
+
const elkjs0111RandomAlgorithm = {
|
|
478
|
+
...randomAlgorithm,
|
|
479
|
+
layout(graph, options) {
|
|
480
|
+
return getRandomLayoutWithBehavior(graph, options ?? {}, elkjs0111Behavior);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/spore.ts
|
|
486
|
+
function getPadding(value) {
|
|
487
|
+
if (typeof value === "number") return {
|
|
488
|
+
top: value,
|
|
489
|
+
right: value,
|
|
490
|
+
bottom: value,
|
|
491
|
+
left: value
|
|
492
|
+
};
|
|
493
|
+
return {
|
|
494
|
+
top: value?.top ?? 0,
|
|
495
|
+
right: value?.right ?? 0,
|
|
496
|
+
bottom: value?.bottom ?? 0,
|
|
497
|
+
left: value?.left ?? 0
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function getSporeLayout(graph, options, compact) {
|
|
501
|
+
const spacing = options.spacing ?? 20;
|
|
502
|
+
const padding = getPadding(options.padding);
|
|
503
|
+
const nodes = [];
|
|
504
|
+
graph.nodes.forEach((node, index) => {
|
|
505
|
+
const size = (0, _statelyai_graph_layout.getNodeSize)(node, options);
|
|
506
|
+
const previous = graph.nodes[index - 1];
|
|
507
|
+
const placedPrevious = nodes[index - 1];
|
|
508
|
+
if (!previous || !placedPrevious) {
|
|
509
|
+
nodes.push({
|
|
510
|
+
...node,
|
|
511
|
+
x: padding.left,
|
|
512
|
+
y: padding.top,
|
|
513
|
+
...size
|
|
514
|
+
});
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const deltaX = (node.x ?? 0) - (previous.x ?? 0);
|
|
518
|
+
const deltaY = (node.y ?? 0) - (previous.y ?? 0);
|
|
519
|
+
const requiredX = placedPrevious.width + spacing;
|
|
520
|
+
const requiredY = placedPrevious.height + spacing;
|
|
521
|
+
const distance = (delta, required) => {
|
|
522
|
+
if (delta === 0) return 0;
|
|
523
|
+
const magnitude = compact ? required : Math.max(Math.abs(delta), required);
|
|
524
|
+
return Math.sign(delta) * magnitude;
|
|
525
|
+
};
|
|
526
|
+
nodes.push({
|
|
527
|
+
...node,
|
|
528
|
+
x: placedPrevious.x + distance(deltaX, requiredX),
|
|
529
|
+
y: placedPrevious.y + distance(deltaY, requiredY),
|
|
530
|
+
...size
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
return getFixedLayout({
|
|
534
|
+
...graph,
|
|
535
|
+
nodes
|
|
536
|
+
}, { direction: options.direction ?? graph.direction });
|
|
537
|
+
}
|
|
538
|
+
/** Compact an existing layout while preserving its relative directions. */
|
|
539
|
+
function getSporeCompactionLayout(graph, options = {}) {
|
|
540
|
+
return getSporeLayout(graph, options, true);
|
|
541
|
+
}
|
|
542
|
+
/** Remove overlap while preserving existing distances that already fit. */
|
|
543
|
+
function getSporeOverlapRemovalLayout(graph, options = {}) {
|
|
544
|
+
return getSporeLayout(graph, options, false);
|
|
545
|
+
}
|
|
546
|
+
function algorithm(id, compact) {
|
|
547
|
+
return {
|
|
548
|
+
id,
|
|
549
|
+
capabilities: {
|
|
550
|
+
full: true,
|
|
551
|
+
incremental: false,
|
|
552
|
+
partial: false,
|
|
553
|
+
routeOnly: false,
|
|
554
|
+
hierarchy: false,
|
|
555
|
+
ports: true
|
|
556
|
+
},
|
|
557
|
+
layout(graph, options) {
|
|
558
|
+
return getSporeLayout(graph, options ?? {}, compact);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
const sporeCompactionAlgorithm = algorithm("sporeCompaction", true);
|
|
563
|
+
const sporeOverlapRemovalAlgorithm = algorithm("sporeOverlap", false);
|
|
564
|
+
|
|
565
|
+
//#endregion
|
|
566
|
+
//#region src/internal/layout-engine.ts
|
|
567
|
+
const builtInLayoutAlgorithms = {
|
|
568
|
+
box: boxAlgorithm,
|
|
569
|
+
fixed: fixedAlgorithm,
|
|
570
|
+
layered: require_layered.layeredAlgorithm,
|
|
571
|
+
random: randomAlgorithm,
|
|
572
|
+
rectpacking: rectanglePackingAlgorithm,
|
|
573
|
+
sporeCompaction: sporeCompactionAlgorithm,
|
|
574
|
+
sporeOverlap: sporeOverlapRemovalAlgorithm
|
|
575
|
+
};
|
|
576
|
+
const elkjs0111LayoutAlgorithms = {
|
|
577
|
+
...builtInLayoutAlgorithms,
|
|
578
|
+
box: elkjs0111BoxAlgorithm,
|
|
579
|
+
random: elkjs0111RandomAlgorithm,
|
|
580
|
+
rectpacking: elkjs0111RectanglePackingAlgorithm
|
|
581
|
+
};
|
|
582
|
+
function getBuiltInLayoutAlgorithm(id) {
|
|
583
|
+
if (!Object.hasOwn(builtInLayoutAlgorithms, id)) return void 0;
|
|
584
|
+
return builtInLayoutAlgorithms[id];
|
|
585
|
+
}
|
|
586
|
+
/** Execute one algorithm through the shared engine seam. */
|
|
587
|
+
function executeLayoutAlgorithm({ algorithm: algorithm$1, graph, options, context }) {
|
|
588
|
+
return algorithm$1.layout(graph, options, context);
|
|
589
|
+
}
|
|
590
|
+
function createDirectExecutionContext() {
|
|
591
|
+
return {
|
|
592
|
+
scope: { mode: "full" },
|
|
593
|
+
diagnostics: [],
|
|
594
|
+
measurePhase(_id, run) {
|
|
595
|
+
return run();
|
|
596
|
+
},
|
|
597
|
+
throwIfAborted() {}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/** Execute through the pinned elkjs 0.11.1 compatibility policy. */
|
|
601
|
+
function executeElkjs0111Layout({ algorithm: algorithm$1, graph, options }) {
|
|
602
|
+
return executeLayoutAlgorithm({
|
|
603
|
+
algorithm: elkjs0111LayoutAlgorithms[algorithm$1],
|
|
604
|
+
graph,
|
|
605
|
+
options,
|
|
606
|
+
context: createDirectExecutionContext()
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
//#endregion
|
|
611
|
+
Object.defineProperty(exports, 'boxAlgorithm', {
|
|
612
|
+
enumerable: true,
|
|
613
|
+
get: function () {
|
|
614
|
+
return boxAlgorithm;
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
Object.defineProperty(exports, 'builtInLayoutAlgorithms', {
|
|
618
|
+
enumerable: true,
|
|
619
|
+
get: function () {
|
|
620
|
+
return builtInLayoutAlgorithms;
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
Object.defineProperty(exports, 'elkjs0111ResultPolicy', {
|
|
624
|
+
enumerable: true,
|
|
625
|
+
get: function () {
|
|
626
|
+
return elkjs0111ResultPolicy;
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
Object.defineProperty(exports, 'executeElkjs0111Layout', {
|
|
630
|
+
enumerable: true,
|
|
631
|
+
get: function () {
|
|
632
|
+
return executeElkjs0111Layout;
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
Object.defineProperty(exports, 'executeLayoutAlgorithm', {
|
|
636
|
+
enumerable: true,
|
|
637
|
+
get: function () {
|
|
638
|
+
return executeLayoutAlgorithm;
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
Object.defineProperty(exports, 'fixedAlgorithm', {
|
|
642
|
+
enumerable: true,
|
|
643
|
+
get: function () {
|
|
644
|
+
return fixedAlgorithm;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
Object.defineProperty(exports, 'getBoxLayout', {
|
|
648
|
+
enumerable: true,
|
|
649
|
+
get: function () {
|
|
650
|
+
return getBoxLayout;
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
Object.defineProperty(exports, 'getBuiltInLayoutAlgorithm', {
|
|
654
|
+
enumerable: true,
|
|
655
|
+
get: function () {
|
|
656
|
+
return getBuiltInLayoutAlgorithm;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
Object.defineProperty(exports, 'getFixedLayout', {
|
|
660
|
+
enumerable: true,
|
|
661
|
+
get: function () {
|
|
662
|
+
return getFixedLayout;
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
Object.defineProperty(exports, 'getRandomLayout', {
|
|
666
|
+
enumerable: true,
|
|
667
|
+
get: function () {
|
|
668
|
+
return getRandomLayout;
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
Object.defineProperty(exports, 'getRectanglePackingLayout', {
|
|
672
|
+
enumerable: true,
|
|
673
|
+
get: function () {
|
|
674
|
+
return getRectanglePackingLayout;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
Object.defineProperty(exports, 'getSporeCompactionLayout', {
|
|
678
|
+
enumerable: true,
|
|
679
|
+
get: function () {
|
|
680
|
+
return getSporeCompactionLayout;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
Object.defineProperty(exports, 'getSporeOverlapRemovalLayout', {
|
|
684
|
+
enumerable: true,
|
|
685
|
+
get: function () {
|
|
686
|
+
return getSporeOverlapRemovalLayout;
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
Object.defineProperty(exports, 'randomAlgorithm', {
|
|
690
|
+
enumerable: true,
|
|
691
|
+
get: function () {
|
|
692
|
+
return randomAlgorithm;
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
Object.defineProperty(exports, 'rectanglePackingAlgorithm', {
|
|
696
|
+
enumerable: true,
|
|
697
|
+
get: function () {
|
|
698
|
+
return rectanglePackingAlgorithm;
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
Object.defineProperty(exports, 'sporeCompactionAlgorithm', {
|
|
702
|
+
enumerable: true,
|
|
703
|
+
get: function () {
|
|
704
|
+
return sporeCompactionAlgorithm;
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
Object.defineProperty(exports, 'sporeOverlapRemovalAlgorithm', {
|
|
708
|
+
enumerable: true,
|
|
709
|
+
get: function () {
|
|
710
|
+
return sporeOverlapRemovalAlgorithm;
|
|
711
|
+
}
|
|
712
|
+
});
|