@statelyai/layout 0.0.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/LICENSE.md +264 -0
- package/NOTICE.md +10 -0
- package/README.md +140 -0
- package/dist/elkjs/index.d.mts +97 -0
- package/dist/elkjs/index.mjs +412 -0
- package/dist/index-v0P1Ake8.d.mts +149 -0
- package/dist/index.d.mts +77 -0
- package/dist/index.mjs +139 -0
- package/dist/layered/index.d.mts +2 -0
- package/dist/layered/index.mjs +3 -0
- package/dist/layered-ByNCZQgJ.mjs +439 -0
- package/dist/spore-cihb_Aht.mjs +487 -0
- package/package.json +61 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { a as getPolylineMidpoint, c as placePorts } from "./layered-ByNCZQgJ.mjs";
|
|
2
|
+
import { getNodeSize } from "@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 = getNodeSize(node, options);
|
|
19
|
+
const rect = {
|
|
20
|
+
x: node.x ?? 0,
|
|
21
|
+
y: node.y ?? 0,
|
|
22
|
+
...size
|
|
23
|
+
};
|
|
24
|
+
const ports = 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 = 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/box.ts
|
|
73
|
+
function getPadding$3(value) {
|
|
74
|
+
if (typeof value === "number") return {
|
|
75
|
+
top: value,
|
|
76
|
+
right: value,
|
|
77
|
+
bottom: value,
|
|
78
|
+
left: value
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
top: value?.top ?? 15,
|
|
82
|
+
right: value?.right ?? 15,
|
|
83
|
+
bottom: value?.bottom ?? 15,
|
|
84
|
+
left: value?.left ?? 15
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function standardDeviation(values, mean) {
|
|
88
|
+
if (values.length < 2) return 0;
|
|
89
|
+
const variance = values.reduce((sum, value) => sum + (value - mean) ** 2, 0);
|
|
90
|
+
return Math.sqrt(variance / (values.length - 1));
|
|
91
|
+
}
|
|
92
|
+
/** ELK Box SIMPLE packing translated onto `@statelyai/graph`. */
|
|
93
|
+
function getBoxLayout(graph, options = {}) {
|
|
94
|
+
const spacing = Math.fround(options.spacing ?? 15);
|
|
95
|
+
const padding = getPadding$3(options.padding);
|
|
96
|
+
const aspectRatio = (options.aspectRatio ?? 1.3) > 0 ? options.aspectRatio ?? 1.3 : 1.3;
|
|
97
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, getNodeSize(node, options)]));
|
|
98
|
+
const sortedNodes = [...graph.nodes].sort((first, second) => {
|
|
99
|
+
const priorityDifference = (options.priority?.(second) ?? 0) - (options.priority?.(first) ?? 0);
|
|
100
|
+
if (priorityDifference !== 0) return priorityDifference;
|
|
101
|
+
if (options.interactive) {
|
|
102
|
+
const yDifference = (first.y ?? 0) - (second.y ?? 0);
|
|
103
|
+
if (yDifference !== 0) return yDifference;
|
|
104
|
+
const xDifference = (first.x ?? 0) - (second.x ?? 0);
|
|
105
|
+
if (xDifference !== 0) return xDifference;
|
|
106
|
+
}
|
|
107
|
+
const firstSize = sizes.get(first.id) ?? {
|
|
108
|
+
width: 0,
|
|
109
|
+
height: 0
|
|
110
|
+
};
|
|
111
|
+
const secondSize = sizes.get(second.id) ?? {
|
|
112
|
+
width: 0,
|
|
113
|
+
height: 0
|
|
114
|
+
};
|
|
115
|
+
return firstSize.width * firstSize.height - secondSize.width * secondSize.height;
|
|
116
|
+
});
|
|
117
|
+
const areas = sortedNodes.map((node) => {
|
|
118
|
+
const size = sizes.get(node.id) ?? {
|
|
119
|
+
width: 0,
|
|
120
|
+
height: 0
|
|
121
|
+
};
|
|
122
|
+
return size.width * size.height;
|
|
123
|
+
});
|
|
124
|
+
let totalArea = areas.reduce((sum, area) => sum + area, 0);
|
|
125
|
+
const mean = areas.length === 0 ? 0 : totalArea / areas.length;
|
|
126
|
+
totalArea += areas.length * standardDeviation(areas, mean);
|
|
127
|
+
totalArea += Math.sqrt(totalArea) * (padding.bottom + padding.top);
|
|
128
|
+
totalArea += Math.sqrt(totalArea) * padding.right;
|
|
129
|
+
const widestNode = Math.max(0, ...sortedNodes.map((node) => sizes.get(node.id)?.width ?? 0));
|
|
130
|
+
const maximumRowWidth = Math.max(widestNode, Math.sqrt(totalArea * aspectRatio)) + padding.left;
|
|
131
|
+
let x = padding.left;
|
|
132
|
+
let y = padding.top;
|
|
133
|
+
let rowHeight = 0;
|
|
134
|
+
let rowStart = 0;
|
|
135
|
+
const rows = [];
|
|
136
|
+
const nodes = [];
|
|
137
|
+
for (const node of sortedNodes) {
|
|
138
|
+
const size = sizes.get(node.id) ?? {
|
|
139
|
+
width: 0,
|
|
140
|
+
height: 0
|
|
141
|
+
};
|
|
142
|
+
if (x + size.width > maximumRowWidth) {
|
|
143
|
+
rows.push({
|
|
144
|
+
start: rowStart,
|
|
145
|
+
end: nodes.length,
|
|
146
|
+
height: rowHeight
|
|
147
|
+
});
|
|
148
|
+
rowStart = nodes.length;
|
|
149
|
+
x = padding.left;
|
|
150
|
+
y += rowHeight + spacing;
|
|
151
|
+
rowHeight = 0;
|
|
152
|
+
}
|
|
153
|
+
nodes.push({
|
|
154
|
+
...node,
|
|
155
|
+
x,
|
|
156
|
+
y,
|
|
157
|
+
...size
|
|
158
|
+
});
|
|
159
|
+
x += size.width + spacing;
|
|
160
|
+
rowHeight = Math.max(rowHeight, size.height);
|
|
161
|
+
}
|
|
162
|
+
rows.push({
|
|
163
|
+
start: rowStart,
|
|
164
|
+
end: nodes.length,
|
|
165
|
+
height: rowHeight
|
|
166
|
+
});
|
|
167
|
+
if (options.expandNodes) {
|
|
168
|
+
const broadestRow = Math.max(padding.left + padding.right, ...nodes.map((node) => node.x + node.width + padding.right));
|
|
169
|
+
for (const row of rows) for (let index = row.start; index < row.end; index++) {
|
|
170
|
+
const node = nodes[index];
|
|
171
|
+
if (!node) continue;
|
|
172
|
+
node.height = row.height;
|
|
173
|
+
if (index === row.end - 1) node.width = broadestRow - node.x - padding.right;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const placedById = new Map(nodes.map((node) => [node.id, node]));
|
|
177
|
+
return getFixedLayout({
|
|
178
|
+
...graph,
|
|
179
|
+
nodes: graph.nodes.map((node) => placedById.get(node.id) ?? node)
|
|
180
|
+
}, { direction: options.direction ?? graph.direction });
|
|
181
|
+
}
|
|
182
|
+
const boxAlgorithm = {
|
|
183
|
+
id: "box",
|
|
184
|
+
capabilities: {
|
|
185
|
+
full: true,
|
|
186
|
+
incremental: false,
|
|
187
|
+
partial: false,
|
|
188
|
+
routeOnly: false,
|
|
189
|
+
hierarchy: false,
|
|
190
|
+
ports: true
|
|
191
|
+
},
|
|
192
|
+
layout(graph, options) {
|
|
193
|
+
return getBoxLayout(graph, options ?? {});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/packing.ts
|
|
199
|
+
function getPadding$2(value) {
|
|
200
|
+
if (typeof value === "number") return {
|
|
201
|
+
top: value,
|
|
202
|
+
right: value,
|
|
203
|
+
bottom: value,
|
|
204
|
+
left: value
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
top: value?.top ?? 0,
|
|
208
|
+
right: value?.right ?? 0,
|
|
209
|
+
bottom: value?.bottom ?? 0,
|
|
210
|
+
left: value?.left ?? 0
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Deterministic shelf-based rectangle packing for `@statelyai/graph`. */
|
|
214
|
+
function getRectanglePackingLayout(graph, options = {}) {
|
|
215
|
+
const spacing = options.spacing ?? 20;
|
|
216
|
+
const padding = getPadding$2(options.padding);
|
|
217
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, getNodeSize(node, options)]));
|
|
218
|
+
const totalArea = [...sizes.values()].reduce((area, size) => area + (size.width + spacing) * (size.height + spacing), 0);
|
|
219
|
+
const targetWidth = options.targetWidth ?? Math.max(1, Math.sqrt(totalArea) * 1.5);
|
|
220
|
+
let x = padding.left;
|
|
221
|
+
let y = padding.top;
|
|
222
|
+
let rowHeight = 0;
|
|
223
|
+
const nodes = [];
|
|
224
|
+
for (const node of graph.nodes) {
|
|
225
|
+
const size = sizes.get(node.id) ?? {
|
|
226
|
+
width: 0,
|
|
227
|
+
height: 0
|
|
228
|
+
};
|
|
229
|
+
if (x > padding.left && x + size.width > padding.left + targetWidth) {
|
|
230
|
+
x = padding.left;
|
|
231
|
+
y += rowHeight + spacing;
|
|
232
|
+
rowHeight = 0;
|
|
233
|
+
}
|
|
234
|
+
nodes.push({
|
|
235
|
+
...node,
|
|
236
|
+
x,
|
|
237
|
+
y,
|
|
238
|
+
...size
|
|
239
|
+
});
|
|
240
|
+
x += size.width + spacing;
|
|
241
|
+
rowHeight = Math.max(rowHeight, size.height);
|
|
242
|
+
}
|
|
243
|
+
return getFixedLayout({
|
|
244
|
+
...graph,
|
|
245
|
+
nodes
|
|
246
|
+
}, { direction: options.direction ?? graph.direction });
|
|
247
|
+
}
|
|
248
|
+
const rectanglePackingAlgorithm = {
|
|
249
|
+
id: "rectpacking",
|
|
250
|
+
capabilities: {
|
|
251
|
+
full: true,
|
|
252
|
+
incremental: false,
|
|
253
|
+
partial: false,
|
|
254
|
+
routeOnly: false,
|
|
255
|
+
hierarchy: false,
|
|
256
|
+
ports: true
|
|
257
|
+
},
|
|
258
|
+
layout(graph, options) {
|
|
259
|
+
return getRectanglePackingLayout(graph, options ?? {});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/random.ts
|
|
265
|
+
var JavaRandom = class JavaRandom {
|
|
266
|
+
static #multiplier = 25214903917n;
|
|
267
|
+
static #addend = 11n;
|
|
268
|
+
static #mask = (1n << 48n) - 1n;
|
|
269
|
+
#seed;
|
|
270
|
+
constructor(seed) {
|
|
271
|
+
this.#seed = (BigInt(seed) ^ JavaRandom.#multiplier) & JavaRandom.#mask;
|
|
272
|
+
}
|
|
273
|
+
#next(bits) {
|
|
274
|
+
this.#seed = this.#seed * JavaRandom.#multiplier + JavaRandom.#addend & JavaRandom.#mask;
|
|
275
|
+
return Number(this.#seed >> BigInt(48 - bits));
|
|
276
|
+
}
|
|
277
|
+
nextDouble() {
|
|
278
|
+
return (this.#next(26) * 2 ** 27 + this.#next(27)) / 2 ** 53;
|
|
279
|
+
}
|
|
280
|
+
nextFloat() {
|
|
281
|
+
return this.#next(24) / 2 ** 24;
|
|
282
|
+
}
|
|
283
|
+
nextInt(bound) {
|
|
284
|
+
if (bound <= 0) throw new RangeError("bound must be positive");
|
|
285
|
+
if ((bound & -bound) === bound) return Math.floor(bound * this.#next(31) / 2 ** 31);
|
|
286
|
+
let bits;
|
|
287
|
+
let value;
|
|
288
|
+
do {
|
|
289
|
+
bits = this.#next(31);
|
|
290
|
+
value = bits % bound;
|
|
291
|
+
} while (bits - value + (bound - 1) >= 2 ** 31);
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
function getPadding$1(value) {
|
|
296
|
+
if (typeof value === "number") return {
|
|
297
|
+
top: value,
|
|
298
|
+
right: value,
|
|
299
|
+
bottom: value,
|
|
300
|
+
left: value
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
top: value?.top ?? 15,
|
|
304
|
+
right: value?.right ?? 15,
|
|
305
|
+
bottom: value?.bottom ?? 15,
|
|
306
|
+
left: value?.left ?? 15
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function borderPoint(source, target) {
|
|
310
|
+
const sourceCenter = {
|
|
311
|
+
x: source.x + source.width / 2,
|
|
312
|
+
y: source.y + source.height / 2
|
|
313
|
+
};
|
|
314
|
+
const targetCenter = {
|
|
315
|
+
x: target.x + target.width / 2,
|
|
316
|
+
y: target.y + target.height / 2
|
|
317
|
+
};
|
|
318
|
+
const dx = targetCenter.x - sourceCenter.x;
|
|
319
|
+
const dy = targetCenter.y - sourceCenter.y;
|
|
320
|
+
if (dx === 0 && dy === 0) return {
|
|
321
|
+
x: source.x + source.width,
|
|
322
|
+
y: sourceCenter.y
|
|
323
|
+
};
|
|
324
|
+
const scale = Math.min(dx === 0 ? Infinity : source.width / 2 / Math.abs(dx), dy === 0 ? Infinity : source.height / 2 / Math.abs(dy));
|
|
325
|
+
return {
|
|
326
|
+
x: sourceCenter.x + dx * scale,
|
|
327
|
+
y: sourceCenter.y + dy * scale
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Seeded random distribution using Java's 48-bit `Random` sequence. */
|
|
331
|
+
function getRandomLayout(graph, options = {}) {
|
|
332
|
+
if (graph.nodes.length === 0) return getFixedLayout(graph, options);
|
|
333
|
+
const random = new JavaRandom(options.seed && options.seed !== 0 ? options.seed : Date.now());
|
|
334
|
+
const aspectRatio = Math.fround(options.aspectRatio ?? 1.6);
|
|
335
|
+
const spacing = Math.fround(options.spacing ?? 15);
|
|
336
|
+
const padding = getPadding$1(options.padding);
|
|
337
|
+
const sizes = new Map(graph.nodes.map((node) => [node.id, getNodeSize(node, options)]));
|
|
338
|
+
const nodeArea = [...sizes.values()].reduce((sum, size) => sum + size.width * size.height, 0);
|
|
339
|
+
const maximumWidth = Math.max(...[...sizes.values()].map((size) => size.width));
|
|
340
|
+
const maximumHeight = Math.max(...[...sizes.values()].map((size) => size.height));
|
|
341
|
+
const edgeFactor = 1 + graph.edges.length;
|
|
342
|
+
const drawArea = nodeArea + 2 * spacing * spacing * edgeFactor * graph.nodes.length;
|
|
343
|
+
const areaRoot = Math.sqrt(drawArea);
|
|
344
|
+
const drawWidth = Math.max(areaRoot * aspectRatio, maximumWidth);
|
|
345
|
+
const drawHeight = Math.max(areaRoot / aspectRatio, maximumHeight);
|
|
346
|
+
const nodes = graph.nodes.map((node) => {
|
|
347
|
+
const size = sizes.get(node.id) ?? {
|
|
348
|
+
width: 0,
|
|
349
|
+
height: 0
|
|
350
|
+
};
|
|
351
|
+
return {
|
|
352
|
+
...node,
|
|
353
|
+
x: padding.left + random.nextDouble() * (drawWidth - size.width),
|
|
354
|
+
y: padding.left + random.nextDouble() * (drawHeight - size.height),
|
|
355
|
+
...size
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
359
|
+
const totalWidth = drawWidth + padding.left + padding.right;
|
|
360
|
+
const totalHeight = drawHeight + padding.top + padding.bottom;
|
|
361
|
+
const edges = graph.edges.map((edge) => {
|
|
362
|
+
const source = nodeById.get(edge.sourceId);
|
|
363
|
+
const target = nodeById.get(edge.targetId);
|
|
364
|
+
if (!source || !target) return edge;
|
|
365
|
+
const start = borderPoint(source, target);
|
|
366
|
+
const end = borderPoint(target, source);
|
|
367
|
+
const bendCount = random.nextInt(5) + (source === target ? 1 : 0);
|
|
368
|
+
const maximumDeviation = Math.hypot(end.x - start.x, end.y - start.y) * .2;
|
|
369
|
+
const points = [start];
|
|
370
|
+
for (let index = 1; index <= bendCount; index++) {
|
|
371
|
+
const progress = index / (bendCount + 1);
|
|
372
|
+
points.push({
|
|
373
|
+
x: Math.min(totalWidth - 1, Math.max(1, start.x + (end.x - start.x) * progress + random.nextFloat() * maximumDeviation - maximumDeviation / 2)),
|
|
374
|
+
y: Math.min(totalHeight - 1, Math.max(1, start.y + (end.y - start.y) * progress + random.nextFloat() * maximumDeviation - maximumDeviation / 2))
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
points.push(end);
|
|
378
|
+
return {
|
|
379
|
+
...edge,
|
|
380
|
+
points,
|
|
381
|
+
routing: "polyline"
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
return getFixedLayout({
|
|
385
|
+
...graph,
|
|
386
|
+
nodes,
|
|
387
|
+
edges
|
|
388
|
+
}, { direction: options.direction ?? graph.direction });
|
|
389
|
+
}
|
|
390
|
+
const randomAlgorithm = {
|
|
391
|
+
id: "random",
|
|
392
|
+
capabilities: {
|
|
393
|
+
full: true,
|
|
394
|
+
incremental: false,
|
|
395
|
+
partial: false,
|
|
396
|
+
routeOnly: false,
|
|
397
|
+
hierarchy: false,
|
|
398
|
+
ports: false
|
|
399
|
+
},
|
|
400
|
+
layout(graph, options) {
|
|
401
|
+
return getRandomLayout(graph, options ?? {});
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
//#endregion
|
|
406
|
+
//#region src/spore.ts
|
|
407
|
+
function getPadding(value) {
|
|
408
|
+
if (typeof value === "number") return {
|
|
409
|
+
top: value,
|
|
410
|
+
right: value,
|
|
411
|
+
bottom: value,
|
|
412
|
+
left: value
|
|
413
|
+
};
|
|
414
|
+
return {
|
|
415
|
+
top: value?.top ?? 0,
|
|
416
|
+
right: value?.right ?? 0,
|
|
417
|
+
bottom: value?.bottom ?? 0,
|
|
418
|
+
left: value?.left ?? 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function getSporeLayout(graph, options, compact) {
|
|
422
|
+
const spacing = options.spacing ?? 20;
|
|
423
|
+
const padding = getPadding(options.padding);
|
|
424
|
+
const nodes = [];
|
|
425
|
+
graph.nodes.forEach((node, index) => {
|
|
426
|
+
const size = getNodeSize(node, options);
|
|
427
|
+
const previous = graph.nodes[index - 1];
|
|
428
|
+
const placedPrevious = nodes[index - 1];
|
|
429
|
+
if (!previous || !placedPrevious) {
|
|
430
|
+
nodes.push({
|
|
431
|
+
...node,
|
|
432
|
+
x: padding.left,
|
|
433
|
+
y: padding.top,
|
|
434
|
+
...size
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
const deltaX = (node.x ?? 0) - (previous.x ?? 0);
|
|
439
|
+
const deltaY = (node.y ?? 0) - (previous.y ?? 0);
|
|
440
|
+
const requiredX = placedPrevious.width + spacing;
|
|
441
|
+
const requiredY = placedPrevious.height + spacing;
|
|
442
|
+
const distance = (delta, required) => {
|
|
443
|
+
if (delta === 0) return 0;
|
|
444
|
+
const magnitude = compact ? required : Math.max(Math.abs(delta), required);
|
|
445
|
+
return Math.sign(delta) * magnitude;
|
|
446
|
+
};
|
|
447
|
+
nodes.push({
|
|
448
|
+
...node,
|
|
449
|
+
x: placedPrevious.x + distance(deltaX, requiredX),
|
|
450
|
+
y: placedPrevious.y + distance(deltaY, requiredY),
|
|
451
|
+
...size
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
return getFixedLayout({
|
|
455
|
+
...graph,
|
|
456
|
+
nodes
|
|
457
|
+
}, { direction: options.direction ?? graph.direction });
|
|
458
|
+
}
|
|
459
|
+
/** Compact an existing layout while preserving its relative directions. */
|
|
460
|
+
function getSporeCompactionLayout(graph, options = {}) {
|
|
461
|
+
return getSporeLayout(graph, options, true);
|
|
462
|
+
}
|
|
463
|
+
/** Remove overlap while preserving existing distances that already fit. */
|
|
464
|
+
function getSporeOverlapRemovalLayout(graph, options = {}) {
|
|
465
|
+
return getSporeLayout(graph, options, false);
|
|
466
|
+
}
|
|
467
|
+
function algorithm(id, compact) {
|
|
468
|
+
return {
|
|
469
|
+
id,
|
|
470
|
+
capabilities: {
|
|
471
|
+
full: true,
|
|
472
|
+
incremental: false,
|
|
473
|
+
partial: false,
|
|
474
|
+
routeOnly: false,
|
|
475
|
+
hierarchy: false,
|
|
476
|
+
ports: true
|
|
477
|
+
},
|
|
478
|
+
layout(graph, options) {
|
|
479
|
+
return getSporeLayout(graph, options ?? {}, compact);
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const sporeCompactionAlgorithm = algorithm("sporeCompaction", true);
|
|
484
|
+
const sporeOverlapRemovalAlgorithm = algorithm("sporeOverlap", false);
|
|
485
|
+
|
|
486
|
+
//#endregion
|
|
487
|
+
export { getRandomLayout as a, rectanglePackingAlgorithm as c, fixedAlgorithm as d, getFixedLayout as f, sporeOverlapRemovalAlgorithm as i, boxAlgorithm as l, getSporeOverlapRemovalLayout as n, randomAlgorithm as o, sporeCompactionAlgorithm as r, getRectanglePackingLayout as s, getSporeCompactionLayout as t, getBoxLayout as u };
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@statelyai/layout",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Extensible, graph-native layout algorithms for @statelyai/graph",
|
|
5
|
+
"license": "EPL-2.0 OR GPL-3.0-or-later",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"LICENSE.md",
|
|
9
|
+
"NOTICE.md"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.mjs",
|
|
14
|
+
"module": "./dist/index.mjs",
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.mjs",
|
|
18
|
+
"./elkjs": "./dist/elkjs/index.mjs",
|
|
19
|
+
"./layered": "./dist/layered/index.mjs",
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsdown",
|
|
24
|
+
"bench": "vitest bench --run",
|
|
25
|
+
"demo": "vite --config demo/vite.config.ts",
|
|
26
|
+
"demo:build": "vite build --config demo/vite.config.ts",
|
|
27
|
+
"demo:check": "tsx scripts/generate-demo-corpus.ts --check",
|
|
28
|
+
"demo:generate": "tsx scripts/generate-demo-corpus.ts",
|
|
29
|
+
"demo:typecheck": "tsc --project demo/tsconfig.json",
|
|
30
|
+
"dev": "tsdown --watch",
|
|
31
|
+
"format": "oxfmt .",
|
|
32
|
+
"format:check": "oxfmt --check .",
|
|
33
|
+
"lint": "oxlint",
|
|
34
|
+
"lint:fix": "oxlint --fix",
|
|
35
|
+
"test": "vitest",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"typecheck:repo": "tsc --project tsconfig.repo.json",
|
|
38
|
+
"verify": "oxfmt --check . && oxlint && tsc --noEmit && tsc --project tsconfig.repo.json && tsc --project demo/tsconfig.json && tsx scripts/generate-demo-corpus.ts --check && vitest --run && tsdown && vite build --config demo/vite.config.ts && publint --pack npm && tsx scripts/smoke-package.ts",
|
|
39
|
+
"validate:package": "publint --pack npm && tsx scripts/smoke-package.ts"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@statelyai/graph": "^2.1.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@statelyai/sdk": "^0.20.1",
|
|
46
|
+
"@types/node": "^25.0.3",
|
|
47
|
+
"elkjs": "^0.11.1",
|
|
48
|
+
"oxfmt": "0.62.0",
|
|
49
|
+
"oxlint": "1.77.0",
|
|
50
|
+
"publint": "^0.3.15",
|
|
51
|
+
"tsdown": "^0.18.1",
|
|
52
|
+
"tsx": "^4.22.4",
|
|
53
|
+
"typescript": "^5.9.3",
|
|
54
|
+
"vite": "^8.2.1",
|
|
55
|
+
"vitest": "^4.0.18"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@statelyai/graph": "^2.1.0"
|
|
59
|
+
},
|
|
60
|
+
"packageManager": "pnpm@10.28.2"
|
|
61
|
+
}
|