@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,412 @@
|
|
|
1
|
+
import { t as getLayeredLayout } from "../layered-ByNCZQgJ.mjs";
|
|
2
|
+
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-cihb_Aht.mjs";
|
|
3
|
+
import { createGraph } from "@statelyai/graph";
|
|
4
|
+
|
|
5
|
+
//#region src/elkjs/index.ts
|
|
6
|
+
var ELK = class {
|
|
7
|
+
#options;
|
|
8
|
+
#algorithmIds;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.#options = options;
|
|
11
|
+
this.#algorithmIds = new Set([
|
|
12
|
+
"box",
|
|
13
|
+
"fixed",
|
|
14
|
+
"random",
|
|
15
|
+
"rectpacking",
|
|
16
|
+
"sporeCompaction",
|
|
17
|
+
"sporeOverlap",
|
|
18
|
+
...(options.algorithms ?? ["layered"]).filter((id) => id === "layered")
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
async knownLayoutAlgorithms() {
|
|
22
|
+
return [...this.#algorithmIds].map((id) => ({
|
|
23
|
+
id,
|
|
24
|
+
name: {
|
|
25
|
+
layered: "Layered",
|
|
26
|
+
box: "Box",
|
|
27
|
+
fixed: "Fixed",
|
|
28
|
+
random: "Random",
|
|
29
|
+
rectpacking: "Rectangle Packing",
|
|
30
|
+
sporeCompaction: "SPOrE Compaction",
|
|
31
|
+
sporeOverlap: "SPOrE Overlap Removal"
|
|
32
|
+
}[id],
|
|
33
|
+
category: id === "layered" ? "layered" : "other",
|
|
34
|
+
knownOptions: id === "layered" ? [
|
|
35
|
+
"elk.direction",
|
|
36
|
+
"elk.padding",
|
|
37
|
+
"elk.spacing.nodeNode",
|
|
38
|
+
"elk.layered.spacing.nodeNodeBetweenLayers"
|
|
39
|
+
] : id === "box" ? [
|
|
40
|
+
"padding",
|
|
41
|
+
"spacing.nodeNode",
|
|
42
|
+
"aspectRatio",
|
|
43
|
+
"box.packingMode"
|
|
44
|
+
] : id === "random" ? [
|
|
45
|
+
"padding",
|
|
46
|
+
"spacing.nodeNode",
|
|
47
|
+
"aspectRatio",
|
|
48
|
+
"randomSeed"
|
|
49
|
+
] : ["position", "bendPoints"]
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
async knownLayoutOptions() {
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
id: "elk.algorithm",
|
|
56
|
+
name: "Layout Algorithm",
|
|
57
|
+
type: "STRING"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "elk.direction",
|
|
61
|
+
name: "Direction",
|
|
62
|
+
type: "ENUM"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "elk.padding",
|
|
66
|
+
name: "Padding",
|
|
67
|
+
type: "OBJECT"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "elk.aspectRatio",
|
|
71
|
+
name: "Aspect Ratio",
|
|
72
|
+
type: "DOUBLE"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "elk.randomSeed",
|
|
76
|
+
name: "Random Seed",
|
|
77
|
+
type: "INT"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "elk.spacing.nodeNode",
|
|
81
|
+
name: "Node Spacing",
|
|
82
|
+
type: "DOUBLE"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "elk.layered.spacing.nodeNodeBetweenLayers",
|
|
86
|
+
name: "Layer Spacing",
|
|
87
|
+
type: "DOUBLE"
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
async knownLayoutCategories() {
|
|
92
|
+
return [{
|
|
93
|
+
id: "layered",
|
|
94
|
+
name: "Layered",
|
|
95
|
+
knownLayouters: this.#algorithmIds.has("layered") ? ["layered"] : []
|
|
96
|
+
}, {
|
|
97
|
+
id: "other",
|
|
98
|
+
name: "Other",
|
|
99
|
+
knownLayouters: [
|
|
100
|
+
"box",
|
|
101
|
+
"fixed",
|
|
102
|
+
"random"
|
|
103
|
+
]
|
|
104
|
+
}];
|
|
105
|
+
}
|
|
106
|
+
terminateWorker() {}
|
|
107
|
+
async layout(graph, arguments_ = {}) {
|
|
108
|
+
const startedAt = performance.now();
|
|
109
|
+
if (graph === void 0 || graph === null) throw new TypeError("Missing mandatory parameter: graph");
|
|
110
|
+
if (typeof graph.id !== "string" && !(typeof graph.id === "number" && Number.isInteger(graph.id))) throw new TypeError("Graph id must be a string or integer");
|
|
111
|
+
delete graph.logging;
|
|
112
|
+
const layoutOptions = {
|
|
113
|
+
...this.#options.defaultLayoutOptions,
|
|
114
|
+
...arguments_.layoutOptions,
|
|
115
|
+
...graph.properties,
|
|
116
|
+
...graph.layoutOptions
|
|
117
|
+
};
|
|
118
|
+
const requestedAlgorithm = String(getOption(layoutOptions, "algorithm") ?? "layered");
|
|
119
|
+
const algorithm = requestedAlgorithm.replace(/^(?:org\.eclipse\.)?elk\./, "");
|
|
120
|
+
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`);
|
|
121
|
+
const hasHierarchy = (graph.children ?? []).some((child) => (child.children?.length ?? 0) > 0);
|
|
122
|
+
const hierarchyHandling = getOption(layoutOptions, "hierarchyHandling");
|
|
123
|
+
if (hasHierarchy && hierarchyHandling !== void 0 && hierarchyHandling !== "INCLUDE_CHILDREN") throw new Error("org.eclipse.elk.core.UnsupportedGraphException: Hierarchical edges require INCLUDE_CHILDREN");
|
|
124
|
+
if (hasHierarchy) for (const child of graph.children ?? []) {
|
|
125
|
+
if ((child.children?.length ?? 0) === 0) continue;
|
|
126
|
+
await this.layout(child, {
|
|
127
|
+
...arguments_,
|
|
128
|
+
layoutOptions: {
|
|
129
|
+
...arguments_.layoutOptions,
|
|
130
|
+
hierarchyHandling: "INCLUDE_CHILDREN"
|
|
131
|
+
},
|
|
132
|
+
logging: false,
|
|
133
|
+
measureExecutionTime: false
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const graph_ = toGraph(graph);
|
|
137
|
+
const padding = parsePadding(getOption(layoutOptions, "padding"));
|
|
138
|
+
const constrainedLayerByNodeId = new Map((graph.children ?? []).flatMap((node) => getOption(node.layoutOptions ?? {}, "layerConstraint") === "FIRST" ? [[String(node.id), 0]] : []));
|
|
139
|
+
if (algorithm === "layered" && graph_.edges.some((edge) => {
|
|
140
|
+
const sourceLayer = constrainedLayerByNodeId.get(edge.sourceId);
|
|
141
|
+
const targetLayer = constrainedLayerByNodeId.get(edge.targetId);
|
|
142
|
+
return sourceLayer !== void 0 && targetLayer !== void 0 && targetLayer <= sourceLayer;
|
|
143
|
+
})) throw new Error("org.eclipse.elk.core.UnsupportedConfigurationException: Layer constraints conflict");
|
|
144
|
+
applyLayout(graph, algorithm === "sporeCompaction" ? getSporeCompactionLayout(graph_, {
|
|
145
|
+
padding,
|
|
146
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
|
|
147
|
+
}) : algorithm === "sporeOverlap" ? getSporeOverlapRemovalLayout(graph_, {
|
|
148
|
+
padding,
|
|
149
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
|
|
150
|
+
}) : algorithm === "rectpacking" ? getRectanglePackingLayout(graph_, {
|
|
151
|
+
padding,
|
|
152
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode")
|
|
153
|
+
}) : algorithm === "random" ? getRandomLayout(graph_, {
|
|
154
|
+
padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
|
|
155
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
156
|
+
aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
|
|
157
|
+
seed: getNumberOption(layoutOptions, "randomSeed")
|
|
158
|
+
}) : algorithm === "box" ? getBoxLayout(graph_, {
|
|
159
|
+
padding: getOption(layoutOptions, "padding") === void 0 ? 15 : padding,
|
|
160
|
+
spacing: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
161
|
+
aspectRatio: getNumberOption(layoutOptions, "aspectRatio"),
|
|
162
|
+
interactive: getBooleanOption(layoutOptions, "interactive"),
|
|
163
|
+
expandNodes: getBooleanOption(layoutOptions, "expandNodes"),
|
|
164
|
+
priority: (node) => {
|
|
165
|
+
const child = graph.children?.find((candidate) => String(candidate.id) === node.id);
|
|
166
|
+
return getNumberOption(child?.layoutOptions ?? {}, "priority");
|
|
167
|
+
}
|
|
168
|
+
}) : algorithm === "fixed" ? getFixedLayout(graph_, { direction: getDirection(layoutOptions) }) : getLayeredLayout(graph_, {
|
|
169
|
+
direction: getDirection(layoutOptions),
|
|
170
|
+
spacing: {
|
|
171
|
+
node: getNumberOption(layoutOptions, "spacing.nodeNode"),
|
|
172
|
+
layer: getNumberOption(layoutOptions, "layered.spacing.nodeNodeBetweenLayers")
|
|
173
|
+
},
|
|
174
|
+
padding,
|
|
175
|
+
constraints: { layer: (node) => constrainedLayerByNodeId.get(node.id) }
|
|
176
|
+
}), padding, layoutOptions);
|
|
177
|
+
if (arguments_.logging || arguments_.measureExecutionTime) graph.logging = {
|
|
178
|
+
name: "Native TypeScript layout",
|
|
179
|
+
children: [{ name: String(algorithm) }],
|
|
180
|
+
...arguments_.measureExecutionTime ? { executionTime: (performance.now() - startedAt) / 1e3 } : {}
|
|
181
|
+
};
|
|
182
|
+
return graph;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
function parsePadding(value) {
|
|
186
|
+
if (typeof value === "number") return {
|
|
187
|
+
top: value,
|
|
188
|
+
right: value,
|
|
189
|
+
bottom: value,
|
|
190
|
+
left: value
|
|
191
|
+
};
|
|
192
|
+
if (typeof value !== "string") return {
|
|
193
|
+
top: 0,
|
|
194
|
+
right: 0,
|
|
195
|
+
bottom: 0,
|
|
196
|
+
left: 0
|
|
197
|
+
};
|
|
198
|
+
const padding = {
|
|
199
|
+
top: 0,
|
|
200
|
+
right: 0,
|
|
201
|
+
bottom: 0,
|
|
202
|
+
left: 0
|
|
203
|
+
};
|
|
204
|
+
for (const match of value.matchAll(/(top|right|bottom|left)\s*=\s*(-?\d+(?:\.\d+)?)/g)) {
|
|
205
|
+
const side = match[1];
|
|
206
|
+
padding[side] = Number(match[2]);
|
|
207
|
+
}
|
|
208
|
+
return padding;
|
|
209
|
+
}
|
|
210
|
+
function getOption(options, suffix) {
|
|
211
|
+
const exactKeys = [
|
|
212
|
+
suffix,
|
|
213
|
+
`elk.${suffix}`,
|
|
214
|
+
`org.eclipse.elk.${suffix}`
|
|
215
|
+
];
|
|
216
|
+
for (const key of exactKeys) if (options[key] !== void 0) return options[key];
|
|
217
|
+
return Object.entries(options).find(([key]) => key.endsWith(`.${suffix}`))?.[1];
|
|
218
|
+
}
|
|
219
|
+
function getNumberOption(options, suffix) {
|
|
220
|
+
const value = getOption(options, suffix);
|
|
221
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
222
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
223
|
+
const parsed = Number(value);
|
|
224
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function getBooleanOption(options, suffix) {
|
|
228
|
+
const value = getOption(options, suffix);
|
|
229
|
+
if (typeof value === "boolean") return value;
|
|
230
|
+
if (typeof value === "string") {
|
|
231
|
+
if (value.toLowerCase() === "true") return true;
|
|
232
|
+
if (value.toLowerCase() === "false") return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function getDirection(options) {
|
|
236
|
+
const direction = String(getOption(options, "direction") ?? "DOWN").toLowerCase();
|
|
237
|
+
return direction === "up" || direction === "left" || direction === "right" ? direction : "down";
|
|
238
|
+
}
|
|
239
|
+
function endpoint(value, portOwnerById) {
|
|
240
|
+
const id = String(value);
|
|
241
|
+
const ownerId = portOwnerById.get(id);
|
|
242
|
+
return ownerId === void 0 ? { nodeId: id } : {
|
|
243
|
+
nodeId: ownerId,
|
|
244
|
+
port: id
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function toGraph(root) {
|
|
248
|
+
const children = root.children ?? [];
|
|
249
|
+
const nodeIds = new Set(children.map((child) => String(child.id)));
|
|
250
|
+
const portOwnerById = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const child of children) for (const port of child.ports ?? []) if (port.id !== void 0) portOwnerById.set(String(port.id), String(child.id));
|
|
252
|
+
return createGraph({
|
|
253
|
+
id: String(root.id),
|
|
254
|
+
nodes: children.map((child) => ({
|
|
255
|
+
id: String(child.id),
|
|
256
|
+
x: child.x,
|
|
257
|
+
y: child.y,
|
|
258
|
+
...parsePosition(getOption(child.layoutOptions ?? {}, "position")),
|
|
259
|
+
width: child.width,
|
|
260
|
+
height: child.height,
|
|
261
|
+
label: child.labels?.[0]?.text,
|
|
262
|
+
ports: child.ports?.map((port) => ({
|
|
263
|
+
name: String(port.id),
|
|
264
|
+
direction: "inout",
|
|
265
|
+
x: port.x,
|
|
266
|
+
y: port.y,
|
|
267
|
+
width: port.width,
|
|
268
|
+
height: port.height
|
|
269
|
+
}))
|
|
270
|
+
})),
|
|
271
|
+
edges: (root.edges ?? []).flatMap((edge) => {
|
|
272
|
+
const source = endpoint(edge.sources?.[0] ?? edge.source, portOwnerById);
|
|
273
|
+
const target = endpoint(edge.targets?.[0] ?? edge.target, portOwnerById);
|
|
274
|
+
return nodeIds.has(source.nodeId) && nodeIds.has(target.nodeId) ? [{
|
|
275
|
+
id: String(edge.id),
|
|
276
|
+
sourceId: source.nodeId,
|
|
277
|
+
targetId: target.nodeId,
|
|
278
|
+
sourcePort: source.port,
|
|
279
|
+
targetPort: target.port,
|
|
280
|
+
label: edge.labels?.[0]?.text,
|
|
281
|
+
width: edge.labels?.[0]?.width,
|
|
282
|
+
height: edge.labels?.[0]?.height,
|
|
283
|
+
points: parsePoints(getOption(edge.layoutOptions ?? {}, "bendPoints"))
|
|
284
|
+
}] : [];
|
|
285
|
+
})
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function parsePoints(value) {
|
|
289
|
+
if (typeof value !== "string") return void 0;
|
|
290
|
+
const points = [...value.matchAll(/\{\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\}/g)].map((match) => ({
|
|
291
|
+
x: Number(match[1]),
|
|
292
|
+
y: Number(match[2])
|
|
293
|
+
}));
|
|
294
|
+
return points.length > 0 ? points : void 0;
|
|
295
|
+
}
|
|
296
|
+
function parsePosition(value) {
|
|
297
|
+
if (typeof value !== "string") return void 0;
|
|
298
|
+
const match = value.match(/\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/);
|
|
299
|
+
return match ? {
|
|
300
|
+
x: Number(match[1]),
|
|
301
|
+
y: Number(match[2])
|
|
302
|
+
} : void 0;
|
|
303
|
+
}
|
|
304
|
+
function toSection(edge, points) {
|
|
305
|
+
const startPoint = points[0];
|
|
306
|
+
const endPoint = points.at(-1);
|
|
307
|
+
if (!startPoint || !endPoint) return void 0;
|
|
308
|
+
return {
|
|
309
|
+
id: `${String(edge.id)}_s0`,
|
|
310
|
+
startPoint: { ...startPoint },
|
|
311
|
+
endPoint: { ...endPoint },
|
|
312
|
+
...points.length > 2 ? { bendPoints: points.slice(1, -1).map((point) => ({ ...point })) } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function applyLayout(root, graph, padding, layoutOptions) {
|
|
316
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
317
|
+
const edgeById = new Map(graph.edges.map((edge) => [edge.id, edge]));
|
|
318
|
+
for (const child of root.children ?? []) {
|
|
319
|
+
const node = nodeById.get(String(child.id));
|
|
320
|
+
if (!node) continue;
|
|
321
|
+
child.x = node.x;
|
|
322
|
+
child.y = node.y;
|
|
323
|
+
child.width = node.width;
|
|
324
|
+
child.height = node.height;
|
|
325
|
+
placeNodeLabels(child, layoutOptions);
|
|
326
|
+
for (const port of child.ports ?? []) {
|
|
327
|
+
const laidOutPort = node.ports?.find((candidate) => candidate.name === String(port.id));
|
|
328
|
+
if (!laidOutPort) continue;
|
|
329
|
+
port.x = laidOutPort.x;
|
|
330
|
+
port.y = laidOutPort.y;
|
|
331
|
+
port.width = laidOutPort.width;
|
|
332
|
+
port.height = laidOutPort.height;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
for (const edge of root.edges ?? []) {
|
|
336
|
+
const laidOutEdge = edgeById.get(String(edge.id));
|
|
337
|
+
if (!laidOutEdge) {
|
|
338
|
+
const section$1 = getParentEdgeSection(root, edge);
|
|
339
|
+
if (section$1) edge.sections = [section$1];
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const section = toSection(edge, laidOutEdge.points ?? []);
|
|
343
|
+
edge.sections = section ? [section] : [];
|
|
344
|
+
const label = edge.labels?.[0];
|
|
345
|
+
if (label) {
|
|
346
|
+
label.x = laidOutEdge.x;
|
|
347
|
+
label.y = laidOutEdge.y;
|
|
348
|
+
label.width = laidOutEdge.width;
|
|
349
|
+
label.height = laidOutEdge.height;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
root.width = Math.max(0, ...graph.nodes.map((node) => node.x + node.width)) + padding.right;
|
|
353
|
+
root.height = Math.max(0, ...graph.nodes.map((node) => node.y + node.height)) + padding.bottom;
|
|
354
|
+
}
|
|
355
|
+
function getParentEdgeSection(root, edge) {
|
|
356
|
+
const sourceId = String(edge.sources?.[0] ?? edge.source);
|
|
357
|
+
const targetId = String(edge.targets?.[0] ?? edge.target);
|
|
358
|
+
const rootId = String(root.id);
|
|
359
|
+
const source = root.children?.find((child) => String(child.id) === sourceId);
|
|
360
|
+
const target = root.children?.find((child) => String(child.id) === targetId);
|
|
361
|
+
if (source && targetId === rootId) {
|
|
362
|
+
const startPoint = {
|
|
363
|
+
x: (source.x ?? 0) + (source.width ?? 0) / 2,
|
|
364
|
+
y: (source.y ?? 0) + (source.height ?? 0)
|
|
365
|
+
};
|
|
366
|
+
return {
|
|
367
|
+
id: `${String(edge.id)}_s0`,
|
|
368
|
+
startPoint,
|
|
369
|
+
endPoint: {
|
|
370
|
+
x: startPoint.x,
|
|
371
|
+
y: 0
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (sourceId === rootId && target) {
|
|
376
|
+
const endPoint = {
|
|
377
|
+
x: (target.x ?? 0) + (target.width ?? 0) / 2,
|
|
378
|
+
y: target.y ?? 0
|
|
379
|
+
};
|
|
380
|
+
return {
|
|
381
|
+
id: `${String(edge.id)}_s0`,
|
|
382
|
+
startPoint: {
|
|
383
|
+
x: endPoint.x,
|
|
384
|
+
y: 0
|
|
385
|
+
},
|
|
386
|
+
endPoint
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
function placeNodeLabels(node, globalOptions) {
|
|
391
|
+
for (const label of node.labels ?? []) {
|
|
392
|
+
const placement = String(getOption({
|
|
393
|
+
...globalOptions,
|
|
394
|
+
...node.layoutOptions,
|
|
395
|
+
...label.layoutOptions
|
|
396
|
+
}, "nodeLabels.placement") ?? "");
|
|
397
|
+
if (!placement) continue;
|
|
398
|
+
const width = label.width ?? 0;
|
|
399
|
+
const height = label.height ?? 0;
|
|
400
|
+
const nodeWidth = node.width ?? 0;
|
|
401
|
+
const nodeHeight = node.height ?? 0;
|
|
402
|
+
label.x = placement.includes("H_CENTER") ? (nodeWidth - width) / 2 : placement.includes("H_RIGHT") ? nodeWidth - width : 0;
|
|
403
|
+
if (placement.includes("OUTSIDE") && placement.includes("V_TOP")) label.y = -height - 5;
|
|
404
|
+
else if (placement.includes("OUTSIDE") && placement.includes("V_BOTTOM")) label.y = nodeHeight + 5;
|
|
405
|
+
else if (placement.includes("V_CENTER")) label.y = (nodeHeight - height) / 2;
|
|
406
|
+
else if (placement.includes("V_BOTTOM")) label.y = nodeHeight - height;
|
|
407
|
+
else label.y = 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//#endregion
|
|
412
|
+
export { ELK as default };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { LayoutConstraints } from "@statelyai/graph/layout";
|
|
2
|
+
import { EntityRect, Graph, GraphNode, GraphPatch, Point, VisualGraph } from "@statelyai/graph";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
type AnyGraph = Graph<unknown, unknown, unknown, unknown>;
|
|
6
|
+
type LayoutDirection = "up" | "down" | "left" | "right";
|
|
7
|
+
type LayoutScope = {
|
|
8
|
+
mode: "full";
|
|
9
|
+
} | {
|
|
10
|
+
mode: "incremental";
|
|
11
|
+
previous: VisualGraph;
|
|
12
|
+
} | {
|
|
13
|
+
mode: "partial";
|
|
14
|
+
previous: VisualGraph;
|
|
15
|
+
nodeIds: readonly string[];
|
|
16
|
+
} | {
|
|
17
|
+
mode: "route-only";
|
|
18
|
+
previous: VisualGraph;
|
|
19
|
+
edgeIds?: readonly string[];
|
|
20
|
+
};
|
|
21
|
+
interface LayoutDiagnostic {
|
|
22
|
+
severity: "info" | "warning" | "error";
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
entityIds?: readonly string[];
|
|
26
|
+
phase?: string;
|
|
27
|
+
}
|
|
28
|
+
interface LayoutPhaseMetrics {
|
|
29
|
+
id: string;
|
|
30
|
+
durationMs: number;
|
|
31
|
+
}
|
|
32
|
+
interface LayoutMetrics {
|
|
33
|
+
durationMs: number;
|
|
34
|
+
nodeCount: number;
|
|
35
|
+
edgeCount: number;
|
|
36
|
+
phases: readonly LayoutPhaseMetrics[];
|
|
37
|
+
}
|
|
38
|
+
interface LayoutCapabilities {
|
|
39
|
+
full: boolean;
|
|
40
|
+
incremental: boolean;
|
|
41
|
+
partial: boolean;
|
|
42
|
+
routeOnly: boolean;
|
|
43
|
+
hierarchy: boolean;
|
|
44
|
+
ports: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface LayoutAlgorithm<Options = unknown> {
|
|
47
|
+
readonly id: string;
|
|
48
|
+
readonly capabilities: LayoutCapabilities;
|
|
49
|
+
layout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options: Options, context: LayoutExecutionContext): VisualGraph<N, E, G, P> | Promise<VisualGraph<N, E, G, P>>;
|
|
50
|
+
}
|
|
51
|
+
interface LayoutExecutionContext {
|
|
52
|
+
readonly scope: LayoutScope;
|
|
53
|
+
readonly signal?: AbortSignal;
|
|
54
|
+
readonly diagnostics: LayoutDiagnostic[];
|
|
55
|
+
measurePhase<T>(id: string, run: () => T): T;
|
|
56
|
+
throwIfAborted(): void;
|
|
57
|
+
}
|
|
58
|
+
interface LayoutRequest<N = unknown, E = unknown, G = unknown, P = unknown, O = unknown> {
|
|
59
|
+
graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>;
|
|
60
|
+
algorithm?: string | LayoutAlgorithm<O>;
|
|
61
|
+
options?: O;
|
|
62
|
+
scope?: LayoutScope;
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
}
|
|
65
|
+
interface LayoutResult<N = unknown, E = unknown, G = unknown, P = unknown> {
|
|
66
|
+
graph: VisualGraph<N, E, G, P>;
|
|
67
|
+
patches: readonly GraphPatch<N, E>[];
|
|
68
|
+
diagnostics: readonly LayoutDiagnostic[];
|
|
69
|
+
metrics: LayoutMetrics;
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/layered/types.d.ts
|
|
73
|
+
interface NodeSize {
|
|
74
|
+
width: number;
|
|
75
|
+
height: number;
|
|
76
|
+
}
|
|
77
|
+
interface LayeredSpacing {
|
|
78
|
+
node: number;
|
|
79
|
+
layer: number;
|
|
80
|
+
}
|
|
81
|
+
interface LayoutPadding {
|
|
82
|
+
top: number;
|
|
83
|
+
right: number;
|
|
84
|
+
bottom: number;
|
|
85
|
+
left: number;
|
|
86
|
+
}
|
|
87
|
+
interface LayeredPhaseInput {
|
|
88
|
+
graph: Graph<unknown, unknown, unknown, unknown>;
|
|
89
|
+
sizes: ReadonlyMap<string, NodeSize>;
|
|
90
|
+
direction: LayoutDirection;
|
|
91
|
+
spacing: LayeredSpacing;
|
|
92
|
+
padding: LayoutPadding;
|
|
93
|
+
constrainedLayerByNodeId: ReadonlyMap<string, number>;
|
|
94
|
+
}
|
|
95
|
+
interface AcyclicOrientation {
|
|
96
|
+
reversedEdgeIds: ReadonlySet<string>;
|
|
97
|
+
}
|
|
98
|
+
interface LayerAssignment {
|
|
99
|
+
layerByNodeId: ReadonlyMap<string, number>;
|
|
100
|
+
}
|
|
101
|
+
interface LayerOrder {
|
|
102
|
+
layers: readonly (readonly string[])[];
|
|
103
|
+
}
|
|
104
|
+
interface NodePlacement {
|
|
105
|
+
rectByNodeId: ReadonlyMap<string, EntityRect>;
|
|
106
|
+
}
|
|
107
|
+
interface EdgeRoutes {
|
|
108
|
+
pointsByEdgeId: ReadonlyMap<string, readonly Point[]>;
|
|
109
|
+
}
|
|
110
|
+
type CycleBreaker = (input: LayeredPhaseInput) => AcyclicOrientation;
|
|
111
|
+
type LayerAssigner = (input: LayeredPhaseInput, orientation: AcyclicOrientation) => LayerAssignment;
|
|
112
|
+
type CrossingMinimizer = (input: LayeredPhaseInput, orientation: AcyclicOrientation, assignment: LayerAssignment) => LayerOrder;
|
|
113
|
+
type NodePlacer = (input: LayeredPhaseInput, order: LayerOrder) => NodePlacement;
|
|
114
|
+
type EdgeRouter = (input: LayeredPhaseInput, orientation: AcyclicOrientation, placement: NodePlacement) => EdgeRoutes;
|
|
115
|
+
interface LayeredStrategies {
|
|
116
|
+
breakCycles?: CycleBreaker;
|
|
117
|
+
assignLayers?: LayerAssigner;
|
|
118
|
+
minimizeCrossings?: CrossingMinimizer;
|
|
119
|
+
placeNodes?: NodePlacer;
|
|
120
|
+
routeEdges?: EdgeRouter;
|
|
121
|
+
}
|
|
122
|
+
interface LayeredLayoutOptions {
|
|
123
|
+
direction?: LayoutDirection;
|
|
124
|
+
spacing?: Partial<LayeredSpacing>;
|
|
125
|
+
padding?: number | Partial<LayoutPadding>;
|
|
126
|
+
constraints?: LayoutConstraints;
|
|
127
|
+
measure?: (node: GraphNode) => NodeSize;
|
|
128
|
+
crossingSweeps?: number;
|
|
129
|
+
strategies?: LayeredStrategies;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/layered/strategies.d.ts
|
|
133
|
+
declare const breakCyclesWithDepthFirstSearch: CycleBreaker;
|
|
134
|
+
declare const assignLayersByLongestPath: LayerAssigner;
|
|
135
|
+
declare function minimizeCrossingsWithBarycenter(sweeps?: number): CrossingMinimizer;
|
|
136
|
+
declare const placeNodesInLayers: NodePlacer;
|
|
137
|
+
declare const routeEdgesOrthogonally: EdgeRouter;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/layered/index.d.ts
|
|
140
|
+
/**
|
|
141
|
+
* Deterministic native layered layout for an `@statelyai/graph` graph.
|
|
142
|
+
*
|
|
143
|
+
* This initial vertical slice supports flat graphs, cycles, ports, self-loops,
|
|
144
|
+
* four directions, custom phase strategies, and orthogonal routes.
|
|
145
|
+
*/
|
|
146
|
+
declare function getLayeredLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: LayeredLayoutOptions): VisualGraph<N, E, G, P>;
|
|
147
|
+
declare const layeredAlgorithm: LayoutAlgorithm<LayeredLayoutOptions>;
|
|
148
|
+
//#endregion
|
|
149
|
+
export { LayoutMetrics as A, NodeSize as C, LayoutDiagnostic as D, LayoutCapabilities as E, LayoutRequest as M, LayoutResult as N, LayoutDirection as O, LayoutScope as P, NodePlacer as S, LayoutAlgorithm as T, LayeredPhaseInput as _, minimizeCrossingsWithBarycenter as a, LayoutPadding as b, AcyclicOrientation as c, EdgeRouter as d, EdgeRoutes as f, LayeredLayoutOptions as g, LayerOrder as h, breakCyclesWithDepthFirstSearch as i, LayoutPhaseMetrics as j, LayoutExecutionContext as k, CrossingMinimizer as l, LayerAssignment as m, layeredAlgorithm as n, placeNodesInLayers as o, LayerAssigner as p, assignLayersByLongestPath as r, routeEdgesOrthogonally as s, getLayeredLayout as t, CycleBreaker as u, LayeredSpacing as v, AnyGraph as w, NodePlacement as x, LayeredStrategies as y };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { A as LayoutMetrics, C as NodeSize, D as LayoutDiagnostic, E as LayoutCapabilities, M as LayoutRequest, N as LayoutResult, O as LayoutDirection, P as LayoutScope, S as NodePlacer, T as LayoutAlgorithm, _ as LayeredPhaseInput, a as minimizeCrossingsWithBarycenter, b as LayoutPadding, c as AcyclicOrientation, d as EdgeRouter, f as EdgeRoutes, g as LayeredLayoutOptions, h as LayerOrder, i as breakCyclesWithDepthFirstSearch, j as LayoutPhaseMetrics, k as LayoutExecutionContext, l as CrossingMinimizer, m as LayerAssignment, n as layeredAlgorithm, o as placeNodesInLayers, p as LayerAssigner, r as assignLayersByLongestPath, s as routeEdgesOrthogonally, t as getLayeredLayout, u as CycleBreaker, v as LayeredSpacing, w as AnyGraph, x as NodePlacement, y as LayeredStrategies } from "./index-v0P1Ake8.mjs";
|
|
2
|
+
import { LayoutOptions } from "@statelyai/graph/layout";
|
|
3
|
+
import { Graph, GraphNode, VisualGraph } from "@statelyai/graph";
|
|
4
|
+
|
|
5
|
+
//#region src/errors.d.ts
|
|
6
|
+
declare class LayoutError extends Error {
|
|
7
|
+
readonly code: string;
|
|
8
|
+
constructor(message: string, code: string);
|
|
9
|
+
}
|
|
10
|
+
declare class UnsupportedLayoutError extends LayoutError {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/box.d.ts
|
|
15
|
+
|
|
16
|
+
interface BoxLayoutOptions extends Pick<LayoutOptions, "direction" | "measure"> {
|
|
17
|
+
spacing?: number;
|
|
18
|
+
padding?: number | Partial<LayoutPadding>;
|
|
19
|
+
aspectRatio?: number;
|
|
20
|
+
interactive?: boolean;
|
|
21
|
+
expandNodes?: boolean;
|
|
22
|
+
priority?: (node: GraphNode) => number | undefined;
|
|
23
|
+
}
|
|
24
|
+
/** ELK Box SIMPLE packing translated onto `@statelyai/graph`. */
|
|
25
|
+
declare function getBoxLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: BoxLayoutOptions): VisualGraph<N, E, G, P>;
|
|
26
|
+
declare const boxAlgorithm: LayoutAlgorithm<BoxLayoutOptions>;
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/fixed.d.ts
|
|
29
|
+
interface FixedLayoutOptions extends Pick<LayoutOptions, "direction" | "measure"> {}
|
|
30
|
+
/** Preserve authored positions and routes while completing visual geometry. */
|
|
31
|
+
declare function getFixedLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: FixedLayoutOptions): VisualGraph<N, E, G, P>;
|
|
32
|
+
declare const fixedAlgorithm: LayoutAlgorithm<FixedLayoutOptions>;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/packing.d.ts
|
|
35
|
+
interface RectanglePackingLayoutOptions extends Pick<LayoutOptions, "direction" | "measure"> {
|
|
36
|
+
spacing?: number;
|
|
37
|
+
padding?: number | Partial<LayoutPadding>;
|
|
38
|
+
targetWidth?: number;
|
|
39
|
+
}
|
|
40
|
+
/** Deterministic shelf-based rectangle packing for `@statelyai/graph`. */
|
|
41
|
+
declare function getRectanglePackingLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: RectanglePackingLayoutOptions): VisualGraph<N, E, G, P>;
|
|
42
|
+
declare const rectanglePackingAlgorithm: LayoutAlgorithm<RectanglePackingLayoutOptions>;
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/random.d.ts
|
|
45
|
+
interface RandomLayoutOptions extends Pick<LayoutOptions, "direction" | "measure"> {
|
|
46
|
+
spacing?: number;
|
|
47
|
+
padding?: number | Partial<LayoutPadding>;
|
|
48
|
+
aspectRatio?: number;
|
|
49
|
+
seed?: number;
|
|
50
|
+
}
|
|
51
|
+
/** Seeded random distribution using Java's 48-bit `Random` sequence. */
|
|
52
|
+
declare function getRandomLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: RandomLayoutOptions): VisualGraph<N, E, G, P>;
|
|
53
|
+
declare const randomAlgorithm: LayoutAlgorithm<RandomLayoutOptions>;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/spore.d.ts
|
|
56
|
+
interface SporeLayoutOptions extends Pick<LayoutOptions, "direction" | "measure"> {
|
|
57
|
+
spacing?: number;
|
|
58
|
+
padding?: number | Partial<LayoutPadding>;
|
|
59
|
+
}
|
|
60
|
+
/** Compact an existing layout while preserving its relative directions. */
|
|
61
|
+
declare function getSporeCompactionLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: SporeLayoutOptions): VisualGraph<N, E, G, P>;
|
|
62
|
+
/** Remove overlap while preserving existing distances that already fit. */
|
|
63
|
+
declare function getSporeOverlapRemovalLayout<N, E, G, P>(graph: Graph<N, E, G, P> | VisualGraph<N, E, G, P>, options?: SporeLayoutOptions): VisualGraph<N, E, G, P>;
|
|
64
|
+
declare const sporeCompactionAlgorithm: LayoutAlgorithm<SporeLayoutOptions>;
|
|
65
|
+
declare const sporeOverlapRemovalAlgorithm: LayoutAlgorithm<SporeLayoutOptions>;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/layout.d.ts
|
|
68
|
+
/** Register or replace a layout algorithm for subsequent `getLayout` calls. */
|
|
69
|
+
declare function registerLayoutAlgorithm<O>(algorithm: LayoutAlgorithm<O>): () => void;
|
|
70
|
+
declare function getLayoutAlgorithm(id: string): LayoutAlgorithm<unknown> | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Run a registered or inline algorithm against an `@statelyai/graph` graph.
|
|
73
|
+
* The input is not mutated.
|
|
74
|
+
*/
|
|
75
|
+
declare function getLayout<N, E, G, P, O = unknown>(request: LayoutRequest<N, E, G, P, O>): Promise<LayoutResult<N, E, G, P>>;
|
|
76
|
+
//#endregion
|
|
77
|
+
export { type AcyclicOrientation, type AnyGraph, type BoxLayoutOptions, type CrossingMinimizer, type CycleBreaker, type EdgeRouter, type EdgeRoutes, type FixedLayoutOptions, type LayerAssigner, type LayerAssignment, type LayerOrder, type LayeredLayoutOptions, type LayeredPhaseInput, type LayeredSpacing, type LayeredStrategies, type LayoutAlgorithm, type LayoutCapabilities, type LayoutDiagnostic, type LayoutDirection, LayoutError, type LayoutExecutionContext, type LayoutMetrics, type LayoutPadding, type LayoutPhaseMetrics, type LayoutRequest, type LayoutResult, type LayoutScope, type NodePlacement, type NodePlacer, type NodeSize, type RandomLayoutOptions, type RectanglePackingLayoutOptions, type SporeLayoutOptions, UnsupportedLayoutError, assignLayersByLongestPath, boxAlgorithm, breakCyclesWithDepthFirstSearch, fixedAlgorithm, getBoxLayout, getFixedLayout, getLayeredLayout, getLayout, getLayoutAlgorithm, getRandomLayout, getRectanglePackingLayout, getSporeCompactionLayout, getSporeOverlapRemovalLayout, layeredAlgorithm, minimizeCrossingsWithBarycenter, placeNodesInLayers, randomAlgorithm, rectanglePackingAlgorithm, registerLayoutAlgorithm, routeEdgesOrthogonally, sporeCompactionAlgorithm, sporeOverlapRemovalAlgorithm };
|