@statelyai/layout 0.0.5 → 0.1.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/README.md +14 -0
- package/dist/elkjs/index.d.mts +44 -1
- package/dist/elkjs/index.mjs +269 -7
- package/dist/index.mjs +2 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-BYhTymAk.mjs → layered-CZs9YWVx.mjs} +120 -28
- package/dist/{spore-B504wOFR.mjs → spore-9N0W9dZn.mjs} +1 -1
- package/package.json +1 -1
- package/src/elkjs/index.ts +15 -1
- package/src/elkjs/statechart.ts +338 -0
- package/src/layered/index.ts +82 -28
- package/src/layered/strategies.ts +188 -13
package/README.md
CHANGED
|
@@ -71,6 +71,10 @@ The adapter accepts ELK JSON and option aliases, translates to
|
|
|
71
71
|
`@statelyai/graph`, runs native algorithms, and translates the result back.
|
|
72
72
|
Native algorithms never consume ELK JSON directly.
|
|
73
73
|
|
|
74
|
+
<!-- fixed-port placement and inline center-label guarantees from src/layered/index.ts and src/layered/strategies.ts -->
|
|
75
|
+
|
|
76
|
+
Fixed port sides constrain routes without collapsing fan-out targets onto each other. Port preferences cannot reintroduce cycles before layering. Inline center labels use reserved inter-rank space; dedicated label layers and route-track compaction retain their placement strategies. Inline self-loop labels reserve clearance on their assigned sides from both their owner and neighboring nodes. Hierarchy decomposition preserves native self-loop routes for ancestor-to-descendant edges. FIRST/FIRST_SEPARATE nodes may have self-loops.
|
|
77
|
+
|
|
74
78
|
Advanced layered settings use shorter names such as
|
|
75
79
|
`layering.strategy`, `spacing.edgeNode`, and `nodePlacement.strategy`.
|
|
76
80
|
`toElkLayeredOptions` and `fromElkLayeredOptionId` provide the exact one-to-one
|
|
@@ -166,3 +170,13 @@ workflow opens or updates a version pull request. Merging that pull request
|
|
|
166
170
|
publishes the package to npm and creates the GitHub release and tag.
|
|
167
171
|
|
|
168
172
|
Publishing uses npm Trusted Publishing through `.github/workflows/release.yml`.
|
|
173
|
+
|
|
174
|
+
## Statechart reading order
|
|
175
|
+
|
|
176
|
+
<!-- statechart policy API from src/elkjs/statechart.ts -->
|
|
177
|
+
|
|
178
|
+
The opt-in `layoutStatechart` export from `@statelyai/layout/elkjs` compiles
|
|
179
|
+
initial-state and preferred-path hints into scoped settings, then selects among
|
|
180
|
+
at most three fresh layout attempts. It returns all quality scores, including
|
|
181
|
+
remaining defects. See [statechart policies](docs/statechart-layout.md) for the
|
|
182
|
+
API, supported controls, tradeoffs and reproducible visual comparison.
|
package/dist/elkjs/index.d.mts
CHANGED
|
@@ -86,6 +86,49 @@ type LaidOutElkNode<T extends ElkNode> = Omit<T, "children" | "edges"> & Omit<El
|
|
|
86
86
|
edges?: Array<NonNullable<T["edges"]>[number] & ElkEdge>;
|
|
87
87
|
};
|
|
88
88
|
//#endregion
|
|
89
|
+
//#region src/elkjs/statechart.d.ts
|
|
90
|
+
interface StatechartScope {
|
|
91
|
+
/** Direct child of this container; never inferred from state names. */
|
|
92
|
+
initialNodeId?: ElkId;
|
|
93
|
+
/** Connected direct-child path. Explicit paths resolve branching ambiguity. */
|
|
94
|
+
preferredPath?: readonly ElkId[];
|
|
95
|
+
direction?: "UP" | "DOWN" | "LEFT" | "RIGHT";
|
|
96
|
+
}
|
|
97
|
+
interface StatechartLayoutOptions {
|
|
98
|
+
scopes: Readonly<Record<string, StatechartScope>>;
|
|
99
|
+
/** Total engine calls, including baseline; integer from 1 to 3. Default 3. */
|
|
100
|
+
maxAttempts?: number;
|
|
101
|
+
}
|
|
102
|
+
interface StatechartLayoutScore {
|
|
103
|
+
invalid: number;
|
|
104
|
+
overlaps: number;
|
|
105
|
+
pathOrder: number;
|
|
106
|
+
crossings: number;
|
|
107
|
+
bends: number;
|
|
108
|
+
routeLength: number;
|
|
109
|
+
}
|
|
110
|
+
interface StatechartLayoutPlan {
|
|
111
|
+
graph: ElkNode;
|
|
112
|
+
paths: Record<string, ElkId[]>;
|
|
113
|
+
commonExits: Record<string, ElkId[]>;
|
|
114
|
+
}
|
|
115
|
+
/** Compile semantic hints into settings implemented by this package's ELK adapter. */
|
|
116
|
+
declare function compileStatechartLayout(input: ElkNode, scopes: StatechartLayoutOptions["scopes"]): StatechartLayoutPlan;
|
|
117
|
+
/** Diagnostic objective, not a proof of geometric validity. Coordinates are container-relative. */
|
|
118
|
+
declare function scoreStatechartLayout(graph: ElkNode, paths?: StatechartLayoutPlan["paths"]): StatechartLayoutScore;
|
|
119
|
+
/** Each attempt starts from original input. No post-layout geometry edits. */
|
|
120
|
+
declare function layoutStatechart<T extends ElkNode>(input: T, options: StatechartLayoutOptions): Promise<{
|
|
121
|
+
attempts: {
|
|
122
|
+
kind: "baseline" | "policy" | "spacing";
|
|
123
|
+
score: StatechartLayoutScore;
|
|
124
|
+
}[];
|
|
125
|
+
paths: Record<string, ElkId[]>;
|
|
126
|
+
commonExits: Record<string, ElkId[]>;
|
|
127
|
+
graph: LaidOutElkNode<T>;
|
|
128
|
+
score: StatechartLayoutScore;
|
|
129
|
+
attempt: number;
|
|
130
|
+
}>;
|
|
131
|
+
//#endregion
|
|
89
132
|
//#region src/elkjs/index.d.ts
|
|
90
133
|
declare class ELK {
|
|
91
134
|
#private;
|
|
@@ -97,4 +140,4 @@ declare class ELK {
|
|
|
97
140
|
layout<T extends ElkNode>(graph: T, arguments_?: ElkLayoutArguments): Promise<LaidOutElkNode<T>>;
|
|
98
141
|
}
|
|
99
142
|
//#endregion
|
|
100
|
-
export { type ElkCommonDescription, type ElkConstructorArguments, type ElkEdge, type ElkEdgeSection, type ElkGraphElement, type ElkId, type ElkLabel, type ElkLayoutAlgorithmDescription, type ElkLayoutArguments, type ElkLayoutCategoryDescription, type ElkLayoutOptionDescription, type ElkLogging, type ElkNode, type ElkPoint, type ElkPort, type ElkShape, type LaidOutElkNode, ELK as default };
|
|
143
|
+
export { type ElkCommonDescription, type ElkConstructorArguments, type ElkEdge, type ElkEdgeSection, type ElkGraphElement, type ElkId, type ElkLabel, type ElkLayoutAlgorithmDescription, type ElkLayoutArguments, type ElkLayoutCategoryDescription, type ElkLayoutOptionDescription, type ElkLogging, type ElkNode, type ElkPoint, type ElkPort, type ElkShape, type LaidOutElkNode, type StatechartLayoutOptions, type StatechartLayoutPlan, type StatechartLayoutScore, type StatechartScope, compileStatechartLayout, ELK as default, layoutStatechart, scoreStatechartLayout };
|
package/dist/elkjs/index.mjs
CHANGED
|
@@ -1,7 +1,266 @@
|
|
|
1
|
-
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-
|
|
2
|
-
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-
|
|
1
|
+
import { o as elkLayeredOptionDefinitions, t as getLayeredLayout } from "../layered-CZs9YWVx.mjs";
|
|
2
|
+
import { a as getRandomLayout, f as getFixedLayout, n as getSporeOverlapRemovalLayout, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "../spore-9N0W9dZn.mjs";
|
|
3
3
|
import { createGraph } from "@statelyai/graph";
|
|
4
4
|
|
|
5
|
+
//#region src/elkjs/statechart.ts
|
|
6
|
+
function option(node, suffix) {
|
|
7
|
+
const settings = {
|
|
8
|
+
...node.properties,
|
|
9
|
+
...node.layoutOptions
|
|
10
|
+
};
|
|
11
|
+
return settings[suffix] ?? settings[`elk.${suffix}`] ?? settings[`org.eclipse.elk.${suffix}`];
|
|
12
|
+
}
|
|
13
|
+
const endpoint$1 = (edge, source) => source ? edge.sources?.[0] ?? edge.source : edge.targets?.[0] ?? edge.target;
|
|
14
|
+
/** Compile semantic hints into settings implemented by this package's ELK adapter. */
|
|
15
|
+
function compileStatechartLayout(input, scopes) {
|
|
16
|
+
const graph = structuredClone(input);
|
|
17
|
+
const paths = Object.create(null);
|
|
18
|
+
const commonExits = Object.create(null);
|
|
19
|
+
const containers = /* @__PURE__ */ new Map();
|
|
20
|
+
const edges = [];
|
|
21
|
+
const collect = (node) => {
|
|
22
|
+
if (node.id === void 0 || containers.has(String(node.id))) throw new Error("Statechart nodes require unique IDs");
|
|
23
|
+
containers.set(String(node.id), node);
|
|
24
|
+
edges.push(...node.edges ?? []);
|
|
25
|
+
node.children?.forEach(collect);
|
|
26
|
+
};
|
|
27
|
+
collect(graph);
|
|
28
|
+
for (const [id, scope] of Object.entries(scopes)) {
|
|
29
|
+
const container = containers.get(id);
|
|
30
|
+
if (!container) throw new Error(`Unknown statechart container: ${id}`);
|
|
31
|
+
const children = container.children ?? [];
|
|
32
|
+
const ids = new Set(children.map((node) => node.id));
|
|
33
|
+
const owner = /* @__PURE__ */ new Map();
|
|
34
|
+
const own = (node, childId) => {
|
|
35
|
+
owner.set(node.id, childId);
|
|
36
|
+
node.ports?.forEach((port) => owner.set(port.id, childId));
|
|
37
|
+
node.children?.forEach((descendant) => own(descendant, childId));
|
|
38
|
+
};
|
|
39
|
+
children.forEach((child) => own(child, child.id));
|
|
40
|
+
const localEdges = edges.filter((edge) => owner.has(endpoint$1(edge, true)) && owner.has(endpoint$1(edge, false)));
|
|
41
|
+
const successors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
42
|
+
const predecessors = new Map(children.map((node) => [node.id, /* @__PURE__ */ new Set()]));
|
|
43
|
+
for (const edge of localEdges) {
|
|
44
|
+
const source = owner.get(endpoint$1(edge, true));
|
|
45
|
+
const target = owner.get(endpoint$1(edge, false));
|
|
46
|
+
if (source === target) continue;
|
|
47
|
+
successors.get(source).add(target);
|
|
48
|
+
predecessors.get(target).add(source);
|
|
49
|
+
}
|
|
50
|
+
const exits = children.filter((node) => successors.get(node.id).size === 0 && predecessors.get(node.id).size >= 2).map((node) => node.id);
|
|
51
|
+
commonExits[id] = exits;
|
|
52
|
+
const path = [...scope.preferredPath ?? (scope.initialNodeId === void 0 ? [] : [scope.initialNodeId])];
|
|
53
|
+
if (new Set(path).size !== path.length || path.some((nodeId) => !ids.has(nodeId))) throw new Error(`Path must contain distinct direct children of ${id}`);
|
|
54
|
+
if (scope.initialNodeId !== void 0 && (!ids.has(scope.initialNodeId) || path[0] !== scope.initialNodeId)) throw new Error(`Path must start at initial child of ${id}`);
|
|
55
|
+
if (scope.preferredPath) {
|
|
56
|
+
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}`);
|
|
57
|
+
} else while (path.length) {
|
|
58
|
+
const next = [...successors.get(path.at(-1))].filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId));
|
|
59
|
+
if (next.length !== 1) break;
|
|
60
|
+
path.push(next[0]);
|
|
61
|
+
}
|
|
62
|
+
paths[id] = path;
|
|
63
|
+
container.children = [
|
|
64
|
+
...path,
|
|
65
|
+
...children.map((node) => node.id).filter((nodeId) => !path.includes(nodeId) && !exits.includes(nodeId)),
|
|
66
|
+
...exits.filter((nodeId) => !path.includes(nodeId))
|
|
67
|
+
].map((nodeId) => children.find((node) => node.id === nodeId));
|
|
68
|
+
container.layoutOptions = {
|
|
69
|
+
...path.length > 1 && option(container, "layered.cycleBreaking.strategy") === void 0 ? { "elk.layered.cycleBreaking.strategy": "MODEL_ORDER" } : {},
|
|
70
|
+
...path.length > 1 && option(container, "layered.feedbackEdges") === void 0 ? { "elk.layered.feedbackEdges": true } : {},
|
|
71
|
+
...container.layoutOptions,
|
|
72
|
+
...scope.direction ? { direction: scope.direction } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
graph,
|
|
77
|
+
paths,
|
|
78
|
+
commonExits
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
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;
|
|
82
|
+
const cross = (a, b, c) => (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
|
83
|
+
/** Diagnostic objective, not a proof of geometric validity. Coordinates are container-relative. */
|
|
84
|
+
function scoreStatechartLayout(graph, paths = {}) {
|
|
85
|
+
const score = {
|
|
86
|
+
invalid: 0,
|
|
87
|
+
overlaps: 0,
|
|
88
|
+
pathOrder: 0,
|
|
89
|
+
crossings: 0,
|
|
90
|
+
bends: 0,
|
|
91
|
+
routeLength: 0
|
|
92
|
+
};
|
|
93
|
+
const segments = [];
|
|
94
|
+
const boxes = [];
|
|
95
|
+
const rect = (shape, x, y) => {
|
|
96
|
+
if ([
|
|
97
|
+
shape.x,
|
|
98
|
+
shape.y,
|
|
99
|
+
shape.width,
|
|
100
|
+
shape.height
|
|
101
|
+
].some((value) => value === void 0 || !Number.isFinite(value)) || (shape.width ?? 0) < 0 || (shape.height ?? 0) < 0) score.invalid++;
|
|
102
|
+
return {
|
|
103
|
+
x: x + (shape.x ?? 0),
|
|
104
|
+
y: y + (shape.y ?? 0),
|
|
105
|
+
width: shape.width ?? 0,
|
|
106
|
+
height: shape.height ?? 0
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const visit = (node, x, y, ancestors) => {
|
|
110
|
+
const direction = String(option(node, "direction") ?? "RIGHT").toUpperCase();
|
|
111
|
+
const horizontal = direction === "RIGHT" || direction === "LEFT";
|
|
112
|
+
const sign = direction === "LEFT" || direction === "UP" ? -1 : 1;
|
|
113
|
+
const path = paths[String(node.id)] ?? [];
|
|
114
|
+
for (let i = 1; i < path.length; i++) {
|
|
115
|
+
const a = node.children?.find((child) => child.id === path[i - 1]);
|
|
116
|
+
const b = node.children?.find((child) => child.id === path[i]);
|
|
117
|
+
if (!a || !b || sign * (horizontal ? b.x - a.x : b.y - a.y) <= 0) score.pathOrder++;
|
|
118
|
+
}
|
|
119
|
+
for (const child of node.children ?? []) {
|
|
120
|
+
if (String(option(child, "noLayout")).toLowerCase() === "true") continue;
|
|
121
|
+
const box = rect(child, x, y);
|
|
122
|
+
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++;
|
|
123
|
+
boxes.push({
|
|
124
|
+
rect: box,
|
|
125
|
+
ancestors,
|
|
126
|
+
node: child
|
|
127
|
+
});
|
|
128
|
+
visit(child, box.x, box.y, [...ancestors, child]);
|
|
129
|
+
}
|
|
130
|
+
for (const edge of node.edges ?? []) {
|
|
131
|
+
if (String(option(edge, "noLayout")).toLowerCase() === "true") continue;
|
|
132
|
+
if (!edge.sections?.length) score.invalid++;
|
|
133
|
+
for (const label of edge.labels ?? []) {
|
|
134
|
+
if (String(option(label, "noLayout")).toLowerCase() === "true") continue;
|
|
135
|
+
boxes.push({
|
|
136
|
+
rect: rect(label, x, y),
|
|
137
|
+
ancestors
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
for (const section of edge.sections ?? []) {
|
|
141
|
+
const points = [
|
|
142
|
+
section.startPoint,
|
|
143
|
+
...section.bendPoints ?? [],
|
|
144
|
+
section.endPoint
|
|
145
|
+
];
|
|
146
|
+
score.bends += section.bendPoints?.length ?? 0;
|
|
147
|
+
for (const point of points) if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) score.invalid++;
|
|
148
|
+
for (let i = 1; i < points.length; i++) {
|
|
149
|
+
const a = {
|
|
150
|
+
x: x + points[i - 1].x,
|
|
151
|
+
y: y + points[i - 1].y
|
|
152
|
+
};
|
|
153
|
+
const b = {
|
|
154
|
+
x: x + points[i].x,
|
|
155
|
+
y: y + points[i].y
|
|
156
|
+
};
|
|
157
|
+
score.routeLength += Math.hypot(b.x - a.x, b.y - a.y);
|
|
158
|
+
segments.push({
|
|
159
|
+
a,
|
|
160
|
+
b,
|
|
161
|
+
edge
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
visit(graph, 0, 0, [graph]);
|
|
168
|
+
for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) {
|
|
169
|
+
const a = boxes[i], b = boxes[j];
|
|
170
|
+
if (a.node && b.ancestors.includes(a.node) || b.node && a.ancestors.includes(b.node)) continue;
|
|
171
|
+
if (intersects(a.rect, b.rect)) score.overlaps++;
|
|
172
|
+
}
|
|
173
|
+
for (let i = 0; i < segments.length; i++) for (let j = i + 1; j < segments.length; j++) {
|
|
174
|
+
const a = segments[i], b = segments[j];
|
|
175
|
+
if (a.edge === b.edge) continue;
|
|
176
|
+
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++;
|
|
177
|
+
}
|
|
178
|
+
if (!Number.isFinite(score.routeLength)) score.routeLength = Number.MAX_VALUE;
|
|
179
|
+
return score;
|
|
180
|
+
}
|
|
181
|
+
const objectives = [
|
|
182
|
+
"invalid",
|
|
183
|
+
"overlaps",
|
|
184
|
+
"pathOrder",
|
|
185
|
+
"crossings",
|
|
186
|
+
"bends",
|
|
187
|
+
"routeLength"
|
|
188
|
+
];
|
|
189
|
+
function better(a, b) {
|
|
190
|
+
for (const key of objectives) if (a[key] !== b[key]) return a[key] < b[key];
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
/** Each attempt starts from original input. No post-layout geometry edits. */
|
|
194
|
+
async function layoutStatechart(input, options) {
|
|
195
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
196
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 3) throw new RangeError("maxAttempts must be an integer from 1 to 3");
|
|
197
|
+
const plan = compileStatechartLayout(input, options.scopes);
|
|
198
|
+
const attempts = [];
|
|
199
|
+
let selected;
|
|
200
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
201
|
+
const candidate = structuredClone(attempt === 0 ? input : plan.graph);
|
|
202
|
+
const configure = (node) => {
|
|
203
|
+
const scope = Object.hasOwn(options.scopes, String(node.id)) ? options.scopes[String(node.id)] : void 0;
|
|
204
|
+
if (scope?.direction) node.layoutOptions = {
|
|
205
|
+
...node.layoutOptions,
|
|
206
|
+
direction: scope.direction
|
|
207
|
+
};
|
|
208
|
+
if (attempt === 2 && scope) node.layoutOptions = {
|
|
209
|
+
...node.layoutOptions,
|
|
210
|
+
"spacing.nodeNode": Math.max(40, Number(option(node, "spacing.nodeNode") ?? 20) * 1.5),
|
|
211
|
+
"layered.spacing.nodeNodeBetweenLayers": Math.max(60, Number(option(node, "layered.spacing.nodeNodeBetweenLayers") ?? 20) * 1.5)
|
|
212
|
+
};
|
|
213
|
+
if (attempt === 2 && scope) {
|
|
214
|
+
const groups = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const edge of node.edges ?? []) {
|
|
216
|
+
if (!edge.labels?.length) continue;
|
|
217
|
+
const key = JSON.stringify([endpoint$1(edge, true), endpoint$1(edge, false)]);
|
|
218
|
+
const group = groups.get(key) ?? [];
|
|
219
|
+
group.push(edge);
|
|
220
|
+
groups.set(key, group);
|
|
221
|
+
}
|
|
222
|
+
for (const group of groups.values()) for (let i = 1; i < Math.min(group.length, 3); i++) {
|
|
223
|
+
const edge = group[i];
|
|
224
|
+
if (option(edge, "edgeLabels.placement") === void 0 && edge.labels.every((label) => option(label, "edgeLabels.placement") === void 0)) {
|
|
225
|
+
const placement = i === 1 ? "HEAD" : "TAIL";
|
|
226
|
+
edge.layoutOptions = {
|
|
227
|
+
...edge.layoutOptions,
|
|
228
|
+
"elk.edgeLabels.placement": placement
|
|
229
|
+
};
|
|
230
|
+
edge.labels = edge.labels.map((label) => ({
|
|
231
|
+
...label,
|
|
232
|
+
layoutOptions: {
|
|
233
|
+
...label.layoutOptions,
|
|
234
|
+
"elk.edgeLabels.placement": placement
|
|
235
|
+
}
|
|
236
|
+
}));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
node.children?.forEach(configure);
|
|
241
|
+
};
|
|
242
|
+
configure(candidate);
|
|
243
|
+
const graph = await new ELK().layout(candidate);
|
|
244
|
+
const score = scoreStatechartLayout(graph, plan.paths);
|
|
245
|
+
attempts.push({
|
|
246
|
+
kind: attempt === 0 ? "baseline" : attempt === 1 ? "policy" : "spacing",
|
|
247
|
+
score
|
|
248
|
+
});
|
|
249
|
+
if (!selected || better(score, selected.score)) selected = {
|
|
250
|
+
graph,
|
|
251
|
+
score,
|
|
252
|
+
attempt
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...selected,
|
|
257
|
+
attempts,
|
|
258
|
+
paths: plan.paths,
|
|
259
|
+
commonExits: plan.commonExits
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
//#endregion
|
|
5
264
|
//#region src/elkjs/index.ts
|
|
6
265
|
function isLayoutEdgeLabel(label) {
|
|
7
266
|
return getBooleanOption(label.layoutOptions ?? {}, "noLayout") !== true && (Boolean(label.text) || label.width !== void 0 || label.height !== void 0);
|
|
@@ -344,6 +603,7 @@ var ELK = class {
|
|
|
344
603
|
const graph_ = toGraph(graph, layoutOptions);
|
|
345
604
|
const layerConstraintByNodeId = new Map((graph.children ?? []).map((node) => [String(node.id), String(getOption(node.layoutOptions ?? {}, "layered.layering.layerConstraint") ?? getOption(node.layoutOptions ?? {}, "layerConstraint") ?? "NONE")]));
|
|
346
605
|
if (algorithm === "layered" && graph_.edges.some((edge) => {
|
|
606
|
+
if (edge.sourceId === edge.targetId) return false;
|
|
347
607
|
const sourceConstraint = layerConstraintByNodeId.get(edge.sourceId);
|
|
348
608
|
const targetConstraint = layerConstraintByNodeId.get(edge.targetId);
|
|
349
609
|
return (sourceConstraint === "FIRST" || sourceConstraint === "FIRST_SEPARATE") && (targetConstraint === "FIRST" || targetConstraint === "FIRST_SEPARATE");
|
|
@@ -408,7 +668,7 @@ var ELK = class {
|
|
|
408
668
|
if (hierarchyRestorations.length > 0) {
|
|
409
669
|
const direction = getDirection(layoutOptions);
|
|
410
670
|
const horizontal = direction === "right" || direction === "left";
|
|
411
|
-
const cross = (point) => horizontal ? point.y : point.x;
|
|
671
|
+
const cross$1 = (point) => horizontal ? point.y : point.x;
|
|
412
672
|
const nodeSpacing = getNumberOption(layoutOptions, "spacing.nodeNode") ?? 20;
|
|
413
673
|
const shiftsByOutsideId = /* @__PURE__ */ new Map();
|
|
414
674
|
for (const restoration of hierarchyRestorations) {
|
|
@@ -418,7 +678,7 @@ var ELK = class {
|
|
|
418
678
|
const originalTargetId = String(restoration.targets?.[0] ?? restoration.target);
|
|
419
679
|
const sourceInside = !laidOut.nodes.some((node) => node.id === originalSourceId);
|
|
420
680
|
const outsideId = sourceInside ? originalTargetId : originalSourceId;
|
|
421
|
-
const delta = sourceInside ? cross(route[0]) - cross(route.at(-1)) : cross(route.at(-1)) - cross(route[0]);
|
|
681
|
+
const delta = sourceInside ? cross$1(route[0]) - cross$1(route.at(-1)) : cross$1(route.at(-1)) - cross$1(route[0]);
|
|
422
682
|
const candidates = shiftsByOutsideId.get(outsideId) ?? [];
|
|
423
683
|
candidates.push(delta);
|
|
424
684
|
shiftsByOutsideId.set(outsideId, candidates);
|
|
@@ -468,11 +728,13 @@ var ELK = class {
|
|
|
468
728
|
}
|
|
469
729
|
const edgeNodeSpacing = getNumberOption(layoutOptions, "spacing.edgeNodeBetweenLayers") ?? 10;
|
|
470
730
|
for (const restoration of hierarchyRestorations) {
|
|
471
|
-
const
|
|
731
|
+
const nativeEdge = laidOut.edges.find((edge) => edge.id === String(restoration.edge.id));
|
|
732
|
+
const route = nativeEdge?.points;
|
|
472
733
|
if (!route || route.length < 2) continue;
|
|
473
734
|
const start = route[0];
|
|
474
735
|
const end = route.at(-1);
|
|
475
|
-
if (
|
|
736
|
+
if (nativeEdge.sourceId === nativeEdge.targetId) continue;
|
|
737
|
+
if (Math.abs(cross$1(start) - cross$1(end)) < 1e-9) {
|
|
476
738
|
route.splice(1, route.length - 2);
|
|
477
739
|
continue;
|
|
478
740
|
}
|
|
@@ -1220,4 +1482,4 @@ function placePortLabels(node, globalOptions) {
|
|
|
1220
1482
|
}
|
|
1221
1483
|
|
|
1222
1484
|
//#endregion
|
|
1223
|
-
export { ELK as default };
|
|
1485
|
+
export { compileStatechartLayout, ELK as default, layoutStatechart, scoreStatechartLayout };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-
|
|
2
|
-
import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-
|
|
1
|
+
import { A as minimizeCrossingsWithBarycenter, B as LayoutError, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, V as UnsupportedLayoutError, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "./layered-CZs9YWVx.mjs";
|
|
2
|
+
import { a as getRandomLayout, c as rectanglePackingAlgorithm, d as fixedAlgorithm, f as getFixedLayout, i as sporeOverlapRemovalAlgorithm, l as boxAlgorithm, n as getSporeOverlapRemovalLayout, o as randomAlgorithm, r as sporeCompactionAlgorithm, s as getRectanglePackingLayout, t as getSporeCompactionLayout, u as getBoxLayout } from "./spore-9N0W9dZn.mjs";
|
|
3
3
|
import { getGraphIssues } from "@statelyai/graph";
|
|
4
4
|
|
|
5
5
|
//#region src/layout.ts
|
package/dist/layered/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-
|
|
1
|
+
import { A as minimizeCrossingsWithBarycenter, C as breakCyclesGreedilyByModelOrder, D as breakCyclesWithModelOrderDepthFirstSearch, E as breakCyclesWithModelOrderBreadthFirstSearch, I as routeEdgesOrthogonally, L as routeEdgesWithPolylines, M as minimizeCrossingsWithModelOrder, N as placeNodesInLayers, P as placeNodesInteractively, R as routeEdgesWithSplines, S as breakCyclesGreedily, T as breakCyclesWithDepthFirstSearch, _ as assignLayersInteractively, a as elkLayeredEnumValues, b as breakCyclesByStronglyConnectedConnectivity, c as placeNodesWithLinearSegments, d as assignLayersWithMinWidth, f as assignLayersWithNetworkSimplex, g as assignLayersByLongestPathToSink, h as assignLayersByLongestPath, i as toElkLayeredOptions, j as minimizeCrossingsWithMedian, k as minimizeCrossingsInteractively, l as placeNodesWithBrandesKoepf, m as assignLayersByDepthFirstModelOrder, n as layeredAlgorithm, o as elkLayeredOptionDefinitions, p as assignLayersByBreadthFirstModelOrder, r as fromElkLayeredOptionId, s as placeNodesWithNetworkSimplex, t as getLayeredLayout, u as assignLayersWithStretchWidth, v as assignLayersWithCoffmanGraham, w as breakCyclesInteractively, x as breakCyclesByStronglyConnectedNodeType, y as breakCyclesByModelOrder } from "../layered-CZs9YWVx.mjs";
|
|
2
2
|
|
|
3
3
|
export { assignLayersByBreadthFirstModelOrder, assignLayersByDepthFirstModelOrder, assignLayersByLongestPath, assignLayersByLongestPathToSink, assignLayersInteractively, assignLayersWithCoffmanGraham, assignLayersWithMinWidth, assignLayersWithNetworkSimplex, assignLayersWithStretchWidth, breakCyclesByModelOrder, breakCyclesByStronglyConnectedConnectivity, breakCyclesByStronglyConnectedNodeType, breakCyclesGreedily, breakCyclesGreedilyByModelOrder, breakCyclesInteractively, breakCyclesWithDepthFirstSearch, breakCyclesWithModelOrderBreadthFirstSearch, breakCyclesWithModelOrderDepthFirstSearch, elkLayeredEnumValues, elkLayeredOptionDefinitions, fromElkLayeredOptionId, getLayeredLayout, layeredAlgorithm, minimizeCrossingsInteractively, minimizeCrossingsWithBarycenter, minimizeCrossingsWithMedian, minimizeCrossingsWithModelOrder, placeNodesInLayers, placeNodesInteractively, placeNodesWithBrandesKoepf, placeNodesWithLinearSegments, placeNodesWithNetworkSimplex, routeEdgesOrthogonally, routeEdgesWithPolylines, routeEdgesWithSplines, toElkLayeredOptions };
|