@statelyai/layout 0.0.5 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -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
|
@@ -392,7 +392,8 @@ function addDepthFirstBackEdges(input, initialReversedEdgeIds, targetOrder) {
|
|
|
392
392
|
const targetId = reversed ? edge.sourceId : edge.targetId;
|
|
393
393
|
if (sourceId === targetId) continue;
|
|
394
394
|
const targetState = state.get(targetId);
|
|
395
|
-
if (targetState === "active") reversedEdgeIds.
|
|
395
|
+
if (targetState === "active") if (reversed) reversedEdgeIds.delete(edge.id);
|
|
396
|
+
else reversedEdgeIds.add(edge.id);
|
|
396
397
|
else if (targetState === void 0) {
|
|
397
398
|
state.set(targetId, "active");
|
|
398
399
|
stack.push({
|
|
@@ -664,7 +665,7 @@ function applyLayerConstraintOrientation(input, orientation) {
|
|
|
664
665
|
if (reversedEdgeIds.has(edge.id)) reversedEdgeIds.delete(edge.id);
|
|
665
666
|
else reversedEdgeIds.add(edge.id);
|
|
666
667
|
}
|
|
667
|
-
return { reversedEdgeIds };
|
|
668
|
+
return { reversedEdgeIds: addDepthFirstBackEdges(input, reversedEdgeIds, "model") };
|
|
668
669
|
}
|
|
669
670
|
/** Keep activated ELK partitions in ascending, contiguous layer blocks. */
|
|
670
671
|
function applyPartitions(input, assignment) {
|
|
@@ -1730,7 +1731,7 @@ function applyDirectionCongruency(input, order) {
|
|
|
1730
1731
|
return { layers: order.layers.map((layer) => [...layer].reverse()) };
|
|
1731
1732
|
}
|
|
1732
1733
|
/** ELK post-placement one-dimensional graph compaction. */
|
|
1733
|
-
function applyPostCompaction(input, placement, routes) {
|
|
1734
|
+
function applyPostCompaction(input, placement, routes, labelEdges = input.graph.edges) {
|
|
1734
1735
|
const strategy = input.settings["compaction.postCompaction.strategy"] ?? "NONE";
|
|
1735
1736
|
input.settings["compaction.postCompaction.constraints"];
|
|
1736
1737
|
if (strategy === "NONE") return placement;
|
|
@@ -1778,7 +1779,15 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1778
1779
|
}
|
|
1779
1780
|
}
|
|
1780
1781
|
const verticalSpacing = (left, right) => left.kind === "node" && right.kind === "node" ? input.spacing.node : left.kind === "segment" && right.kind === "segment" && left.edgeId === right.edgeId ? 1 : Number(input.settings[left.kind === "segment" && right.kind === "segment" ? "spacing.edgeEdge" : "spacing.edgeNode"] ?? 10);
|
|
1781
|
-
const
|
|
1782
|
+
const inlineLabelSpaceByPair = /* @__PURE__ */ new Map();
|
|
1783
|
+
if (strategy !== "EDGE_LENGTH" && (input.direction === "left" || input.direction === "right")) for (const edge of labelEdges) {
|
|
1784
|
+
const settings = input.edgeSettings?.(edge);
|
|
1785
|
+
if (edge.sourceId === edge.targetId || settings?.["edgeLabels.inline"] !== true || (settings["edgeLabels.placement"] ?? "CENTER") !== "CENTER") continue;
|
|
1786
|
+
const key = [edge.sourceId, edge.targetId].sort().join("\0");
|
|
1787
|
+
inlineLabelSpaceByPair.set(key, Math.max(inlineLabelSpaceByPair.get(key) ?? 0, (edge.width ?? 0) + 2 * input.spacing.layer));
|
|
1788
|
+
}
|
|
1789
|
+
const labelSpace = (left, right) => left.nodeId !== void 0 && right.nodeId !== void 0 ? inlineLabelSpaceByPair.get([left.nodeId, right.nodeId].sort().join("\0")) ?? 0 : 0;
|
|
1790
|
+
const horizontalSpacing = (left, right) => left.kind === "node" && right.kind === "node" ? Math.max(input.spacing.node, labelSpace(left, right)) : left.kind === "segment" && right.kind === "segment" && left.edgeId === right.edgeId ? 0 : Number(input.settings[left.kind === "segment" && right.kind === "segment" ? "spacing.edgeEdge" : "spacing.edgeNode"] ?? 10);
|
|
1782
1791
|
const centerLabeledEdgeIds = new Set(input.graph.edges.filter((edge) => (edge.width ?? 0) > 0 && (input.edgeSettings?.(edge)?.["edgeLabels.placement"] ?? "CENTER") === "CENTER").map((edge) => edge.id));
|
|
1783
1792
|
const labelFlowLocked = new Set(compactables.filter((item) => item.kind === "segment" && item.edgeId !== void 0 && centerLabeledEdgeIds.has(item.edgeId)).map((item) => item.id));
|
|
1784
1793
|
const compact$1 = (direction, locked = /* @__PURE__ */ new Set()) => {
|
|
@@ -1794,7 +1803,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1794
1803
|
if (left === right) continue;
|
|
1795
1804
|
const ordered = right.x > left.x || right.x === left.x && left.width < right.width;
|
|
1796
1805
|
const verticalCollision = right.y + right.height + verticalSpacing(left, right) > left.y + 1e-9 && right.y < left.y + left.height + verticalSpacing(left, right) - 1e-9;
|
|
1797
|
-
if (!ordered || !verticalCollision) continue;
|
|
1806
|
+
if (!ordered || !verticalCollision && labelSpace(left, right) === 0) continue;
|
|
1798
1807
|
constraints.get(left.id).push(right.id);
|
|
1799
1808
|
incoming.set(right.id, (incoming.get(right.id) ?? 0) + 1);
|
|
1800
1809
|
}
|
|
@@ -1884,7 +1893,7 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1884
1893
|
const itemByNodeId = new Map(compactables.flatMap((item) => item.kind === "node" && item.nodeId ? [[item.nodeId, item]] : []));
|
|
1885
1894
|
const edgeNodeSpacing = Number(input.settings["spacing.edgeNodeBetweenLayers"] ?? 10);
|
|
1886
1895
|
for (const edge of input.graph.edges) {
|
|
1887
|
-
if (!centerLabeledEdgeIds.has(edge.id)) continue;
|
|
1896
|
+
if (!centerLabeledEdgeIds.has(edge.id) || strategy !== "EDGE_LENGTH" && input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true) continue;
|
|
1888
1897
|
const source = itemByNodeId.get(edge.sourceId);
|
|
1889
1898
|
const target = itemByNodeId.get(edge.targetId);
|
|
1890
1899
|
const originalEndpoints = originalEndpointsByEdgeId.get(edge.id);
|
|
@@ -1933,7 +1942,13 @@ function applyPostCompaction(input, placement, routes) {
|
|
|
1933
1942
|
...originalEndpoints.end,
|
|
1934
1943
|
x: originalEndpoints.end.x + (nodeDeltaById.get(edge.targetId) ?? 0)
|
|
1935
1944
|
};
|
|
1936
|
-
if (routes.outsideFeedbackEdgeIds?.has(edgeId)) {
|
|
1945
|
+
if (edge.sourceId === edge.targetId && routes.outsideFeedbackEdgeIds?.has(edgeId)) {
|
|
1946
|
+
const delta = nodeDeltaById.get(edge.sourceId) ?? 0;
|
|
1947
|
+
for (let index = 1; index + 1 < points.length; index++) points[index] = {
|
|
1948
|
+
...points[index],
|
|
1949
|
+
x: points[index].x + delta
|
|
1950
|
+
};
|
|
1951
|
+
} else if (routes.outsideFeedbackEdgeIds?.has(edgeId)) {
|
|
1937
1952
|
const horizontal = input.direction === "left" || input.direction === "right";
|
|
1938
1953
|
const startDelta = points[0].x - originalEndpoints.start.x;
|
|
1939
1954
|
const endDelta = points.at(-1).x - originalEndpoints.end.x;
|
|
@@ -2350,6 +2365,34 @@ function routeEdges(style) {
|
|
|
2350
2365
|
});
|
|
2351
2366
|
}
|
|
2352
2367
|
}
|
|
2368
|
+
const loopSide = (distribution, index, edge) => {
|
|
2369
|
+
const node = edge ? nodeById.get(edge.sourceId) : void 0;
|
|
2370
|
+
const sourcePort = node?.ports?.find((port) => port.name === edge?.sourcePort);
|
|
2371
|
+
const targetPort = node?.ports?.find((port) => port.name === edge?.targetPort);
|
|
2372
|
+
const sourceSide = node && sourcePort ? input.portSettings?.(sourcePort, node)?.["port.side"] : void 0;
|
|
2373
|
+
const targetSide = node && targetPort ? input.portSettings?.(targetPort, node)?.["port.side"] : void 0;
|
|
2374
|
+
if (sourceSide && sourceSide === targetSide && [
|
|
2375
|
+
"NORTH",
|
|
2376
|
+
"SOUTH",
|
|
2377
|
+
"EAST",
|
|
2378
|
+
"WEST"
|
|
2379
|
+
].includes(sourceSide)) return sourceSide;
|
|
2380
|
+
return distribution === "EQUALLY" ? [
|
|
2381
|
+
"NORTH",
|
|
2382
|
+
"SOUTH",
|
|
2383
|
+
"EAST",
|
|
2384
|
+
"WEST"
|
|
2385
|
+
][index % 4] : distribution === "NORTH_SOUTH" && index % 2 === 1 ? "SOUTH" : "NORTH";
|
|
2386
|
+
};
|
|
2387
|
+
const loopDistance = (loops, edge, side, ordering) => {
|
|
2388
|
+
const axis = side === "NORTH" || side === "SOUTH" ? "height" : "width";
|
|
2389
|
+
const ordered = ordering === "REVERSE_STACKED" ? [...loops].reverse() : loops;
|
|
2390
|
+
const index = ordered.indexOf(edge);
|
|
2391
|
+
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
2392
|
+
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
2393
|
+
const size = (candidate) => input.edgeSettings?.(candidate)?.["edgeLabels.inline"] === true ? candidate[axis] ?? 0 : 0;
|
|
2394
|
+
return spacing * (ordering === "SEQUENCED" ? 1 : index + 1) + ordered.slice(0, index).reduce((extent$1, candidate) => extent$1 + (size(candidate) > 0 ? size(candidate) + labelSpacing : 0), 0) + (size(edge) > 0 ? size(edge) / 2 + labelSpacing : 0);
|
|
2395
|
+
};
|
|
2353
2396
|
const northReserveByLayer = /* @__PURE__ */ new Map();
|
|
2354
2397
|
for (const [id, loops] of selfLoopsByNodeId) {
|
|
2355
2398
|
const rect = mutableRects.get(id);
|
|
@@ -2379,6 +2422,41 @@ function routeEdges(style) {
|
|
|
2379
2422
|
y: candidateRect.y + reserve
|
|
2380
2423
|
});
|
|
2381
2424
|
}
|
|
2425
|
+
if (style === "ORTHOGONAL") for (const axis of ["x", "y"]) {
|
|
2426
|
+
const size = axis === "x" ? "width" : "height";
|
|
2427
|
+
const reservations = /* @__PURE__ */ new Map();
|
|
2428
|
+
for (const [id, loops] of selfLoopsByNodeId) {
|
|
2429
|
+
const rect = mutableRects.get(id);
|
|
2430
|
+
const node = nodeById.get(id);
|
|
2431
|
+
if (!rect || !node) continue;
|
|
2432
|
+
const settings = input.nodeSettings?.(node);
|
|
2433
|
+
const distribution = settings?.["edgeRouting.selfLoopDistribution"] ?? "NORTH";
|
|
2434
|
+
const ordering = settings?.["edgeRouting.selfLoopOrdering"] ?? "STACKED";
|
|
2435
|
+
for (const [index, edge] of loops.entries()) {
|
|
2436
|
+
if (input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true) continue;
|
|
2437
|
+
const side = loopSide(distribution, index, edge);
|
|
2438
|
+
if (axis === "y" !== (side === "NORTH" || side === "SOUTH")) continue;
|
|
2439
|
+
const extent$1 = loopDistance(loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side), edge, side, ordering) + (edge[size] ?? 0) / 2;
|
|
2440
|
+
const negative = side === "NORTH" || side === "WEST";
|
|
2441
|
+
const boundary = rect[axis] + (negative ? 0 : rect[size]);
|
|
2442
|
+
const neighboringBounds = [...mutableRects.entries()].flatMap(([otherId, other]) => {
|
|
2443
|
+
if (otherId === id) return [];
|
|
2444
|
+
const bound = other[axis] + (negative ? other[size] : 0);
|
|
2445
|
+
return negative ? bound <= boundary ? [bound] : [] : bound >= boundary ? [bound] : [];
|
|
2446
|
+
});
|
|
2447
|
+
const existingGap = neighboringBounds.length === 0 ? 0 : negative ? boundary - Math.max(...neighboringBounds) : Math.min(...neighboringBounds) - boundary;
|
|
2448
|
+
const required = Math.max(0, extent$1 + (neighboringBounds.length > 0 ? input.spacing.node : 0) - existingGap);
|
|
2449
|
+
reservations.set(boundary, Math.max(reservations.get(boundary) ?? 0, required));
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
for (const [id, rect] of mutableRects) {
|
|
2453
|
+
const shift = [...reservations].reduce((sum, [boundary, extent$1]) => sum + (rect[axis] >= boundary - 1e-9 ? extent$1 : 0), 0);
|
|
2454
|
+
if (shift > 0) mutableRects.set(id, {
|
|
2455
|
+
...rect,
|
|
2456
|
+
[axis]: rect[axis] + shift
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2382
2460
|
const edgeLabelSideSelection = input.settings["edgeLabels.sideSelection"] ?? "SMART_DOWN";
|
|
2383
2461
|
if (edgeLabelSideSelection === "ALWAYS_UP" || edgeLabelSideSelection === "SMART_UP" || edgeLabelSideSelection === "DIRECTION_UP") {
|
|
2384
2462
|
let crossShift = 0;
|
|
@@ -2671,7 +2749,7 @@ function routeEdges(style) {
|
|
|
2671
2749
|
const slots = slotsByGap[layerNo] ?? 0;
|
|
2672
2750
|
const routesNearTarget = candidatesByGap[layerNo]?.some((candidate) => hasZeroFixedSideTarget(candidate.edge) && candidatesByGap[layerNo].filter(({ edge }) => edge.targetId === candidate.edge.targetId).length > candidatesByGap[layerNo].filter(({ edge }) => edge.sourceId === candidate.edge.sourceId).length);
|
|
2673
2751
|
const preservedGap = (existingGapByLayer[layerNo] ?? input.spacing.layer) - (routesNearTarget ? edgeEdgeSpacing : 0);
|
|
2674
|
-
const gapSpacing = slots === 0 ? existingGapByLayer[layerNo] ?? input.spacing.layer : Math.max(preservesNodeFlexibilityGap || (labelExtraByGap[layerNo] ?? 0) > 0 ? preservedGap : input.spacing.layer, 2 * edgeNodeSpacing + Math.max(0, slots - 1) * edgeEdgeSpacing);
|
|
2752
|
+
const gapSpacing = slots === 0 ? existingGapByLayer[layerNo] ?? input.spacing.layer : Math.max(preservesNodeFlexibilityGap || (labelExtraByGap[layerNo] ?? 0) > 0 || [...selfLoopsByNodeId].some(([id, loops]) => (flowLayerByNodeId.get(id) === layerNo || flowLayerByNodeId.get(id) === layerNo + 1) && loops.some((edge) => input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true)) ? preservedGap : input.spacing.layer, 2 * edgeNodeSpacing + Math.max(0, slots - 1) * edgeEdgeSpacing);
|
|
2675
2753
|
nextStart = bounds.end + gapSpacing;
|
|
2676
2754
|
}
|
|
2677
2755
|
implicitEndpoints = implicitEdgeEndpoints(input, placement, orientation);
|
|
@@ -2935,7 +3013,7 @@ function routeEdges(style) {
|
|
|
2935
3013
|
const constraints = String(input.nodeSettings?.(node)?.portConstraints ?? "UNDEFINED");
|
|
2936
3014
|
return constraints !== "UNDEFINED" && constraints !== "FREE";
|
|
2937
3015
|
};
|
|
2938
|
-
const fixedSideFeedback = style === "ORTHOGONAL" && !sameSideSelfLoop && (hasFixedPortSide(source) && feedbackSourcePortSide === sourceAwaySide || hasFixedPortSide(target) && feedbackTargetPortSide === targetAwaySide);
|
|
3016
|
+
const fixedSideFeedback = style === "ORTHOGONAL" && !sameSideSelfLoop && (source.id !== target.id || input.edgeSettings?.(edge)?.["edgeLabels.inline"] !== true) && (hasFixedPortSide(source) && feedbackSourcePortSide === sourceAwaySide || hasFixedPortSide(target) && feedbackTargetPortSide === targetAwaySide);
|
|
2939
3017
|
if (input.settings.feedbackEdges === true && reversedEdge || fixedSideFeedback) {
|
|
2940
3018
|
if (fixedSideFeedback) outsideFeedbackEdgeIds.add(edge.id);
|
|
2941
3019
|
const crossSpacing = Number(input.settings["spacing.edgeNode"] ?? 10);
|
|
@@ -3066,6 +3144,7 @@ function routeEdges(style) {
|
|
|
3066
3144
|
continue;
|
|
3067
3145
|
}
|
|
3068
3146
|
if (source.id === target.id) {
|
|
3147
|
+
if (style === "ORTHOGONAL" && input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true) outsideFeedbackEdgeIds.add(edge.id);
|
|
3069
3148
|
const loops = selfLoopsByNodeId.get(source.id) ?? [edge];
|
|
3070
3149
|
const loopIndex = loops.indexOf(edge);
|
|
3071
3150
|
const spacing = Number(input.settings["spacing.nodeSelfLoop"] ?? 10);
|
|
@@ -3093,7 +3172,7 @@ function routeEdges(style) {
|
|
|
3093
3172
|
});
|
|
3094
3173
|
const sameSideLoopIndex = sameSideLoops.findIndex((candidate) => candidate.id === edge.id);
|
|
3095
3174
|
const labelSpacing = Number(input.settings["spacing.edgeLabel"] ?? 2);
|
|
3096
|
-
const trackDistance = spacing + sameSideLoops.slice(0, Math.max(0, sameSideLoopIndex)).reduce((extent$1, candidate) => extent$1 + (side === "EAST" || side === "WEST" ? candidate.width ?? 0 : candidate.height ?? 0) + labelSpacing, 0);
|
|
3175
|
+
const trackDistance = spacing + sameSideLoops.slice(0, Math.max(0, sameSideLoopIndex)).reduce((extent$1, candidate) => extent$1 + (side === "EAST" || side === "WEST" ? candidate.width ?? 0 : candidate.height ?? 0) + labelSpacing, 0) + ((side === "NORTH" || side === "SOUTH") && input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? (edge.height ?? 0) / 2 + labelSpacing : 0);
|
|
3097
3176
|
if (side === "EAST" || side === "WEST") {
|
|
3098
3177
|
const nodeRects = [...placement.rectByNodeId.values()];
|
|
3099
3178
|
const minimumNodeX = Math.min(...nodeRects.map((rect) => rect.x));
|
|
@@ -3146,7 +3225,6 @@ function routeEdges(style) {
|
|
|
3146
3225
|
const denominator = countOnSide * 2 + 1;
|
|
3147
3226
|
const sequenced = ordering === "SEQUENCED";
|
|
3148
3227
|
const nestingIndex = ordering === "REVERSE_STACKED" ? countOnSide - indexOnSide - 1 : indexOnSide;
|
|
3149
|
-
const trackIndex = sequenced ? 1 : nestingIndex + 1;
|
|
3150
3228
|
let startRatio;
|
|
3151
3229
|
let endRatio;
|
|
3152
3230
|
if (sequenced) if (side === "NORTH") {
|
|
@@ -3163,7 +3241,8 @@ function routeEdges(style) {
|
|
|
3163
3241
|
startRatio = (countOnSide + nestingIndex + 1) / denominator;
|
|
3164
3242
|
endRatio = (countOnSide - nestingIndex) / denominator;
|
|
3165
3243
|
}
|
|
3166
|
-
const
|
|
3244
|
+
const distance = loopDistance(loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side), edge, side, ordering);
|
|
3245
|
+
const y = side === "NORTH" ? sourceRect.y - distance : sourceRect.y + sourceRect.height + distance;
|
|
3167
3246
|
const start$2 = {
|
|
3168
3247
|
x: sourceRect.x + sourceRect.width * startRatio,
|
|
3169
3248
|
y: side === "NORTH" ? sourceRect.y : sourceRect.y + sourceRect.height
|
|
@@ -3192,7 +3271,8 @@ function routeEdges(style) {
|
|
|
3192
3271
|
const secondRatio = sequenced ? 1 / denominator : 2 / denominator;
|
|
3193
3272
|
const startRatio = side === "EAST" ? firstRatio : secondRatio;
|
|
3194
3273
|
const endRatio = side === "EAST" ? secondRatio : firstRatio;
|
|
3195
|
-
const
|
|
3274
|
+
const distance = input.edgeSettings?.(edge)?.["edgeLabels.inline"] === true ? loopDistance(loops.filter((candidate, i) => loopSide(distribution, i, candidate) === side), edge, side, ordering) : spacing * (indexOnSide + 1);
|
|
3275
|
+
const x = side === "EAST" ? sourceRect.x + sourceRect.width + distance : sourceRect.x - distance;
|
|
3196
3276
|
const start$2 = {
|
|
3197
3277
|
x: side === "EAST" ? sourceRect.x + sourceRect.width : sourceRect.x,
|
|
3198
3278
|
y: sourceRect.y + sourceRect.height * startRatio
|
|
@@ -8762,30 +8842,37 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8762
8842
|
}
|
|
8763
8843
|
{
|
|
8764
8844
|
const horizontal = direction === "right" || direction === "left";
|
|
8845
|
+
const targetsBySource = /* @__PURE__ */ new Map();
|
|
8846
|
+
for (const edge of graph.edges) {
|
|
8847
|
+
if (edge.sourceId === edge.targetId) continue;
|
|
8848
|
+
const targets = targetsBySource.get(edge.sourceId) ?? /* @__PURE__ */ new Set();
|
|
8849
|
+
targets.add(edge.targetId);
|
|
8850
|
+
targetsBySource.set(edge.sourceId, targets);
|
|
8851
|
+
}
|
|
8765
8852
|
for (const edge of graph.edges) {
|
|
8766
8853
|
if (edge.sourcePort === void 0) continue;
|
|
8767
8854
|
const source = graph.nodes.find((node) => node.id === edge.sourceId);
|
|
8768
8855
|
const targetRect = mutableRects.get(edge.targetId);
|
|
8769
8856
|
const sourceRect = mutableRects.get(edge.sourceId);
|
|
8770
|
-
const port = source?.ports?.find((candidate) => candidate.name === edge.sourcePort);
|
|
8857
|
+
const port = source?.ports?.find((candidate$1) => candidate$1.name === edge.sourcePort);
|
|
8771
8858
|
if (!source || !sourceRect || !targetRect || !port) continue;
|
|
8772
8859
|
const side = input.portSettings?.(port, source)?.["port.side"];
|
|
8773
|
-
if (
|
|
8860
|
+
if (edge.sourceId === edge.targetId || (targetsBySource.get(edge.sourceId)?.size ?? 0) > 1) continue;
|
|
8861
|
+
const candidate = horizontal && side === "NORTH" ? {
|
|
8774
8862
|
...targetRect,
|
|
8775
8863
|
y: sourceRect.y - (port.height ?? 0) - targetRect.height
|
|
8776
|
-
}
|
|
8777
|
-
else if (horizontal && side === "SOUTH") mutableRects.set(edge.targetId, {
|
|
8864
|
+
} : horizontal && side === "SOUTH" ? {
|
|
8778
8865
|
...targetRect,
|
|
8779
8866
|
y: sourceRect.y + sourceRect.height + (port.height ?? 0)
|
|
8780
|
-
}
|
|
8781
|
-
else if (!horizontal && side === "WEST") mutableRects.set(edge.targetId, {
|
|
8867
|
+
} : !horizontal && side === "WEST" ? {
|
|
8782
8868
|
...targetRect,
|
|
8783
8869
|
x: sourceRect.x - (port.width ?? 0) - targetRect.width
|
|
8784
|
-
}
|
|
8785
|
-
else if (!horizontal && side === "EAST") mutableRects.set(edge.targetId, {
|
|
8870
|
+
} : !horizontal && side === "EAST" ? {
|
|
8786
8871
|
...targetRect,
|
|
8787
8872
|
x: sourceRect.x + sourceRect.width + (port.width ?? 0)
|
|
8788
|
-
}
|
|
8873
|
+
} : void 0;
|
|
8874
|
+
if (!candidate || [...mutableRects].some(([id, other]) => id !== edge.targetId && other.width > 0 && other.height > 0 && candidate.x < other.x + other.width && candidate.x + candidate.width > other.x && candidate.y < other.y + other.height && candidate.y + candidate.height > other.y)) continue;
|
|
8875
|
+
mutableRects.set(edge.targetId, candidate);
|
|
8789
8876
|
}
|
|
8790
8877
|
}
|
|
8791
8878
|
measure("port-margin-normalization", () => normalizePlacementForPortExtents(expanded.input, placement, order, expanded.orientation));
|
|
@@ -8814,7 +8901,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
8814
8901
|
const edgeRouting = options.settings?.edgeRouting ?? "ORTHOGONAL";
|
|
8815
8902
|
const edgeRouter = options.strategies?.routeEdges ?? (edgeRouting === "POLYLINE" ? routeEdgesWithPolylines : edgeRouting === "SPLINES" ? routeEdgesWithSplines : routeEdgesOrthogonally);
|
|
8816
8903
|
let expandedRoutes = measure("edge-routing", () => edgeRouter(expanded.input, expanded.orientation, placement));
|
|
8817
|
-
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes));
|
|
8904
|
+
measure("post-compaction", () => applyPostCompaction(expanded.input, placement, expandedRoutes, graph.edges));
|
|
8818
8905
|
const antiparallelLabelPositions = /* @__PURE__ */ new Map();
|
|
8819
8906
|
const outerAntiparallelLabelIds = /* @__PURE__ */ new Set();
|
|
8820
8907
|
const parallelLabelPositions = /* @__PURE__ */ new Map();
|
|
@@ -9319,10 +9406,10 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
9319
9406
|
const constraints = node ? options.nodeSettings?.(node)?.portConstraints : void 0;
|
|
9320
9407
|
return constraints === void 0 || constraints === "UNDEFINED" || constraints === "FREE";
|
|
9321
9408
|
};
|
|
9322
|
-
const flexibleFeedbackLabel = labelPlacement === "CENTER" && inlineLabel && expanded.orientation.reversedEdgeIds.has(edge.id) && hasFlexiblePorts(sourceNode) && hasFlexiblePorts(targetNode) && sourceRect !== void 0 && targetRect !== void 0;
|
|
9409
|
+
const flexibleFeedbackLabel = labelPlacement === "CENTER" && inlineLabel && edge.sourceId !== edge.targetId && expanded.orientation.reversedEdgeIds.has(edge.id) && hasFlexiblePorts(sourceNode) && hasFlexiblePorts(targetNode) && sourceRect !== void 0 && targetRect !== void 0;
|
|
9323
9410
|
const [beforeFlowRect, afterFlowRect] = sourceRect && targetRect ? horizontal ? sourceRect.x <= targetRect.x ? [sourceRect, targetRect] : [targetRect, sourceRect] : sourceRect.y <= targetRect.y ? [sourceRect, targetRect] : [targetRect, sourceRect] : [void 0, void 0];
|
|
9324
9411
|
const antiparallelLabelPosition = antiparallelLabelPositions.get(edge.id);
|
|
9325
|
-
const
|
|
9412
|
+
const outerAntiparallelLabelPosition = (() => {
|
|
9326
9413
|
if (!outerAntiparallelLabelIds.has(edge.id)) return void 0;
|
|
9327
9414
|
const edgeNodeSpacing$1 = Number(options.settings?.["spacing.edgeNode"] ?? 10);
|
|
9328
9415
|
if (horizontal) {
|
|
@@ -9347,9 +9434,14 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
9347
9434
|
x: Math.max(...blockers.map((rect) => rect.x + rect.width)) + edgeNodeSpacing$1,
|
|
9348
9435
|
y: y$1
|
|
9349
9436
|
};
|
|
9350
|
-
})()
|
|
9351
|
-
const
|
|
9352
|
-
const
|
|
9437
|
+
})();
|
|
9438
|
+
const centerFlowPosition = options.settings?.["compaction.postCompaction.strategy"] !== "EDGE_LENGTH" && labelDummyRect === void 0 && edgeRouting === "ORTHOGONAL" && inlineLabel && labelPlacement === "CENTER" && edge.sourceId !== edge.targetId && beforeFlowRect && afterFlowRect && (horizontal ? afterFlowRect.x - beforeFlowRect.x - beforeFlowRect.width >= width : afterFlowRect.y - beforeFlowRect.y - beforeFlowRect.height >= height) ? horizontal ? (beforeFlowRect.x + beforeFlowRect.width + afterFlowRect.x - width) / 2 : (beforeFlowRect.y + beforeFlowRect.height + afterFlowRect.y - height) / 2 - edgeThickness / 2 : void 0;
|
|
9439
|
+
const explicitLabelPosition = (edgeRouting === "ORTHOGONAL" && labelPlacement === "CENTER" && inlineLabel && edge.sourceId === edge.targetId && points.length === 4 ? {
|
|
9440
|
+
x: (points[1].x + points[2].x - width) / 2,
|
|
9441
|
+
y: (points[1].y + points[2].y - height) / 2
|
|
9442
|
+
} : void 0) ?? outerAntiparallelLabelPosition ?? antiparallelLabelPosition ?? parallelLabelPositions.get(edge.id);
|
|
9443
|
+
const x = explicitLabelPosition ? explicitLabelPosition.x : flexibleFeedbackLabel ? horizontal ? (beforeFlowRect.x + beforeFlowRect.width + afterFlowRect.x - width) / 2 : targetRect.x + (targetRect.width - width) / 2 : centerFlowPosition !== void 0 && horizontal ? centerFlowPosition : labelPlacement === "CENTER" && horizontal && labelDummyRect ? labelDummyRect.x : routeX;
|
|
9444
|
+
const y = explicitLabelPosition ? explicitLabelPosition.y : flexibleFeedbackLabel ? horizontal ? targetRect.y + (targetRect.height - height) / 2 : (beforeFlowRect.y + beforeFlowRect.height + afterFlowRect.y - height) / 2 : centerFlowPosition !== void 0 && !horizontal ? centerFlowPosition : labelPlacement === "CENTER" && !horizontal && labelDummyRect ? labelDummyRect.y : routeY;
|
|
9353
9445
|
return {
|
|
9354
9446
|
...edge,
|
|
9355
9447
|
x,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-
|
|
1
|
+
import { F as placePorts, O as getPolylineMidpoint, z as JavaRandom } from "./layered-CZs9YWVx.mjs";
|
|
2
2
|
import { getNodeSize } from "@statelyai/graph/layout";
|
|
3
3
|
|
|
4
4
|
//#region src/fixed.ts
|
package/package.json
CHANGED
package/src/elkjs/index.ts
CHANGED
|
@@ -510,6 +510,7 @@ export default class ELK {
|
|
|
510
510
|
if (
|
|
511
511
|
algorithm === "layered" &&
|
|
512
512
|
graph_.edges.some((edge) => {
|
|
513
|
+
if (edge.sourceId === edge.targetId) return false;
|
|
513
514
|
const sourceConstraint = layerConstraintByNodeId.get(edge.sourceId);
|
|
514
515
|
const targetConstraint = layerConstraintByNodeId.get(edge.targetId);
|
|
515
516
|
return (
|
|
@@ -722,10 +723,15 @@ export default class ELK {
|
|
|
722
723
|
}
|
|
723
724
|
const edgeNodeSpacing = getNumberOption(layoutOptions, "spacing.edgeNodeBetweenLayers") ?? 10;
|
|
724
725
|
for (const restoration of hierarchyRestorations) {
|
|
725
|
-
const
|
|
726
|
+
const nativeEdge = laidOut.edges.find((edge) => edge.id === String(restoration.edge.id));
|
|
727
|
+
const route = nativeEdge?.points;
|
|
726
728
|
if (!route || route.length < 2) continue;
|
|
727
729
|
const start = route[0]!;
|
|
728
730
|
const end = route.at(-1)!;
|
|
731
|
+
// Decomposed ancestor/descendant edges are native self-loops. Preserve
|
|
732
|
+
// their reserved exterior route and label placement instead of treating
|
|
733
|
+
// equal endpoint coordinates as a straight inter-node connection.
|
|
734
|
+
if (nativeEdge.sourceId === nativeEdge.targetId) continue;
|
|
729
735
|
if (Math.abs(cross(start) - cross(end)) < 1e-9) {
|
|
730
736
|
route.splice(1, route.length - 2);
|
|
731
737
|
continue;
|
|
@@ -1824,3 +1830,11 @@ function placePortLabels(node: ElkNode, globalOptions: Readonly<Record<string, u
|
|
|
1824
1830
|
}
|
|
1825
1831
|
}
|
|
1826
1832
|
}
|
|
1833
|
+
|
|
1834
|
+
export { compileStatechartLayout, layoutStatechart, scoreStatechartLayout } from "./statechart";
|
|
1835
|
+
export type {
|
|
1836
|
+
StatechartScope,
|
|
1837
|
+
StatechartLayoutOptions,
|
|
1838
|
+
StatechartLayoutScore,
|
|
1839
|
+
StatechartLayoutPlan,
|
|
1840
|
+
} from "./statechart";
|