@statelyai/layout 0.2.0 → 0.3.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 +71 -2
- package/dist/elkjs/index.cjs +3 -2
- package/dist/elkjs/index.d.cts +11 -2
- package/dist/elkjs/index.d.mts +11 -2
- package/dist/elkjs/index.mjs +3 -3
- package/dist/elkjs/main.cjs +2 -2
- package/dist/elkjs/main.d.cts +2 -2
- package/dist/elkjs/main.d.mts +2 -2
- package/dist/elkjs/main.mjs +2 -2
- package/dist/elkjs/worker-api.cjs +1 -1
- package/dist/elkjs/worker-api.d.cts +2 -2
- package/dist/elkjs/worker-api.d.mts +2 -2
- package/dist/elkjs/worker-api.mjs +1 -1
- package/dist/elkjs/worker.cjs +2 -2
- package/dist/elkjs/worker.mjs +2 -2
- package/dist/{elkjs-CFlfkEBz.mjs → elkjs-DaHMISnQ.mjs} +152 -3
- package/dist/{elkjs-BVnDwnBQ.cjs → elkjs-dMghiiWW.cjs} +157 -2
- package/dist/index-BEA_pzeT.d.cts +72 -0
- package/dist/{index-BSH2bloA.d.mts → index-D9dhxrRl.d.cts} +35 -3
- package/dist/index-DNTLRsDl.d.mts +72 -0
- package/dist/{index-CG712jzV.d.cts → index-DNixtE3h.d.mts} +35 -3
- package/dist/index.cjs +36 -9
- package/dist/index.d.cts +4 -3
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +4 -3
- package/dist/layered/index.cjs +1 -1
- package/dist/layered/index.d.cts +3 -2
- package/dist/layered/index.d.mts +3 -2
- package/dist/layered/index.mjs +1 -1
- package/dist/{layered-Cf9Rn8l7.cjs → layered-B2buJqMf.cjs} +417 -469
- package/dist/{layered-Bz2nWmVp.mjs → layered-DGFTeQSl.mjs} +391 -431
- package/dist/{layout-engine-WbC0YjjT.mjs → layout-engine-Cs5tatA6.mjs} +1 -1
- package/dist/{layout-engine-BNex9jxg.cjs → layout-engine-Dn1PiFwP.cjs} +1 -1
- package/dist/routing/index.cjs +28 -0
- package/dist/routing/index.d.cts +3 -0
- package/dist/routing/index.d.mts +3 -0
- package/dist/routing/index.mjs +3 -0
- package/dist/routing-A2hrK9MP.mjs +2396 -0
- package/dist/routing-ujEP7Qh6.cjs +2606 -0
- package/dist/types-CT2akdK0.d.mts +133 -0
- package/dist/types-uxPlGRof.d.cts +133 -0
- package/dist/{worker-client-DAT_z2OR.d.mts → worker-client-BwGGRD5y.d.mts} +1 -1
- package/dist/{worker-client-DVhklF6A.d.cts → worker-client-D4zNyPEI.d.cts} +1 -1
- package/package.json +7 -2
- package/src/authoring/coordinates.ts +10 -2
- package/src/elkjs/index.ts +5 -3
- package/src/elkjs/routes.ts +147 -0
- package/src/index.ts +6 -0
- package/src/layered/compound.ts +417 -0
- package/src/layered/index.ts +27 -102
- package/src/layered/types.ts +21 -0
- package/src/routing/adapters.ts +111 -0
- package/src/routing/algorithms.ts +676 -0
- package/src/routing/coordination.ts +86 -0
- package/src/routing/engine.ts +473 -0
- package/src/routing/index.ts +68 -0
- package/src/routing/layout-cache.ts +65 -0
- package/src/routing/model.ts +218 -0
- package/src/routing/path.ts +324 -0
- package/src/routing/persistent.ts +117 -0
- package/src/routing/search.ts +276 -0
- package/src/routing/spatial.ts +78 -0
- package/src/routing/types.ts +146 -0
- /package/dist/{public-types-B8rAALJq.d.mts → public-types-9NCPoqbp.d.mts} +0 -0
- /package/dist/{public-types-CVB0hXGk.d.cts → public-types-BcRZoJE3.d.cts} +0 -0
- /package/dist/{worker-client-DkwS4sHx.mjs → worker-client-BNjwT1AJ.mjs} +0 -0
- /package/dist/{worker-client-D3HMK33r.cjs → worker-client-D4oEvhAl.cjs} +0 -0
|
@@ -1,21 +1,6 @@
|
|
|
1
|
+
const require_routing = require('./routing-ujEP7Qh6.cjs');
|
|
1
2
|
let _statelyai_graph_layout = require("@statelyai/graph/layout");
|
|
2
3
|
|
|
3
|
-
//#region src/errors.ts
|
|
4
|
-
var LayoutError = class extends Error {
|
|
5
|
-
constructor(message, code) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.name = "LayoutError";
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
var UnsupportedLayoutError = class extends LayoutError {
|
|
12
|
-
constructor(message) {
|
|
13
|
-
super(message, "UNSUPPORTED_LAYOUT");
|
|
14
|
-
this.name = "UnsupportedLayoutError";
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
//#endregion
|
|
19
4
|
//#region src/java-random.ts
|
|
20
5
|
/** Java's 48-bit `java.util.Random`, used by ELK for seeded tie-breaking. */
|
|
21
6
|
var JavaRandom = class JavaRandom {
|
|
@@ -597,7 +582,7 @@ const assignLayersByLongestPath = (input, orientation) => {
|
|
|
597
582
|
if (sourceId === targetId) continue;
|
|
598
583
|
const sourceConstraint = input.constrainedLayerByNodeId.get(sourceId);
|
|
599
584
|
const targetConstraint = input.constrainedLayerByNodeId.get(targetId);
|
|
600
|
-
if (sourceConstraint !== void 0 && targetConstraint !== void 0 && targetConstraint <= sourceConstraint) throw new LayoutError(`Layer constraints conflict on edge ${edge.id}`, "UNSATISFIABLE_CONSTRAINTS");
|
|
585
|
+
if (sourceConstraint !== void 0 && targetConstraint !== void 0 && targetConstraint <= sourceConstraint) throw new require_routing.LayoutError(`Layer constraints conflict on edge ${edge.id}`, "UNSATISFIABLE_CONSTRAINTS");
|
|
601
586
|
successors.get(sourceId)?.push(targetId);
|
|
602
587
|
indegree.set(targetId, (indegree.get(targetId) ?? 0) + 1);
|
|
603
588
|
}
|
|
@@ -608,7 +593,7 @@ const assignLayersByLongestPath = (input, orientation) => {
|
|
|
608
593
|
const sourceLayer = layerByNodeId.get(sourceId) ?? 0;
|
|
609
594
|
for (const targetId of successors.get(sourceId) ?? []) {
|
|
610
595
|
const targetConstraint = input.constrainedLayerByNodeId.get(targetId);
|
|
611
|
-
if (targetConstraint !== void 0 && targetConstraint < sourceLayer + 1) throw new LayoutError(`Layer constraint conflicts at node ${targetId}`, "UNSATISFIABLE_CONSTRAINTS");
|
|
596
|
+
if (targetConstraint !== void 0 && targetConstraint < sourceLayer + 1) throw new require_routing.LayoutError(`Layer constraint conflicts at node ${targetId}`, "UNSATISFIABLE_CONSTRAINTS");
|
|
612
597
|
layerByNodeId.set(targetId, targetConstraint ?? Math.max(layerByNodeId.get(targetId) ?? 0, sourceLayer + 1));
|
|
613
598
|
const nextIndegree = (indegree.get(targetId) ?? 1) - 1;
|
|
614
599
|
indegree.set(targetId, nextIndegree);
|
|
@@ -2229,15 +2214,15 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2229
2214
|
const hypernode = nodeSettings?.hypernode;
|
|
2230
2215
|
const unzipping = (input.settings["layerUnzipping.strategy"] ?? "NONE") === "ALTERNATING";
|
|
2231
2216
|
const crossingStrategy = input.settings["crossingMinimization.strategy"] ?? "LAYER_SWEEP";
|
|
2232
|
-
const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint
|
|
2233
|
-
const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint
|
|
2234
|
-
const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint
|
|
2217
|
+
const interactiveTargetOrder = crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "target") && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
|
|
2218
|
+
const verticalNoneTargetOrder = !horizontal && crossingStrategy === "NONE" && input.settings["layering.strategy"] === "STRETCH_WIDTH" && entries.every(({ endpoint }) => endpoint === "target") && !input.graph.edges.some(({ sourceId }) => sourceId === nodeId) && entries.some(({ edge }) => edge.sourceId.startsWith("__layout_dummy:"));
|
|
2219
|
+
const interactiveLeftSourceOrder = crossingStrategy === "INTERACTIVE" && input.direction === "left" && entries.length >= 3 && entries.every(({ endpoint }) => endpoint === "source") ? [...entries].sort((left, right) => {
|
|
2235
2220
|
const leftRect = placement.rectByNodeId.get(left.edge.targetId);
|
|
2236
2221
|
const rightRect = placement.rectByNodeId.get(right.edge.targetId);
|
|
2237
2222
|
const leftCross = (leftRect?.y ?? 0) + (leftRect?.height ?? 0) / 2;
|
|
2238
2223
|
return (rightRect?.y ?? 0) + (rightRect?.height ?? 0) / 2 - leftCross || (edgeModelOrder.get(left.edge.id) ?? 0) - (edgeModelOrder.get(right.edge.id) ?? 0);
|
|
2239
2224
|
}).map(({ edge }) => edge.id) : void 0;
|
|
2240
|
-
const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint
|
|
2225
|
+
const interactiveForwardLongEdgeSource = input.direction !== "left" && crossingStrategy === "INTERACTIVE" && entries.every(({ endpoint }) => endpoint === "source") && entries.some(({ edge }) => edge.targetId.startsWith("__layout_dummy:"));
|
|
2241
2226
|
const layerOrdered = unzipping || interactiveTargetOrder || verticalNoneTargetOrder || input.settings.hierarchyHandling !== "INCLUDE_CHILDREN" && (crossingStrategy === "LAYER_SWEEP" || crossingStrategy === "MEDIAN_LAYER_SWEEP");
|
|
2242
2227
|
entries.sort((left, right) => {
|
|
2243
2228
|
if (interactiveLeftSourceOrder) {
|
|
@@ -2290,8 +2275,8 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2290
2275
|
const constraints = nodeSettings?.portConstraints;
|
|
2291
2276
|
const portSpacing = Number(input.settings["spacing.portPort"] ?? 10);
|
|
2292
2277
|
const fixedWrapAnchor = nodeId.startsWith("__layout_dummy:wrap:");
|
|
2293
|
-
entries.forEach(({ edge, endpoint
|
|
2294
|
-
const ordinal = endpoint
|
|
2278
|
+
entries.forEach(({ edge, endpoint }, index) => {
|
|
2279
|
+
const ordinal = endpoint === "target" && input.settings["crossingMinimization.strategy"] !== "INTERACTIVE" && !layerOrdered && !(input.settings.directionCongruency === "ROTATION" && (input.direction === "down" || input.direction === "left")) ? entries.length - index : index + 1;
|
|
2295
2280
|
const orderedIndex = ordinal - 1;
|
|
2296
2281
|
const crossPosition = mergeEdges || hypernode === true ? axisSize / 2 : constraints === "FIXED_RATIO" || constraints === "FIXED_POS" ? 0 : alignment === "BEGIN" ? axisStart + orderedIndex * portSpacing : alignment === "END" ? axisStart + availableAxisSize - (entries.length - orderedIndex - 1) * portSpacing : alignment === "CENTER" ? axisStart + (availableAxisSize - (entries.length - 1) * portSpacing) / 2 + orderedIndex * portSpacing : alignment === "JUSTIFIED" ? entries.length === 1 ? axisStart + availableAxisSize / 2 : axisStart + orderedIndex * availableAxisSize / (entries.length - 1) : axisStart + availableAxisSize * ordinal / (entries.length + 1);
|
|
2297
2282
|
let point = horizontal ? {
|
|
@@ -2312,7 +2297,7 @@ function implicitEdgeEndpoints(input, placement, orientation) {
|
|
|
2312
2297
|
source: point,
|
|
2313
2298
|
target: point
|
|
2314
2299
|
};
|
|
2315
|
-
pair[endpoint
|
|
2300
|
+
pair[endpoint] = point;
|
|
2316
2301
|
result.set(edge.id, pair);
|
|
2317
2302
|
});
|
|
2318
2303
|
}
|
|
@@ -2497,12 +2482,12 @@ function routeEdges(style) {
|
|
|
2497
2482
|
flowLayerByNodeId.set(interval.id, flowLayers.length - 1);
|
|
2498
2483
|
}
|
|
2499
2484
|
const increasing = input.direction === "right" || input.direction === "down";
|
|
2500
|
-
const flowPortProtrusion = (edge, endpoint
|
|
2501
|
-
const node = nodeById.get(endpoint
|
|
2502
|
-
const portName = endpoint
|
|
2485
|
+
const flowPortProtrusion = (edge, endpoint) => {
|
|
2486
|
+
const node = nodeById.get(endpoint === "source" ? edge.sourceId : edge.targetId);
|
|
2487
|
+
const portName = endpoint === "source" ? edge.sourcePort : edge.targetPort;
|
|
2503
2488
|
const port = node?.ports?.find((candidate) => candidate.name === portName);
|
|
2504
2489
|
if (!node || !port) return 0;
|
|
2505
|
-
return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint
|
|
2490
|
+
return input.portSettings?.(port, node)?.["port.side"] === (horizontal ? endpoint === "source" ? input.direction === "right" ? "EAST" : "WEST" : input.direction === "right" ? "WEST" : "EAST" : endpoint === "source" ? input.direction === "down" ? "SOUTH" : "NORTH" : input.direction === "down" ? "NORTH" : "SOUTH") ? horizontal ? port.width ?? 0 : port.height ?? 0 : 0;
|
|
2506
2491
|
};
|
|
2507
2492
|
const adjacentNodeIds = /* @__PURE__ */ new Map();
|
|
2508
2493
|
for (const edge of input.graph.edges) {
|
|
@@ -3821,108 +3806,355 @@ function getPolylineMidpoint(points) {
|
|
|
3821
3806
|
}
|
|
3822
3807
|
|
|
3823
3808
|
//#endregion
|
|
3824
|
-
//#region src/
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
y: 0
|
|
3840
|
-
};
|
|
3841
|
-
visiting.delete(id);
|
|
3842
|
-
offsets.set(id, origin);
|
|
3843
|
-
return origin;
|
|
3844
|
-
}
|
|
3845
|
-
const parent = nodes.get(parentId);
|
|
3846
|
-
const ancestor = offset(parentId);
|
|
3847
|
-
const result = {
|
|
3848
|
-
x: ancestor.x + parent.x,
|
|
3849
|
-
y: ancestor.y + parent.y
|
|
3809
|
+
//#region src/layered/compound.ts
|
|
3810
|
+
const paddingOf = (value) => ({
|
|
3811
|
+
top: typeof value === "number" ? value : value?.top ?? 12,
|
|
3812
|
+
right: typeof value === "number" ? value : value?.right ?? 12,
|
|
3813
|
+
bottom: typeof value === "number" ? value : value?.bottom ?? 12,
|
|
3814
|
+
left: typeof value === "number" ? value : value?.left ?? 12
|
|
3815
|
+
});
|
|
3816
|
+
/** Solve each scope's occupied envelope bottom-up; never repair returned sibling positions. */
|
|
3817
|
+
function layoutCompounds(graph, options, flat) {
|
|
3818
|
+
const nodes = graph.nodes.map((node) => {
|
|
3819
|
+
const size = options.measure?.(node);
|
|
3820
|
+
return {
|
|
3821
|
+
...node,
|
|
3822
|
+
width: size?.width ?? node.width ?? 0,
|
|
3823
|
+
height: size?.height ?? node.height ?? 0
|
|
3850
3824
|
};
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3825
|
+
});
|
|
3826
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
3827
|
+
const ancestry = (id) => {
|
|
3828
|
+
const chain = [];
|
|
3829
|
+
let current = id;
|
|
3830
|
+
while (current != null) {
|
|
3831
|
+
if (chain.includes(current)) throw new Error(`Cyclic compound hierarchy at ${current}`);
|
|
3832
|
+
chain.push(current);
|
|
3833
|
+
current = byId.get(current)?.parentId ?? null;
|
|
3834
|
+
}
|
|
3835
|
+
return [...chain, null];
|
|
3836
|
+
};
|
|
3837
|
+
const branch = (id, scope) => {
|
|
3838
|
+
const chain = ancestry(id);
|
|
3839
|
+
return chain[chain.indexOf(scope) - 1];
|
|
3840
|
+
};
|
|
3841
|
+
const owner = (edge) => {
|
|
3842
|
+
const a = ancestry(edge.sourceId), b = ancestry(edge.targetId);
|
|
3843
|
+
if (edge.sourceId !== edge.targetId && b.includes(edge.sourceId)) return edge.sourceId;
|
|
3844
|
+
if (edge.sourceId !== edge.targetId && a.includes(edge.targetId)) return edge.targetId;
|
|
3845
|
+
return a.slice(1).find((id) => b.slice(1).includes(id)) ?? null;
|
|
3846
|
+
};
|
|
3847
|
+
const owned = /* @__PURE__ */ new Map();
|
|
3848
|
+
for (const edge of graph.edges) {
|
|
3849
|
+
const scope = owner(edge);
|
|
3850
|
+
owned.set(scope, [...owned.get(scope) ?? [], edge]);
|
|
3854
3851
|
}
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3852
|
+
const edges = new Map(graph.edges.map((e) => [e.id, {
|
|
3853
|
+
...e,
|
|
3854
|
+
x: 0,
|
|
3855
|
+
y: 0,
|
|
3856
|
+
width: e.width ?? 0,
|
|
3857
|
+
height: e.height ?? 0
|
|
3858
|
+
}]));
|
|
3859
|
+
const localLabels = /* @__PURE__ */ new Map();
|
|
3860
|
+
const compoundGeometry = /* @__PURE__ */ new Map();
|
|
3861
|
+
const childScopes = [...new Set(nodes.flatMap((n) => n.parentId == null ? [] : [n.parentId]))].sort((a, b) => ancestry(b).length - ancestry(a).length);
|
|
3862
|
+
const solve = (scope) => {
|
|
3863
|
+
const children = nodes.filter((n) => (n.parentId ?? null) === scope);
|
|
3864
|
+
if (!children.length) return;
|
|
3865
|
+
const container = scope == null ? void 0 : byId.get(scope);
|
|
3866
|
+
const config = container && options.compound?.(container);
|
|
3867
|
+
const direction = config?.direction ?? options.direction ?? graph.direction ?? "right";
|
|
3868
|
+
const horizontal = direction === "left" || direction === "right";
|
|
3869
|
+
const padding = paddingOf(config?.padding ?? options.padding);
|
|
3870
|
+
const insets = { ...padding };
|
|
3871
|
+
const header = config?.header;
|
|
3872
|
+
const side = header?.side ?? "top";
|
|
3873
|
+
if (header) insets[side] += side === "top" || side === "bottom" ? header.height : header.width;
|
|
3874
|
+
const scopeEdges = owned.get(scope) ?? [];
|
|
3875
|
+
const boundary = /* @__PURE__ */ new Map();
|
|
3876
|
+
const projected = [];
|
|
3877
|
+
for (const edge of scopeEdges) if (edge.sourceId === scope || edge.targetId === scope) {
|
|
3878
|
+
const id = branch(edge.sourceId === scope ? edge.targetId : edge.sourceId, scope);
|
|
3879
|
+
boundary.set(id, [...boundary.get(id) ?? [], edge]);
|
|
3880
|
+
} else projected.push({
|
|
3881
|
+
...edge,
|
|
3882
|
+
sourceId: branch(edge.sourceId, scope),
|
|
3883
|
+
targetId: branch(edge.targetId, scope),
|
|
3884
|
+
points: void 0
|
|
3885
|
+
});
|
|
3886
|
+
const gap = options.settings?.["spacing.labelNode"] ?? options.spacing?.node ?? 20;
|
|
3887
|
+
const labelGap = options.settings?.["spacing.labelLabel"] ?? 8;
|
|
3888
|
+
const sizes = new Map(children.map((n) => {
|
|
3889
|
+
const labels$1 = boundary.get(n.id) ?? [];
|
|
3890
|
+
const labelWidth = Math.max(0, ...labels$1.map((e) => e.width ?? 0));
|
|
3891
|
+
const labelHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
|
|
3892
|
+
return [n.id, {
|
|
3893
|
+
width: labels$1.length && !horizontal ? n.width + gap + labelWidth : Math.max(n.width, labelWidth),
|
|
3894
|
+
height: labels$1.length && horizontal ? n.height + gap + labelHeight : Math.max(n.height, labelHeight)
|
|
3864
3895
|
}];
|
|
3865
|
-
}))
|
|
3896
|
+
}));
|
|
3897
|
+
const labels = projected.filter((edge) => (edge.width ?? 0) > 0 && (edge.height ?? 0) > 0);
|
|
3898
|
+
const used = new Set([...graph.nodes, ...graph.edges].map((entity) => entity.id));
|
|
3899
|
+
const labelIds = /* @__PURE__ */ new Map();
|
|
3900
|
+
for (const edge of labels) {
|
|
3901
|
+
let id = `__layout_label_${edge.id}`;
|
|
3902
|
+
while ([
|
|
3903
|
+
id,
|
|
3904
|
+
`${id}_source`,
|
|
3905
|
+
`${id}_target`
|
|
3906
|
+
].some((key) => used.has(key))) id += "_";
|
|
3907
|
+
for (const key of [
|
|
3908
|
+
id,
|
|
3909
|
+
`${id}_source`,
|
|
3910
|
+
`${id}_target`
|
|
3911
|
+
]) used.add(key);
|
|
3912
|
+
labelIds.set(edge.id, id);
|
|
3913
|
+
sizes.set(id, {
|
|
3914
|
+
width: edge.width,
|
|
3915
|
+
height: edge.height
|
|
3916
|
+
});
|
|
3917
|
+
}
|
|
3918
|
+
const originalEdges = new Map(graph.edges.map((edge) => [edge.id, edge]));
|
|
3919
|
+
for (const edge of labels) {
|
|
3920
|
+
originalEdges.set(`${labelIds.get(edge.id)}_source`, edges.get(edge.id));
|
|
3921
|
+
originalEdges.set(`${labelIds.get(edge.id)}_target`, edges.get(edge.id));
|
|
3922
|
+
}
|
|
3923
|
+
const placementOrder = new Map(children.map((node, index) => [node.id, index]));
|
|
3924
|
+
for (const edge of labels) placementOrder.set(labelIds.get(edge.id), ((placementOrder.get(edge.sourceId) ?? 0) + (placementOrder.get(edge.targetId) ?? 0)) / 2 + (edge.sourceId === edge.targetId ? .25 : 0));
|
|
3925
|
+
const result$1 = flat({
|
|
3926
|
+
...graph,
|
|
3927
|
+
nodes: [...children.map((n) => ({
|
|
3928
|
+
...n,
|
|
3929
|
+
parentId: null
|
|
3930
|
+
})), ...labels.map((edge) => ({
|
|
3931
|
+
type: "node",
|
|
3932
|
+
id: labelIds.get(edge.id),
|
|
3933
|
+
data: void 0
|
|
3934
|
+
}))].sort((a, b) => placementOrder.get(a.id) - placementOrder.get(b.id)),
|
|
3935
|
+
edges: projected.flatMap((edge) => {
|
|
3936
|
+
const labelId = labelIds.get(edge.id);
|
|
3937
|
+
return labelId ? [{
|
|
3938
|
+
...edge,
|
|
3939
|
+
id: `${labelId}_source`,
|
|
3940
|
+
targetId: labelId,
|
|
3941
|
+
targetPort: void 0,
|
|
3942
|
+
width: 0,
|
|
3943
|
+
height: 0
|
|
3944
|
+
}, {
|
|
3945
|
+
...edge,
|
|
3946
|
+
id: `${labelId}_target`,
|
|
3947
|
+
sourceId: labelId,
|
|
3948
|
+
sourcePort: void 0,
|
|
3949
|
+
width: 0,
|
|
3950
|
+
height: 0
|
|
3951
|
+
}] : [edge];
|
|
3952
|
+
})
|
|
3953
|
+
}, {
|
|
3954
|
+
...options,
|
|
3955
|
+
compound: void 0,
|
|
3956
|
+
direction,
|
|
3957
|
+
padding: insets,
|
|
3958
|
+
measure: (node) => sizes.get(node.id),
|
|
3959
|
+
nodeSettings: (node) => {
|
|
3960
|
+
const original = byId.get(node.id);
|
|
3961
|
+
return original ? options.nodeSettings?.(original) : void 0;
|
|
3962
|
+
},
|
|
3963
|
+
edgeSettings: (edge) => options.edgeSettings?.(originalEdges.get(edge.id) ?? edge)
|
|
3964
|
+
});
|
|
3965
|
+
for (const placed of result$1.nodes) {
|
|
3966
|
+
const node = byId.get(placed.id);
|
|
3967
|
+
if (node) Object.assign(node, {
|
|
3968
|
+
x: placed.x,
|
|
3969
|
+
y: placed.y,
|
|
3970
|
+
ports: placed.ports
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
for (const edge of projected) {
|
|
3974
|
+
const labelId = labelIds.get(edge.id);
|
|
3975
|
+
if (labelId) {
|
|
3976
|
+
const label = result$1.nodes.find((n) => n.id === labelId);
|
|
3977
|
+
localLabels.set(edge.id, {
|
|
3978
|
+
scope,
|
|
3979
|
+
rect: {
|
|
3980
|
+
x: label.x,
|
|
3981
|
+
y: label.y,
|
|
3982
|
+
width: label.width,
|
|
3983
|
+
height: label.height
|
|
3984
|
+
}
|
|
3985
|
+
});
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
for (const [id, labels$1] of boundary) {
|
|
3989
|
+
const node = byId.get(id);
|
|
3990
|
+
const totalHeight = labels$1.reduce((sum, e) => sum + (e.height ?? 0), 0) + Math.max(0, labels$1.length - 1) * labelGap;
|
|
3991
|
+
let cross = horizontal ? 0 : (Math.max(node.height, totalHeight) - totalHeight) / 2;
|
|
3992
|
+
for (const edge of labels$1) {
|
|
3993
|
+
const width = edge.width ?? 0, height = edge.height ?? 0;
|
|
3994
|
+
localLabels.set(edge.id, {
|
|
3995
|
+
scope,
|
|
3996
|
+
rect: {
|
|
3997
|
+
x: node.x + (horizontal ? (Math.max(node.width, width) - width) / 2 : node.width + gap),
|
|
3998
|
+
y: node.y + (horizontal ? node.height + gap : 0) + cross,
|
|
3999
|
+
width,
|
|
4000
|
+
height
|
|
4001
|
+
}
|
|
4002
|
+
});
|
|
4003
|
+
cross += height + labelGap;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
const boxes = [...children.map((n) => ({
|
|
4007
|
+
x: n.x,
|
|
4008
|
+
y: n.y,
|
|
4009
|
+
...sizes.get(n.id)
|
|
4010
|
+
})), ...[...localLabels.values()].filter((l) => l.scope === scope).map((l) => l.rect)];
|
|
4011
|
+
let dx = Math.max(0, insets.left - Math.min(...boxes.map((b) => b.x)));
|
|
4012
|
+
let dy = Math.max(0, insets.top - Math.min(...boxes.map((b) => b.y)));
|
|
4013
|
+
if (header && (side === "top" || side === "bottom")) {
|
|
4014
|
+
const occupiedWidth = Math.max(...boxes.map((b) => b.x + b.width)) - Math.min(...boxes.map((b) => b.x)) + padding.left + padding.right;
|
|
4015
|
+
dx += Math.max(0, (header.width - occupiedWidth) / 2);
|
|
4016
|
+
}
|
|
4017
|
+
if (header && (side === "left" || side === "right")) {
|
|
4018
|
+
const occupiedHeight = Math.max(...boxes.map((b) => b.y + b.height)) - Math.min(...boxes.map((b) => b.y)) + padding.top + padding.bottom;
|
|
4019
|
+
dy += Math.max(0, (header.height - occupiedHeight) / 2);
|
|
4020
|
+
}
|
|
4021
|
+
const right = Math.max(...boxes.map((b) => b.x + dx + b.width));
|
|
4022
|
+
const bottom = Math.max(...boxes.map((b) => b.y + dy + b.height));
|
|
4023
|
+
for (const n of children) {
|
|
4024
|
+
n.x += dx;
|
|
4025
|
+
n.y += dy;
|
|
4026
|
+
}
|
|
4027
|
+
for (const l of localLabels.values()) if (l.scope === scope) {
|
|
4028
|
+
l.rect.x += dx;
|
|
4029
|
+
l.rect.y += dy;
|
|
4030
|
+
}
|
|
4031
|
+
if (container) {
|
|
4032
|
+
const width = Math.max(header?.width ?? 0, config?.minContentSize?.width ? config.minContentSize.width + insets.left + insets.right : 0, right + insets.right);
|
|
4033
|
+
const height = Math.max(header?.height ?? 0, config?.minContentSize?.height ? config.minContentSize.height + insets.top + insets.bottom : 0, bottom + insets.bottom);
|
|
4034
|
+
container.width = Math.max(container.width, width);
|
|
4035
|
+
container.height = Math.max(container.height, height);
|
|
4036
|
+
compoundGeometry.set(container.id, {
|
|
4037
|
+
bounds: {
|
|
4038
|
+
x: 0,
|
|
4039
|
+
y: 0,
|
|
4040
|
+
width: container.width,
|
|
4041
|
+
height: container.height
|
|
4042
|
+
},
|
|
4043
|
+
content: {
|
|
4044
|
+
x: insets.left,
|
|
4045
|
+
y: insets.top,
|
|
4046
|
+
width: container.width - insets.left - insets.right,
|
|
4047
|
+
height: container.height - insets.top - insets.bottom
|
|
4048
|
+
},
|
|
4049
|
+
...header ? { header: {
|
|
4050
|
+
x: side === "right" ? container.width - header.width : 0,
|
|
4051
|
+
y: side === "bottom" ? container.height - header.height : 0,
|
|
4052
|
+
width: side === "top" || side === "bottom" ? container.width : header.width,
|
|
4053
|
+
height: side === "left" || side === "right" ? container.height : header.height
|
|
4054
|
+
} } : {}
|
|
4055
|
+
});
|
|
4056
|
+
}
|
|
3866
4057
|
};
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
4058
|
+
for (const scope of childScopes) solve(scope);
|
|
4059
|
+
const roots = nodes.filter((n) => n.parentId == null);
|
|
4060
|
+
if (roots.length === 1 && compoundGeometry.has(roots[0].id) && !owned.get(null)?.length) Object.assign(roots[0], {
|
|
4061
|
+
x: 0,
|
|
4062
|
+
y: 0
|
|
4063
|
+
});
|
|
4064
|
+
else solve(null);
|
|
4065
|
+
const origin = (id) => {
|
|
4066
|
+
if (id == null) return {
|
|
4067
|
+
x: 0,
|
|
4068
|
+
y: 0
|
|
4069
|
+
};
|
|
4070
|
+
return ancestry(id).slice(0, -1).reduce((p, key) => ({
|
|
4071
|
+
x: p.x + byId.get(key).x,
|
|
4072
|
+
y: p.y + byId.get(key).y
|
|
4073
|
+
}), {
|
|
4074
|
+
x: 0,
|
|
4075
|
+
y: 0
|
|
4076
|
+
});
|
|
4077
|
+
};
|
|
4078
|
+
for (const [id, label] of localLabels) {
|
|
4079
|
+
const offset = origin(label.scope);
|
|
4080
|
+
Object.assign(edges.get(id), {
|
|
4081
|
+
...label.rect,
|
|
4082
|
+
x: label.rect.x + offset.x,
|
|
4083
|
+
y: label.rect.y + offset.y
|
|
4084
|
+
});
|
|
4085
|
+
}
|
|
4086
|
+
const attachmentSettings = {};
|
|
4087
|
+
for (const edge of edges.values()) {
|
|
4088
|
+
const sourceGeometry = compoundGeometry.get(edge.sourceId), targetGeometry = compoundGeometry.get(edge.targetId);
|
|
4089
|
+
const intent = options.edgeAttachment?.(edge);
|
|
4090
|
+
attachmentSettings[edge.id] = {
|
|
4091
|
+
...sourceGeometry && edge.sourceId !== edge.targetId && ancestry(edge.targetId).includes(edge.sourceId) ? { sourceAttachment: {
|
|
4092
|
+
bounds: intent?.source === "outer" ? {
|
|
4093
|
+
x: 0,
|
|
4094
|
+
y: 0,
|
|
4095
|
+
width: sourceGeometry.bounds.width,
|
|
4096
|
+
height: sourceGeometry.bounds.height
|
|
4097
|
+
} : sourceGeometry.content,
|
|
4098
|
+
facing: intent?.source === "outer" ? "outward" : "inward"
|
|
4099
|
+
} } : {},
|
|
4100
|
+
...targetGeometry && intent?.target === "content" ? { targetAttachment: {
|
|
4101
|
+
bounds: targetGeometry.content,
|
|
4102
|
+
facing: "inward"
|
|
4103
|
+
} } : {}
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
const worldNodes = nodes.map((n) => ({
|
|
4107
|
+
...n,
|
|
4108
|
+
...origin(n.id)
|
|
4109
|
+
}));
|
|
4110
|
+
const usedIds = new Set(nodes.map((n) => n.id));
|
|
4111
|
+
const headers = [...compoundGeometry].flatMap(([id, geometry]) => {
|
|
4112
|
+
if (!geometry.header) return [];
|
|
4113
|
+
const p = origin(id);
|
|
4114
|
+
let key = `__layout_header_${id}`;
|
|
4115
|
+
while (usedIds.has(key)) key += "_";
|
|
4116
|
+
usedIds.add(key);
|
|
4117
|
+
return [{
|
|
4118
|
+
type: "node",
|
|
4119
|
+
id: key,
|
|
4120
|
+
parentId: id,
|
|
4121
|
+
data: void 0,
|
|
4122
|
+
...geometry.header,
|
|
4123
|
+
x: p.x + geometry.header.x,
|
|
4124
|
+
y: p.y + geometry.header.y
|
|
4125
|
+
}];
|
|
4126
|
+
});
|
|
4127
|
+
const snapshot = require_routing.orthogonalRouting.route({
|
|
3871
4128
|
...graph,
|
|
3872
|
-
nodes:
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
})) } : {}
|
|
3890
|
-
};
|
|
3891
|
-
})
|
|
4129
|
+
nodes: [...worldNodes, ...headers],
|
|
4130
|
+
edges: [...edges.values()]
|
|
4131
|
+
}, {
|
|
4132
|
+
coordinateSpace: "world",
|
|
4133
|
+
edges: attachmentSettings
|
|
4134
|
+
});
|
|
4135
|
+
for (const [id, route] of snapshot.routes) {
|
|
4136
|
+
const polylines = require_routing.routeToPolylines(route);
|
|
4137
|
+
Object.assign(edges.get(id), {
|
|
4138
|
+
points: polylines.flatMap((p) => p.map((point) => ({ ...point }))),
|
|
4139
|
+
routing: "orthogonal"
|
|
4140
|
+
});
|
|
4141
|
+
}
|
|
4142
|
+
for (const [id, geometry] of compoundGeometry) geometry.bounds = {
|
|
4143
|
+
...geometry.bounds,
|
|
4144
|
+
x: byId.get(id).x,
|
|
4145
|
+
y: byId.get(id).y
|
|
3892
4146
|
};
|
|
3893
|
-
|
|
3894
|
-
/** Ancestor frames stay fixed during authoring. Preserve untouched coordinates exactly. */
|
|
3895
|
-
function nativeGeometry(graph, original) {
|
|
3896
|
-
const offsets = frames(original);
|
|
3897
|
-
const world = worldGeometry(original);
|
|
3898
|
-
const originalNodes = new Map(original.nodes.map((n) => [n.id, n]));
|
|
3899
|
-
const originalEdges = new Map(original.edges.map((e) => [e.id, e]));
|
|
3900
|
-
const worldNodes = new Map(world.nodes.map((n) => [n.id, n]));
|
|
3901
|
-
const worldEdges = new Map(world.edges.map((e) => [e.id, e]));
|
|
3902
|
-
return {
|
|
4147
|
+
const result = {
|
|
3903
4148
|
...graph,
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
};
|
|
3911
|
-
}),
|
|
3912
|
-
edges: graph.edges.map((e) => {
|
|
3913
|
-
const p = offsets.edges.get(e.id), before = originalEdges.get(e.id), prior = worldEdges.get(e.id);
|
|
3914
|
-
const points = JSON.stringify(e.points) === JSON.stringify(prior.points) ? before.points : e.points?.map((v) => ({
|
|
3915
|
-
x: v.x - p.x,
|
|
3916
|
-
y: v.y - p.y
|
|
3917
|
-
}));
|
|
3918
|
-
return {
|
|
3919
|
-
...e,
|
|
3920
|
-
x: e.x === prior.x ? before.x : e.x - p.x,
|
|
3921
|
-
y: e.y === prior.y ? before.y : e.y - p.y,
|
|
3922
|
-
...points === void 0 ? {} : { points }
|
|
3923
|
-
};
|
|
3924
|
-
})
|
|
4149
|
+
edgeCoordinateSpace: "world",
|
|
4150
|
+
direction: options.direction ?? graph.direction ?? "right",
|
|
4151
|
+
nodes,
|
|
4152
|
+
edges: graph.edges.map((e) => edges.get(e.id)),
|
|
4153
|
+
compoundGeometry,
|
|
4154
|
+
compoundRoutes: snapshot.routes
|
|
3925
4155
|
};
|
|
4156
|
+
require_routing.recordRouteGeometry(result, snapshot.routes);
|
|
4157
|
+
return result;
|
|
3926
4158
|
}
|
|
3927
4159
|
|
|
3928
4160
|
//#endregion
|
|
@@ -5305,7 +5537,7 @@ const c = {
|
|
|
5305
5537
|
})
|
|
5306
5538
|
};
|
|
5307
5539
|
function constraintFailure(constraint, message) {
|
|
5308
|
-
throw new LayoutError(`${constraint.id}: ${message}`, "UNSATISFIED_CONSTRAINT");
|
|
5540
|
+
throw new require_routing.LayoutError(`${constraint.id}: ${message}`, "UNSATISFIED_CONSTRAINT");
|
|
5309
5541
|
}
|
|
5310
5542
|
/** Solve positions only. Fixed geometry is represented by constants, never edit variables. */
|
|
5311
5543
|
function solveGeometryConstraints(graph, constraints, movableNodes, movableLabels, diagnostics) {
|
|
@@ -5317,7 +5549,7 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
|
|
|
5317
5549
|
const node = "nodeId" in ref;
|
|
5318
5550
|
const id = node ? ref.nodeId : ref.edgeId;
|
|
5319
5551
|
const rect = node ? nodes.get(id) : edges.get(id);
|
|
5320
|
-
if (!rect) throw new LayoutError(`Unknown constraint entity: ${id}`, "INVALID_CONSTRAINT");
|
|
5552
|
+
if (!rect) throw new require_routing.LayoutError(`Unknown constraint entity: ${id}`, "INVALID_CONSTRAINT");
|
|
5321
5553
|
const key = JSON.stringify([node ? "node" : "label", id]);
|
|
5322
5554
|
if ((node ? movableNodes : movableLabels).has(id) && !variables.has(key)) {
|
|
5323
5555
|
const pair = {
|
|
@@ -5343,13 +5575,13 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
|
|
|
5343
5575
|
const checks = [];
|
|
5344
5576
|
const ids = /* @__PURE__ */ new Set();
|
|
5345
5577
|
for (const constraint of constraints) {
|
|
5346
|
-
if (ids.has(constraint.id)) throw new LayoutError(`Duplicate constraint ID: ${constraint.id}`, "INVALID_CONSTRAINT");
|
|
5578
|
+
if (ids.has(constraint.id)) throw new require_routing.LayoutError(`Duplicate constraint ID: ${constraint.id}`, "INVALID_CONSTRAINT");
|
|
5347
5579
|
ids.add(constraint.id);
|
|
5348
5580
|
if (constraint.kind === "waypoint") continue;
|
|
5349
5581
|
const strength = Strength[constraint.strength ?? "required"];
|
|
5350
5582
|
const add = (lhs, operator = Operator.Eq, rhs = 0) => {
|
|
5351
5583
|
const difference = lhs.minus(rhs);
|
|
5352
|
-
if (!Number.isFinite(difference.constant()) || !difference.terms().array.every((item) => Number.isFinite(item.second))) throw new LayoutError(`${constraint.id}: non-finite expression`, "INVALID_CONSTRAINT");
|
|
5584
|
+
if (!Number.isFinite(difference.constant()) || !difference.terms().array.every((item) => Number.isFinite(item.second))) throw new require_routing.LayoutError(`${constraint.id}: non-finite expression`, "INVALID_CONSTRAINT");
|
|
5353
5585
|
try {
|
|
5354
5586
|
solver.addConstraint(new Constraint(difference, operator, 0, strength));
|
|
5355
5587
|
} catch {
|
|
@@ -5407,7 +5639,7 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
|
|
|
5407
5639
|
}
|
|
5408
5640
|
const coordinate = (variable) => {
|
|
5409
5641
|
const value = variable.value();
|
|
5410
|
-
if (!Number.isFinite(value)) throw new LayoutError("Constraint solution contains non-finite geometry", "INVALID_CONSTRAINT");
|
|
5642
|
+
if (!Number.isFinite(value)) throw new require_routing.LayoutError("Constraint solution contains non-finite geometry", "INVALID_CONSTRAINT");
|
|
5411
5643
|
return value === 0 ? 0 : value;
|
|
5412
5644
|
};
|
|
5413
5645
|
return {
|
|
@@ -5431,173 +5663,6 @@ function solveGeometryConstraints(graph, constraints, movableNodes, movableLabel
|
|
|
5431
5663
|
};
|
|
5432
5664
|
}
|
|
5433
5665
|
|
|
5434
|
-
//#endregion
|
|
5435
|
-
//#region src/authoring/routing.ts
|
|
5436
|
-
function overlaps(a, b, gap = 0) {
|
|
5437
|
-
return a.width > 0 && a.height > 0 && b.width > 0 && b.height > 0 && a.x < b.x + b.width + gap && a.x + a.width + gap > b.x && a.y < b.y + b.height + gap && a.y + a.height + gap > b.y;
|
|
5438
|
-
}
|
|
5439
|
-
/** Open-interior intersection: travelling along a rectangle boundary is allowed. */
|
|
5440
|
-
function crossesRect(a, b, rect) {
|
|
5441
|
-
let low = 0, high = 1;
|
|
5442
|
-
for (const axis of ["x", "y"]) {
|
|
5443
|
-
const delta = b[axis] - a[axis];
|
|
5444
|
-
const min = rect[axis], max = min + (axis === "x" ? rect.width : rect.height);
|
|
5445
|
-
if (Math.abs(delta) < 1e-10) {
|
|
5446
|
-
if (a[axis] <= min || a[axis] >= max) return false;
|
|
5447
|
-
} else {
|
|
5448
|
-
const t1 = (min - a[axis]) / delta, t2 = (max - a[axis]) / delta;
|
|
5449
|
-
low = Math.max(low, Math.min(t1, t2));
|
|
5450
|
-
high = Math.min(high, Math.max(t1, t2));
|
|
5451
|
-
}
|
|
5452
|
-
}
|
|
5453
|
-
return low < high - 1e-10;
|
|
5454
|
-
}
|
|
5455
|
-
function routeCrosses(points, rect) {
|
|
5456
|
-
return points?.some((point, i) => i > 0 && crossesRect(points[i - 1], point, rect)) ?? false;
|
|
5457
|
-
}
|
|
5458
|
-
function endpoint(node, portName, direction, source) {
|
|
5459
|
-
if (portName !== void 0) {
|
|
5460
|
-
const port = node.ports?.find((p) => p.name === portName);
|
|
5461
|
-
if (port?.x !== void 0 && port.y !== void 0) return {
|
|
5462
|
-
x: node.x + port.x + (port.width ?? 0) / 2,
|
|
5463
|
-
y: node.y + port.y + (port.height ?? 0) / 2
|
|
5464
|
-
};
|
|
5465
|
-
throw new LayoutError(`Port ${portName} on ${node.id} requires positioned geometry`, "MISSING_GEOMETRY");
|
|
5466
|
-
}
|
|
5467
|
-
const horizontal = direction === "left" || direction === "right";
|
|
5468
|
-
const far = source === (direction === "right" || direction === "down");
|
|
5469
|
-
return horizontal ? {
|
|
5470
|
-
x: node.x + (far ? node.width : 0),
|
|
5471
|
-
y: node.y + node.height / 2
|
|
5472
|
-
} : {
|
|
5473
|
-
x: node.x + node.width / 2,
|
|
5474
|
-
y: node.y + (far ? node.height : 0)
|
|
5475
|
-
};
|
|
5476
|
-
}
|
|
5477
|
-
/** Deterministic orthogonal visibility-grid search around fixed rectangles. */
|
|
5478
|
-
function connect(start, end, obstacles, check) {
|
|
5479
|
-
if (start.x === end.x && start.y === end.y) return [start];
|
|
5480
|
-
for (let attempt = 0; attempt < 8; attempt++) {
|
|
5481
|
-
check();
|
|
5482
|
-
const margin = 64 * 2 ** attempt;
|
|
5483
|
-
const left = Math.min(start.x, end.x) - margin;
|
|
5484
|
-
const right = Math.max(start.x, end.x) + margin;
|
|
5485
|
-
const top = Math.min(start.y, end.y) - margin;
|
|
5486
|
-
const bottom = Math.max(start.y, end.y) + margin;
|
|
5487
|
-
const local = obstacles.filter((r) => r.x <= right && r.x + r.width >= left && r.y <= bottom && r.y + r.height >= top);
|
|
5488
|
-
const result = search(start, end, local, obstacles, check);
|
|
5489
|
-
if (result || local.length === obstacles.length) return result;
|
|
5490
|
-
}
|
|
5491
|
-
}
|
|
5492
|
-
function search(start, end, gridObstacles, obstacles, check) {
|
|
5493
|
-
const xs = [...new Set([
|
|
5494
|
-
start.x,
|
|
5495
|
-
end.x,
|
|
5496
|
-
...gridObstacles.flatMap((r) => [r.x, r.x + r.width])
|
|
5497
|
-
])].sort((a, b) => a - b);
|
|
5498
|
-
const ys = [...new Set([
|
|
5499
|
-
start.y,
|
|
5500
|
-
end.y,
|
|
5501
|
-
...gridObstacles.flatMap((r) => [r.y, r.y + r.height])
|
|
5502
|
-
])].sort((a, b) => a - b);
|
|
5503
|
-
if (xs.length * ys.length > 4e4) return void 0;
|
|
5504
|
-
const width = xs.length;
|
|
5505
|
-
const point = (id) => ({
|
|
5506
|
-
x: xs[id % width],
|
|
5507
|
-
y: ys[Math.floor(id / width)]
|
|
5508
|
-
});
|
|
5509
|
-
const from = ys.indexOf(start.y) * width + xs.indexOf(start.x);
|
|
5510
|
-
const to = ys.indexOf(end.y) * width + xs.indexOf(end.x);
|
|
5511
|
-
const distances = new Map([[from, 0]]);
|
|
5512
|
-
const previous = /* @__PURE__ */ new Map();
|
|
5513
|
-
const queue = [];
|
|
5514
|
-
function push(item) {
|
|
5515
|
-
queue.push(item);
|
|
5516
|
-
let i = queue.length - 1;
|
|
5517
|
-
while (i > 0) {
|
|
5518
|
-
const parent = i - 1 >> 1;
|
|
5519
|
-
if (queue[parent].cost <= item.cost) break;
|
|
5520
|
-
queue[i] = queue[parent];
|
|
5521
|
-
i = parent;
|
|
5522
|
-
}
|
|
5523
|
-
queue[i] = item;
|
|
5524
|
-
}
|
|
5525
|
-
function pop() {
|
|
5526
|
-
const first = queue[0];
|
|
5527
|
-
const last = queue.pop();
|
|
5528
|
-
if (queue.length) {
|
|
5529
|
-
let i = 0;
|
|
5530
|
-
while (i * 2 + 1 < queue.length) {
|
|
5531
|
-
let child = i * 2 + 1;
|
|
5532
|
-
if (child + 1 < queue.length && queue[child + 1].cost < queue[child].cost) child++;
|
|
5533
|
-
if (last.cost <= queue[child].cost) break;
|
|
5534
|
-
queue[i] = queue[child];
|
|
5535
|
-
i = child;
|
|
5536
|
-
}
|
|
5537
|
-
queue[i] = last;
|
|
5538
|
-
}
|
|
5539
|
-
return first;
|
|
5540
|
-
}
|
|
5541
|
-
push({
|
|
5542
|
-
id: from,
|
|
5543
|
-
cost: 0
|
|
5544
|
-
});
|
|
5545
|
-
let visits = 0;
|
|
5546
|
-
while (queue.length) {
|
|
5547
|
-
if (++visits % 128 === 0) check();
|
|
5548
|
-
const current = pop();
|
|
5549
|
-
if (current.cost !== distances.get(current.id)) continue;
|
|
5550
|
-
if (current.id === to) {
|
|
5551
|
-
const result = [end];
|
|
5552
|
-
let id = to;
|
|
5553
|
-
while (id !== from) {
|
|
5554
|
-
id = previous.get(id);
|
|
5555
|
-
result.push(point(id));
|
|
5556
|
-
}
|
|
5557
|
-
return result.reverse();
|
|
5558
|
-
}
|
|
5559
|
-
const x = current.id % width, y = Math.floor(current.id / width);
|
|
5560
|
-
const neighbors = [
|
|
5561
|
-
x > 0 ? current.id - 1 : -1,
|
|
5562
|
-
x + 1 < width ? current.id + 1 : -1,
|
|
5563
|
-
y > 0 ? current.id - width : -1,
|
|
5564
|
-
y + 1 < ys.length ? current.id + width : -1
|
|
5565
|
-
];
|
|
5566
|
-
const a = point(current.id);
|
|
5567
|
-
for (const next of neighbors) {
|
|
5568
|
-
if (next < 0) continue;
|
|
5569
|
-
const b = point(next);
|
|
5570
|
-
if (obstacles.some((rect) => crossesRect(a, b, rect))) continue;
|
|
5571
|
-
const cost = current.cost + Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
5572
|
-
if (cost >= (distances.get(next) ?? Infinity)) continue;
|
|
5573
|
-
distances.set(next, cost);
|
|
5574
|
-
previous.set(next, current.id);
|
|
5575
|
-
push({
|
|
5576
|
-
id: next,
|
|
5577
|
-
cost
|
|
5578
|
-
});
|
|
5579
|
-
}
|
|
5580
|
-
}
|
|
5581
|
-
}
|
|
5582
|
-
function routeOrthogonal(edge, source, target, direction, obstacles, waypoints, check) {
|
|
5583
|
-
const anchors = [
|
|
5584
|
-
endpoint(source, edge.sourcePort, direction, true),
|
|
5585
|
-
...waypoints,
|
|
5586
|
-
endpoint(target, edge.targetPort, direction, false)
|
|
5587
|
-
];
|
|
5588
|
-
const points = [];
|
|
5589
|
-
for (let i = 1; i < anchors.length; i++) {
|
|
5590
|
-
const segment = connect(anchors[i - 1], anchors[i], obstacles, check);
|
|
5591
|
-
if (!segment) return void 0;
|
|
5592
|
-
points.push(...points.length ? segment.slice(1) : segment);
|
|
5593
|
-
}
|
|
5594
|
-
return points.filter((p, i) => {
|
|
5595
|
-
if (i === 0 || i === points.length - 1) return true;
|
|
5596
|
-
const before = points[i - 1], after = points[i + 1];
|
|
5597
|
-
return !(before.x === p.x && p.x === after.x && (p.y - before.y) * (after.y - p.y) >= 0 || before.y === p.y && p.y === after.y && (p.x - before.x) * (after.x - p.x) >= 0);
|
|
5598
|
-
});
|
|
5599
|
-
}
|
|
5600
|
-
|
|
5601
5666
|
//#endregion
|
|
5602
5667
|
//#region src/authoring/partial.ts
|
|
5603
5668
|
const geometryKeys = [
|
|
@@ -5642,14 +5707,14 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5642
5707
|
const constrainedLabels = labelReferences(constraints);
|
|
5643
5708
|
for (const [ids, entities] of [[selectedNodes, input.nodes], [selectedEdges, input.edges]]) {
|
|
5644
5709
|
const known = new Set(entities.map((e) => e.id));
|
|
5645
|
-
for (const id of ids) if (!known.has(id)) throw new LayoutError(`Unknown selected entity: ${id}`, "INVALID_SELECTION");
|
|
5710
|
+
for (const id of ids) if (!known.has(id)) throw new require_routing.LayoutError(`Unknown selected entity: ${id}`, "INVALID_SELECTION");
|
|
5646
5711
|
}
|
|
5647
5712
|
const containers = new Set(input.nodes.flatMap((n) => n.parentId == null ? [] : [n.parentId]));
|
|
5648
|
-
if ([...selectedNodes].some((id) => containers.has(id))) throw new UnsupportedLayoutError("Authoring layout cannot move containers; select leaf nodes or use full layout without geometry constraints");
|
|
5713
|
+
if ([...selectedNodes].some((id) => containers.has(id))) throw new require_routing.UnsupportedLayoutError("Authoring layout cannot move containers; select leaf nodes or use full layout without geometry constraints");
|
|
5649
5714
|
const previousNodes = new Map(scope.previous?.nodes.map((n) => [n.id, n]));
|
|
5650
5715
|
const previousEdges = new Map(scope.previous?.edges.map((e) => [e.id, e]));
|
|
5651
5716
|
const missing = (id) => {
|
|
5652
|
-
throw new LayoutError(`Fixed entity ${id} requires finite geometry in graph or previous`, "MISSING_GEOMETRY");
|
|
5717
|
+
throw new require_routing.LayoutError(`Fixed entity ${id} requires finite geometry in graph or previous`, "MISSING_GEOMETRY");
|
|
5653
5718
|
};
|
|
5654
5719
|
let nodes = input.nodes.map((node) => {
|
|
5655
5720
|
const previous = previousNodes.get(node.id);
|
|
@@ -5684,7 +5749,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5684
5749
|
nodes,
|
|
5685
5750
|
edges
|
|
5686
5751
|
};
|
|
5687
|
-
const baseline = worldGeometry(authored);
|
|
5752
|
+
const baseline = require_routing.worldGeometry(authored);
|
|
5688
5753
|
nodes = baseline.nodes;
|
|
5689
5754
|
edges = baseline.edges;
|
|
5690
5755
|
let graph = baseline;
|
|
@@ -5705,7 +5770,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5705
5770
|
for (const node of nodes) ancestors(node.id);
|
|
5706
5771
|
const direction = options.direction ?? input.direction ?? scope.previous?.direction ?? "down";
|
|
5707
5772
|
const gap = options.spacing?.node ?? 20;
|
|
5708
|
-
if (!finite(gap) || gap < 0) throw new LayoutError("Spacing must be finite and non-negative", "INVALID_OPTIONS");
|
|
5773
|
+
if (!finite(gap) || gap < 0) throw new require_routing.LayoutError("Spacing must be finite and non-negative", "INVALID_OPTIONS");
|
|
5709
5774
|
context.throwIfAborted();
|
|
5710
5775
|
if (selectedNodes.size && phase === "selection") {
|
|
5711
5776
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -5782,7 +5847,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5782
5847
|
};
|
|
5783
5848
|
const parent = n.parentId == null ? void 0 : placed.get(n.parentId);
|
|
5784
5849
|
if (parent && (translated.x < parent.x || translated.y < parent.y || translated.x + translated.width > parent.x + parent.width || translated.y + translated.height > parent.y + parent.height)) return false;
|
|
5785
|
-
return fixedLabels.every((label) => budget() && !overlaps(translated, label, gap)) && obstacles.every((o) => budget() && (ancestors(n.id).has(o.id) || ancestors(o.id).has(n.id) || !overlaps(translated, o, gap)));
|
|
5850
|
+
return fixedLabels.every((label) => budget() && !require_routing.overlaps(translated, label, gap)) && obstacles.every((o) => budget() && (ancestors(n.id).has(o.id) || ancestors(o.id).has(n.id) || !require_routing.overlaps(translated, o, gap)));
|
|
5786
5851
|
});
|
|
5787
5852
|
};
|
|
5788
5853
|
const offset = candidates.find((candidate) => comparisons <= 1e5 && fits(candidate));
|
|
@@ -5809,7 +5874,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5809
5874
|
const movedIds = new Set(moved.map((n) => n.id));
|
|
5810
5875
|
const resultNodes = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
5811
5876
|
const automatic = /* @__PURE__ */ new Set();
|
|
5812
|
-
const affected = (edge) => movedIds.has(edge.sourceId) || movedIds.has(edge.targetId) || moved.some((n) => !ancestors(edge.sourceId).has(n.id) && !ancestors(edge.targetId).has(n.id) && (routeCrosses(edge.points, n) || overlaps(edge, n) || edge.routing === "splines" && edge.points?.length && overlaps(n, {
|
|
5877
|
+
const affected = (edge) => movedIds.has(edge.sourceId) || movedIds.has(edge.targetId) || moved.some((n) => !ancestors(edge.sourceId).has(n.id) && !ancestors(edge.targetId).has(n.id) && (require_routing.routeCrosses(edge.points, n) || require_routing.overlaps(edge, n) || edge.routing === "splines" && edge.points?.length && require_routing.overlaps(n, {
|
|
5813
5878
|
x: Math.min(...edge.points.map((p) => p.x)),
|
|
5814
5879
|
y: Math.min(...edge.points.map((p) => p.y)),
|
|
5815
5880
|
width: Math.max(...edge.points.map((p) => p.x)) - Math.min(...edge.points.map((p) => p.x)),
|
|
@@ -5817,7 +5882,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5817
5882
|
})));
|
|
5818
5883
|
const edgeById = new Map(graph.edges.map((e) => [e.id, e]));
|
|
5819
5884
|
for (const constraint of constraints) if (constraint.kind === "waypoint") {
|
|
5820
|
-
if (!finite(constraint.point.x) || !finite(constraint.point.y) || !edgeById.has(constraint.edgeId)) throw new LayoutError(`${constraint.id}: invalid waypoint`, "INVALID_CONSTRAINT");
|
|
5885
|
+
if (!finite(constraint.point.x) || !finite(constraint.point.y) || !edgeById.has(constraint.edgeId)) throw new require_routing.LayoutError(`${constraint.id}: invalid waypoint`, "INVALID_CONSTRAINT");
|
|
5821
5886
|
}
|
|
5822
5887
|
graph = {
|
|
5823
5888
|
...graph,
|
|
@@ -5840,7 +5905,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5840
5905
|
geometry: "routes"
|
|
5841
5906
|
});
|
|
5842
5907
|
if (routeAllowed) {
|
|
5843
|
-
if (options.settings?.edgeRouting === "SPLINES" || options.strategies?.routeEdges) throw new UnsupportedLayoutError("Partial layout supports its built-in orthogonal router only");
|
|
5908
|
+
if (options.settings?.edgeRouting === "SPLINES" || options.strategies?.routeEdges) throw new require_routing.UnsupportedLayoutError("Partial layout supports its built-in orthogonal router only");
|
|
5844
5909
|
const ancestorIds = new Set([...ancestors(source.id), ...ancestors(target.id)]);
|
|
5845
5910
|
const nodeObstacles = graph.nodes.filter((n) => !ancestorIds.has(n.id)).map((n) => n.id === source.id || n.id === target.id ? n : {
|
|
5846
5911
|
...n,
|
|
@@ -5852,7 +5917,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5852
5917
|
const labelObstacles = [...edgeById.values()].filter((e) => e.id !== edge.id && e.width > 0 && e.height > 0);
|
|
5853
5918
|
const required = waypoints.filter((c$1) => (c$1.strength ?? "required") === "required");
|
|
5854
5919
|
const accepted = new Set(required.map((c$1) => c$1.id));
|
|
5855
|
-
const route = () => routeOrthogonal(edge, source, target, direction, [...nodeObstacles, ...labelObstacles], waypoints.filter((c$1) => c$1.kind === "waypoint" && accepted.has(c$1.id)).map((c$1) => c$1.kind === "waypoint" ? c$1.point : {
|
|
5920
|
+
const route = () => require_routing.routeOrthogonal(edge, source, target, direction, [...nodeObstacles, ...labelObstacles], waypoints.filter((c$1) => c$1.kind === "waypoint" && accepted.has(c$1.id)).map((c$1) => c$1.kind === "waypoint" ? c$1.point : {
|
|
5856
5921
|
x: 0,
|
|
5857
5922
|
y: 0
|
|
5858
5923
|
}), () => context.throwIfAborted());
|
|
@@ -5940,7 +6005,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5940
6005
|
...next,
|
|
5941
6006
|
...p
|
|
5942
6007
|
};
|
|
5943
|
-
return !graph.nodes.some((n) => !parentIds.has(n.id) && overlaps(rect, n)) && ![...edgeById.values()].some((e) => e.id !== edge.id && overlaps(rect, e));
|
|
6008
|
+
return !graph.nodes.some((n) => !parentIds.has(n.id) && require_routing.overlaps(rect, n)) && ![...edgeById.values()].some((e) => e.id !== edge.id && require_routing.overlaps(rect, e));
|
|
5944
6009
|
});
|
|
5945
6010
|
if (chosen) next = {
|
|
5946
6011
|
...next,
|
|
@@ -5981,7 +6046,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5981
6046
|
entityIds: [node.id, parent.id],
|
|
5982
6047
|
geometry: "node"
|
|
5983
6048
|
});
|
|
5984
|
-
if (graph.nodes.some((other) => other.id !== node.id && !ancestors(node.id).has(other.id) && !ancestors(other.id).has(node.id) && overlaps(node, other))) context.diagnostics.push({
|
|
6049
|
+
if (graph.nodes.some((other) => other.id !== node.id && !ancestors(node.id).has(other.id) && !ancestors(other.id).has(node.id) && require_routing.overlaps(node, other))) context.diagnostics.push({
|
|
5985
6050
|
severity: "warning",
|
|
5986
6051
|
code: "NODE_OVERLAP",
|
|
5987
6052
|
message: `Node ${node.id} overlaps another node`,
|
|
@@ -5993,7 +6058,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
5993
6058
|
const before = baseline.edges.find((e) => e.id === edge.id);
|
|
5994
6059
|
if (!samePosition(edge, before)) {
|
|
5995
6060
|
const containerIds = new Set([...ancestors(edge.sourceId), ...ancestors(edge.targetId)]);
|
|
5996
|
-
const collisions = [...graph.nodes.filter((n) => !containerIds.has(n.id) && overlaps(edge, n)), ...graph.edges.filter((other) => other.id !== edge.id && overlaps(edge, other))];
|
|
6061
|
+
const collisions = [...graph.nodes.filter((n) => !containerIds.has(n.id) && require_routing.overlaps(edge, n)), ...graph.edges.filter((other) => other.id !== edge.id && require_routing.overlaps(edge, other))];
|
|
5997
6062
|
if (collisions.length) context.diagnostics.push({
|
|
5998
6063
|
severity: "warning",
|
|
5999
6064
|
code: "LABEL_OVERLAP",
|
|
@@ -6001,7 +6066,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
6001
6066
|
entityIds: [edge.id, ...collisions.map((e) => e.id)],
|
|
6002
6067
|
geometry: "labels"
|
|
6003
6068
|
});
|
|
6004
|
-
for (const other of graph.edges) if (other.id !== edge.id && routeCrosses(other.points, edge) && !routeCrosses(other.points, before)) context.diagnostics.push({
|
|
6069
|
+
for (const other of graph.edges) if (other.id !== edge.id && require_routing.routeCrosses(other.points, edge) && !require_routing.routeCrosses(other.points, before)) context.diagnostics.push({
|
|
6005
6070
|
severity: "warning",
|
|
6006
6071
|
code: "ROUTE_REPAIR_REQUIRED",
|
|
6007
6072
|
message: `Route ${other.id} crosses the moved label ${edge.id}`,
|
|
@@ -6009,7 +6074,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
6009
6074
|
geometry: "routes"
|
|
6010
6075
|
});
|
|
6011
6076
|
}
|
|
6012
|
-
if (geometry === "routes" && routeCrosses(before.points, before) && !routeCrosses(edge.points, edge)) context.diagnostics.push({
|
|
6077
|
+
if (geometry === "routes" && require_routing.routeCrosses(before.points, before) && !require_routing.routeCrosses(edge.points, edge)) context.diagnostics.push({
|
|
6013
6078
|
severity: "warning",
|
|
6014
6079
|
code: "LABEL_REPAIR_REQUIRED",
|
|
6015
6080
|
message: `Label ${edge.id} is no longer attached to its route`,
|
|
@@ -6017,7 +6082,7 @@ function runPartialLayout(input, options, context, arrange, phase = "selection")
|
|
|
6017
6082
|
geometry: "labels"
|
|
6018
6083
|
});
|
|
6019
6084
|
}
|
|
6020
|
-
return nativeGeometry(graph, authored);
|
|
6085
|
+
return require_routing.nativeGeometry(graph, authored);
|
|
6021
6086
|
}
|
|
6022
6087
|
|
|
6023
6088
|
//#endregion
|
|
@@ -10556,7 +10621,7 @@ function runCommentBoxPipeline(graph, options, context) {
|
|
|
10556
10621
|
x: horizontal ? point.x : point.x + crossShift,
|
|
10557
10622
|
y: horizontal ? point.y + crossShift : point.y
|
|
10558
10623
|
}));
|
|
10559
|
-
const endpoint
|
|
10624
|
+
const endpoint = (source) => {
|
|
10560
10625
|
if (horizontal) return {
|
|
10561
10626
|
x: direction === "right" === source ? (targetRect.x ?? 0) + (targetRect.width ?? 0) : targetRect.x ?? 0,
|
|
10562
10627
|
y: (targetRect.y ?? 0) + (targetRect.height ?? 0) / 2
|
|
@@ -10566,8 +10631,8 @@ function runCommentBoxPipeline(graph, options, context) {
|
|
|
10566
10631
|
y: direction === "down" === source ? (targetRect.y ?? 0) + (targetRect.height ?? 0) : targetRect.y ?? 0
|
|
10567
10632
|
};
|
|
10568
10633
|
};
|
|
10569
|
-
if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint
|
|
10570
|
-
if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint
|
|
10634
|
+
if (edge.sourceId === targetId && points.length > 0) points[0] = endpoint(true);
|
|
10635
|
+
if (edge.targetId === targetId && points.length > 0) points[points.length - 1] = endpoint(false);
|
|
10571
10636
|
return [edge.id, {
|
|
10572
10637
|
...edge,
|
|
10573
10638
|
x: horizontal ? edge.x : (edge.x ?? 0) + crossShift,
|
|
@@ -10769,126 +10834,16 @@ function runSeparatedComponents(graph, options, context) {
|
|
|
10769
10834
|
};
|
|
10770
10835
|
}
|
|
10771
10836
|
function runCompoundPipeline(graph, options, context) {
|
|
10772
|
-
const
|
|
10773
|
-
const
|
|
10774
|
-
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
value++;
|
|
10781
|
-
parentId = nodeById.get(parentId)?.parentId;
|
|
10782
|
-
}
|
|
10783
|
-
return value;
|
|
10784
|
-
};
|
|
10785
|
-
const parentIds = [...new Set(nodes.flatMap((node) => node.parentId == null ? [] : [node.parentId]))].sort((left, right) => depth(nodeById.get(right)) - depth(nodeById.get(left)));
|
|
10786
|
-
const edgeById = new Map(graph.edges.map((edge) => [edge.id, { ...edge }]));
|
|
10787
|
-
const padding = typeof options.padding === "number" ? options.padding : 12;
|
|
10788
|
-
const layoutSiblings = (parentId) => {
|
|
10789
|
-
const siblings = nodes.filter((node) => (node.parentId ?? null) === parentId);
|
|
10790
|
-
if (siblings.length === 0) return;
|
|
10791
|
-
const siblingIds = new Set(siblings.map((node) => node.id));
|
|
10792
|
-
const siblingEdges = graph.edges.filter((edge) => siblingIds.has(edge.sourceId) && siblingIds.has(edge.targetId));
|
|
10793
|
-
const flatNodes = siblings.map((node) => ({
|
|
10794
|
-
...node,
|
|
10795
|
-
parentId: null
|
|
10796
|
-
}));
|
|
10797
|
-
const result = runLayeredPipeline({
|
|
10798
|
-
...graph,
|
|
10799
|
-
nodes: flatNodes,
|
|
10800
|
-
edges: siblingEdges
|
|
10801
|
-
}, options, context);
|
|
10802
|
-
for (const laidOut of result.nodes) {
|
|
10803
|
-
const node = nodeById.get(laidOut.id);
|
|
10804
|
-
if (!node) continue;
|
|
10805
|
-
Object.assign(node, laidOut, { parentId });
|
|
10806
|
-
}
|
|
10807
|
-
for (const edge of result.edges) edgeById.set(edge.id, edge);
|
|
10808
|
-
};
|
|
10809
|
-
for (const parentId of parentIds) {
|
|
10810
|
-
layoutSiblings(parentId);
|
|
10811
|
-
const parent = nodeById.get(parentId);
|
|
10812
|
-
if (!parent) continue;
|
|
10813
|
-
const children = nodes.filter((node) => node.parentId === parentId);
|
|
10814
|
-
const right = Math.max(0, ...children.map((node) => (node.x ?? 0) + (node.width ?? 0)));
|
|
10815
|
-
const bottom = Math.max(0, ...children.map((node) => (node.y ?? 0) + (node.height ?? 0)));
|
|
10816
|
-
parent.width = Math.max(parent.width ?? 0, right + padding);
|
|
10817
|
-
parent.height = Math.max(parent.height ?? 0, bottom + padding);
|
|
10818
|
-
}
|
|
10819
|
-
const roots = nodes.filter((node) => node.parentId == null);
|
|
10820
|
-
if (roots.length === 1 && parentIds.includes(roots[0].id)) Object.assign(roots[0], {
|
|
10821
|
-
x: 0,
|
|
10822
|
-
y: 0
|
|
10837
|
+
const result = layoutCompounds(graph, options, (scope, local) => runLayeredPipeline(scope, local, context));
|
|
10838
|
+
for (const route of result.compoundRoutes.values()) for (const diagnostic of route.diagnostics) context?.diagnostics.push({
|
|
10839
|
+
severity: "warning",
|
|
10840
|
+
code: diagnostic.code,
|
|
10841
|
+
message: diagnostic.message,
|
|
10842
|
+
entityIds: [route.edgeId],
|
|
10843
|
+
phase: "compound-routing",
|
|
10844
|
+
geometry: "routes"
|
|
10823
10845
|
});
|
|
10824
|
-
|
|
10825
|
-
const absoluteRect = (id) => {
|
|
10826
|
-
const node = nodeById.get(id);
|
|
10827
|
-
if (!node) return {
|
|
10828
|
-
x: 0,
|
|
10829
|
-
y: 0,
|
|
10830
|
-
width: 0,
|
|
10831
|
-
height: 0
|
|
10832
|
-
};
|
|
10833
|
-
let x = node.x ?? 0;
|
|
10834
|
-
let y = node.y ?? 0;
|
|
10835
|
-
let parentId = node.parentId;
|
|
10836
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10837
|
-
while (parentId != null && !seen.has(parentId)) {
|
|
10838
|
-
seen.add(parentId);
|
|
10839
|
-
const parent = nodeById.get(parentId);
|
|
10840
|
-
if (!parent) break;
|
|
10841
|
-
x += parent.x ?? 0;
|
|
10842
|
-
y += parent.y ?? 0;
|
|
10843
|
-
parentId = parent.parentId;
|
|
10844
|
-
}
|
|
10845
|
-
return {
|
|
10846
|
-
x,
|
|
10847
|
-
y,
|
|
10848
|
-
width: node.width ?? 0,
|
|
10849
|
-
height: node.height ?? 0
|
|
10850
|
-
};
|
|
10851
|
-
};
|
|
10852
|
-
for (const edge of graph.edges) {
|
|
10853
|
-
if (edgeById.get(edge.id)?.points !== void 0) continue;
|
|
10854
|
-
const source = absoluteRect(edge.sourceId);
|
|
10855
|
-
const target = absoluteRect(edge.targetId);
|
|
10856
|
-
const start = {
|
|
10857
|
-
x: source.x + source.width,
|
|
10858
|
-
y: source.y + source.height / 2
|
|
10859
|
-
};
|
|
10860
|
-
const end = {
|
|
10861
|
-
x: target.x,
|
|
10862
|
-
y: target.y + target.height / 2
|
|
10863
|
-
};
|
|
10864
|
-
const track = (start.x + end.x) / 2;
|
|
10865
|
-
const points = [
|
|
10866
|
-
start,
|
|
10867
|
-
{
|
|
10868
|
-
x: track,
|
|
10869
|
-
y: start.y
|
|
10870
|
-
},
|
|
10871
|
-
{
|
|
10872
|
-
x: track,
|
|
10873
|
-
y: end.y
|
|
10874
|
-
},
|
|
10875
|
-
end
|
|
10876
|
-
];
|
|
10877
|
-
edgeById.set(edge.id, {
|
|
10878
|
-
...edge,
|
|
10879
|
-
points,
|
|
10880
|
-
x: track,
|
|
10881
|
-
y: (start.y + end.y) / 2,
|
|
10882
|
-
width: edge.width ?? 0,
|
|
10883
|
-
height: edge.height ?? 0,
|
|
10884
|
-
routing: "orthogonal"
|
|
10885
|
-
});
|
|
10886
|
-
}
|
|
10887
|
-
return {
|
|
10888
|
-
...graph,
|
|
10889
|
-
nodes,
|
|
10890
|
-
edges: graph.edges.map((edge) => edgeById.get(edge.id) ?? edge)
|
|
10891
|
-
};
|
|
10846
|
+
return result;
|
|
10892
10847
|
}
|
|
10893
10848
|
function runLayeredPipeline(graph, options, context) {
|
|
10894
10849
|
if (options.settings?.noLayout) return {
|
|
@@ -10988,7 +10943,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
10988
10943
|
if (cycleBreakingStrategy === "BFS_NODE_ORDER") return breakCyclesWithModelOrderBreadthFirstSearch;
|
|
10989
10944
|
if (cycleBreakingStrategy === "SCC_CONNECTIVITY") return breakCyclesByStronglyConnectedConnectivity;
|
|
10990
10945
|
if (cycleBreakingStrategy === "SCC_NODE_TYPE") return breakCyclesByStronglyConnectedNodeType;
|
|
10991
|
-
throw new UnsupportedLayoutError(`Cycle-breaking strategy ${cycleBreakingStrategy} is not implemented yet`);
|
|
10946
|
+
throw new require_routing.UnsupportedLayoutError(`Cycle-breaking strategy ${cycleBreakingStrategy} is not implemented yet`);
|
|
10992
10947
|
})();
|
|
10993
10948
|
const orientation = measure("cycle-breaking", () => applyPartitionOrientation(input, applyLayerConstraintOrientation(input, cycleBreaker(input))));
|
|
10994
10949
|
const layeringStrategy = options.settings?.["layering.strategy"] ?? "NETWORK_SIMPLEX";
|
|
@@ -11003,7 +10958,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11003
10958
|
if (layeringStrategy === "NETWORK_SIMPLEX") return assignLayersWithNetworkSimplex;
|
|
11004
10959
|
if (layeringStrategy === "MIN_WIDTH") return assignLayersWithMinWidth;
|
|
11005
10960
|
if (layeringStrategy === "STRETCH_WIDTH") return assignLayersWithStretchWidth;
|
|
11006
|
-
throw new UnsupportedLayoutError(`Layering strategy ${layeringStrategy} is not implemented yet`);
|
|
10961
|
+
throw new require_routing.UnsupportedLayoutError(`Layering strategy ${layeringStrategy} is not implemented yet`);
|
|
11007
10962
|
})();
|
|
11008
10963
|
const assignment = measure("layer-assignment", () => applyHighDegreeNodeTreatment(input, orientation, applyNodePromotion(input, orientation, applyPartitions(input, applyLayerConstraints(input, layerAssigner(input, orientation))))));
|
|
11009
10964
|
let expanded = measure("long-edge-splitting", () => splitLongEdges(input, orientation, assignment));
|
|
@@ -11014,7 +10969,7 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11014
10969
|
if (crossingStrategy === "MEDIAN_LAYER_SWEEP") return minimizeCrossingsWithMedian(options.crossingSweeps);
|
|
11015
10970
|
if (crossingStrategy === "INTERACTIVE") return minimizeCrossingsInteractively;
|
|
11016
10971
|
if (crossingStrategy === "NONE") return minimizeCrossingsWithModelOrder;
|
|
11017
|
-
throw new UnsupportedLayoutError(`Crossing-minimization strategy ${crossingStrategy} is not implemented`);
|
|
10972
|
+
throw new require_routing.UnsupportedLayoutError(`Crossing-minimization strategy ${crossingStrategy} is not implemented`);
|
|
11018
10973
|
})();
|
|
11019
10974
|
let order = measure("crossing-minimization", () => applyLayerConstraintOrder(expanded.input, applyGreedySwitch(expanded.input, expanded.orientation, applySemiInteractiveOrder(expanded.input, applyForcedModelOrder(expanded.input, expanded.orientation, crossingMinimizer(expanded.input, expanded.orientation, expanded.assignment))))));
|
|
11020
10975
|
const unzippingFanIn = graph.edges.length === graph.nodes.length - 1 && graph.nodes.some((node) => graph.edges.filter((edge) => edge.targetId === node.id).length === graph.nodes.length - 1);
|
|
@@ -11361,16 +11316,16 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11361
11316
|
const targetDelta = postCompactionNodeCrossDeltas.get(edge.targetId) ?? 0;
|
|
11362
11317
|
const shiftEndpoint = (endpointIndex, adjacentIndex, delta) => {
|
|
11363
11318
|
if (delta === 0) return;
|
|
11364
|
-
const endpoint
|
|
11319
|
+
const endpoint = points[endpointIndex];
|
|
11365
11320
|
const adjacent = points[adjacentIndex];
|
|
11366
|
-
if (!endpoint
|
|
11367
|
-
const originalCross = horizontalAntiparallelFlow ? endpoint
|
|
11321
|
+
if (!endpoint) return;
|
|
11322
|
+
const originalCross = horizontalAntiparallelFlow ? endpoint.y : endpoint.x;
|
|
11368
11323
|
points[endpointIndex] = horizontalAntiparallelFlow ? {
|
|
11369
|
-
...endpoint
|
|
11370
|
-
y: endpoint
|
|
11324
|
+
...endpoint,
|
|
11325
|
+
y: endpoint.y + delta
|
|
11371
11326
|
} : {
|
|
11372
|
-
...endpoint
|
|
11373
|
-
x: endpoint
|
|
11327
|
+
...endpoint,
|
|
11328
|
+
x: endpoint.x + delta
|
|
11374
11329
|
};
|
|
11375
11330
|
if (adjacent && (horizontalAntiparallelFlow ? adjacent.y : adjacent.x) === originalCross) points[adjacentIndex] = horizontalAntiparallelFlow ? {
|
|
11376
11331
|
...adjacent,
|
|
@@ -11460,14 +11415,14 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11460
11415
|
for (const edge of [...incoming, ...outgoing]) {
|
|
11461
11416
|
const points = [...mutableRouteMap.get(edge.id) ?? []];
|
|
11462
11417
|
const endpointIndex = edge.sourceId === node.id ? 0 : points.length - 1;
|
|
11463
|
-
const endpoint
|
|
11464
|
-
if (!endpoint
|
|
11418
|
+
const endpoint = points[endpointIndex];
|
|
11419
|
+
if (!endpoint) continue;
|
|
11465
11420
|
points[endpointIndex] = horizontal ? {
|
|
11466
|
-
...endpoint
|
|
11467
|
-
x: endpoint
|
|
11421
|
+
...endpoint,
|
|
11422
|
+
x: endpoint.x + delta
|
|
11468
11423
|
} : {
|
|
11469
|
-
...endpoint
|
|
11470
|
-
y: endpoint
|
|
11424
|
+
...endpoint,
|
|
11425
|
+
y: endpoint.y + delta
|
|
11471
11426
|
};
|
|
11472
11427
|
mutableRouteMap.set(edge.id, points);
|
|
11473
11428
|
}
|
|
@@ -11572,14 +11527,14 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11572
11527
|
if (points.length === 0) continue;
|
|
11573
11528
|
const index = edge.sourceId === nodeId ? 0 : edge.targetId === nodeId ? points.length - 1 : void 0;
|
|
11574
11529
|
if (index === void 0) continue;
|
|
11575
|
-
const endpoint
|
|
11576
|
-
const originalCross = horizontal ? endpoint
|
|
11530
|
+
const endpoint = points[index];
|
|
11531
|
+
const originalCross = horizontal ? endpoint.y : endpoint.x;
|
|
11577
11532
|
points[index] = horizontal ? {
|
|
11578
|
-
...endpoint
|
|
11579
|
-
y: endpoint
|
|
11533
|
+
...endpoint,
|
|
11534
|
+
y: endpoint.y + delta
|
|
11580
11535
|
} : {
|
|
11581
|
-
...endpoint
|
|
11582
|
-
x: endpoint
|
|
11536
|
+
...endpoint,
|
|
11537
|
+
x: endpoint.x + delta
|
|
11583
11538
|
};
|
|
11584
11539
|
const adjacentIndex = index === 0 ? 1 : points.length - 2;
|
|
11585
11540
|
const adjacent = points[adjacentIndex];
|
|
@@ -11777,7 +11732,12 @@ function runLayeredPipeline(graph, options, context) {
|
|
|
11777
11732
|
* custom phase strategies, and all layered edge-routing styles.
|
|
11778
11733
|
*/
|
|
11779
11734
|
function getLayeredLayout(graph, options = {}) {
|
|
11780
|
-
|
|
11735
|
+
const result = runLayeredPipeline(graph, options);
|
|
11736
|
+
return {
|
|
11737
|
+
...result,
|
|
11738
|
+
compoundGeometry: result.compoundGeometry ?? /* @__PURE__ */ new Map(),
|
|
11739
|
+
compoundRoutes: result.compoundRoutes ?? /* @__PURE__ */ new Map()
|
|
11740
|
+
};
|
|
11781
11741
|
}
|
|
11782
11742
|
const layeredAlgorithm = {
|
|
11783
11743
|
id: "layered",
|
|
@@ -11792,7 +11752,7 @@ const layeredAlgorithm = {
|
|
|
11792
11752
|
},
|
|
11793
11753
|
layout(graph, options, context) {
|
|
11794
11754
|
if (context.scope.mode === "partial" || context.scope.mode === "route-only") return context.measurePhase("partial-layout", () => runPartialLayout(graph, options ?? {}, context, getLayeredLayout));
|
|
11795
|
-
if (context.constraints?.length && graph.nodes.some((node) => node.parentId != null)) throw new UnsupportedLayoutError("Full layout with geometry constraints does not support containers; use partial leaf constraints or full layout without geometry constraints");
|
|
11755
|
+
if (context.constraints?.length && graph.nodes.some((node) => node.parentId != null)) throw new require_routing.UnsupportedLayoutError("Full layout with geometry constraints does not support containers; use partial leaf constraints or full layout without geometry constraints");
|
|
11796
11756
|
const result = runLayeredPipeline(graph, options ?? {}, context);
|
|
11797
11757
|
if (!context.constraints?.length) return result;
|
|
11798
11758
|
return context.measurePhase("constraints", () => runPartialLayout(result, options ?? {}, {
|
|
@@ -11813,18 +11773,6 @@ Object.defineProperty(exports, 'JavaRandom', {
|
|
|
11813
11773
|
return JavaRandom;
|
|
11814
11774
|
}
|
|
11815
11775
|
});
|
|
11816
|
-
Object.defineProperty(exports, 'LayoutError', {
|
|
11817
|
-
enumerable: true,
|
|
11818
|
-
get: function () {
|
|
11819
|
-
return LayoutError;
|
|
11820
|
-
}
|
|
11821
|
-
});
|
|
11822
|
-
Object.defineProperty(exports, 'UnsupportedLayoutError', {
|
|
11823
|
-
enumerable: true,
|
|
11824
|
-
get: function () {
|
|
11825
|
-
return UnsupportedLayoutError;
|
|
11826
|
-
}
|
|
11827
|
-
});
|
|
11828
11776
|
Object.defineProperty(exports, 'assignLayersByBreadthFirstModelOrder', {
|
|
11829
11777
|
enumerable: true,
|
|
11830
11778
|
get: function () {
|