@crazyhappyone/auto-graph 0.2.0 → 0.2.2

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/dist/index.cjs CHANGED
@@ -79,6 +79,17 @@ function intersectsAabb(a, b) {
79
79
  validateBox(b, "b");
80
80
  return a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
81
81
  }
82
+ function overlapArea(first, second) {
83
+ const x = Math.max(
84
+ 0,
85
+ Math.min(first.x + first.width, second.x + second.width) - Math.max(first.x, second.x)
86
+ );
87
+ const y = Math.max(
88
+ 0,
89
+ Math.min(first.y + first.height, second.y + second.height) - Math.max(first.y, second.y)
90
+ );
91
+ return x * y;
92
+ }
82
93
  function validateMargin(value, label) {
83
94
  validateFinite(value, label);
84
95
  if (value < 0) {
@@ -91,6 +102,72 @@ function validateFinite(value, label) {
91
102
  }
92
103
  }
93
104
 
105
+ // src/geometry/spatial-index.ts
106
+ function createBoxSpatialIndex(entries, cellSize = 128) {
107
+ const normalizedCellSize = Number.isFinite(cellSize) && cellSize > 0 ? cellSize : 128;
108
+ const boxes = /* @__PURE__ */ new Map();
109
+ const mutableCells = /* @__PURE__ */ new Map();
110
+ for (const entry of entries) {
111
+ boxes.set(entry.id, { ...entry.box });
112
+ for (const key of cellKeysForBox(entry.box, normalizedCellSize)) {
113
+ const ids = mutableCells.get(key) ?? [];
114
+ ids.push(entry.id);
115
+ mutableCells.set(key, ids);
116
+ }
117
+ }
118
+ const cells = /* @__PURE__ */ new Map();
119
+ for (const [key, ids] of mutableCells) {
120
+ cells.set(key, [...new Set(ids)].sort());
121
+ }
122
+ return { cellSize: normalizedCellSize, entries: boxes, cells };
123
+ }
124
+ function queryBoxSpatialIndex(index, box) {
125
+ const ids = /* @__PURE__ */ new Set();
126
+ for (const key of cellKeysForBox(box, index.cellSize)) {
127
+ for (const id of index.cells.get(key) ?? []) {
128
+ ids.add(id);
129
+ }
130
+ }
131
+ return [...ids].sort().flatMap((id) => {
132
+ const candidate = index.entries.get(id);
133
+ return candidate !== void 0 && intersectsAabb(candidate, box) ? [{ id, box: candidate }] : [];
134
+ });
135
+ }
136
+ function querySegmentSpatialIndex(index, start, end) {
137
+ return queryBoxSpatialIndex(index, segmentBox(start, end));
138
+ }
139
+ function expandBoxForQuery(box, margin) {
140
+ return {
141
+ x: box.x - margin,
142
+ y: box.y - margin,
143
+ width: box.width + margin * 2,
144
+ height: box.height + margin * 2
145
+ };
146
+ }
147
+ function cellKeysForBox(box, cellSize) {
148
+ const minCol = Math.floor(box.x / cellSize);
149
+ const maxCol = Math.floor((box.x + Math.max(1, box.width)) / cellSize);
150
+ const minRow = Math.floor(box.y / cellSize);
151
+ const maxRow = Math.floor((box.y + Math.max(1, box.height)) / cellSize);
152
+ const keys = [];
153
+ for (let col = minCol; col <= maxCol; col += 1) {
154
+ for (let row = minRow; row <= maxRow; row += 1) {
155
+ keys.push(`${col}:${row}`);
156
+ }
157
+ }
158
+ return keys;
159
+ }
160
+ function segmentBox(start, end) {
161
+ const x = Math.min(start.x, end.x);
162
+ const y = Math.min(start.y, end.y);
163
+ return {
164
+ x,
165
+ y,
166
+ width: Math.max(1, Math.abs(start.x - end.x)),
167
+ height: Math.max(1, Math.abs(start.y - end.y))
168
+ };
169
+ }
170
+
94
171
  // src/constraints/solver.ts
95
172
  function applyLayoutConstraints(input) {
96
173
  const diagnostics = [];
@@ -122,7 +199,12 @@ function applyLayoutConstraints(input) {
122
199
  dedupReplayDiagnostics(diagnostics, diagBefore);
123
200
  }
124
201
  removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
125
- reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
202
+ reportOverlaps(
203
+ boxes,
204
+ diagnostics,
205
+ containmentOverlapKeys(input.constraints),
206
+ locks
207
+ );
126
208
  reportIntraContainerOverflow(input, boxes, diagnostics);
127
209
  return { boxes, locks, diagnostics };
128
210
  }
@@ -288,14 +370,19 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
288
370
  if (samePosition(child, next)) {
289
371
  continue;
290
372
  }
291
- if (locks.has(childId)) {
373
+ const lock = locks.get(childId);
374
+ if (lock !== void 0) {
292
375
  if (!reportOverflow) {
293
376
  diagnostics.push({
294
377
  severity: "warning",
295
378
  code: "constraints.locked-target-not-moved",
296
379
  message: `Locked child ${childId} was not moved into containment.`,
297
380
  path: ["constraints", constraint.id ?? constraint.containerId],
298
- detail: { nodeId: childId, containerId: constraint.containerId }
381
+ detail: {
382
+ nodeId: childId,
383
+ containerId: constraint.containerId,
384
+ lockSource: lock.source
385
+ }
299
386
  });
300
387
  if (!isInside(child, content)) {
301
388
  diagnostics.push({
@@ -410,18 +497,29 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
410
497
  const secondaryAxis = axis === "x" ? "y" : "x";
411
498
  const ignoredPairs = containmentOverlapKeys(input.constraints);
412
499
  const ids = [...boxes.keys()].sort();
500
+ const index = createBoxSpatialIndex(
501
+ ids.flatMap((id) => {
502
+ const box = boxes.get(id);
503
+ return box === void 0 ? [] : [{ id, box }];
504
+ }),
505
+ spacing
506
+ );
413
507
  for (let pass = 0; pass < 2; pass += 1) {
414
508
  for (const firstId of ids) {
415
- for (const secondId of ids) {
509
+ const first = boxes.get(firstId);
510
+ if (first === void 0) {
511
+ continue;
512
+ }
513
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
514
+ for (const secondId of candidateIds) {
416
515
  if (firstId >= secondId) {
417
516
  continue;
418
517
  }
419
518
  if (ignoredPairs.has(overlapKey(firstId, secondId))) {
420
519
  continue;
421
520
  }
422
- const first = boxes.get(firstId);
423
521
  const second = boxes.get(secondId);
424
- if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
522
+ if (second === void 0 || !intersectsAabb(first, second)) {
425
523
  continue;
426
524
  }
427
525
  const firstLocked = locks.has(firstId);
@@ -445,7 +543,7 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
445
543
  }
446
544
  }
447
545
  }
448
- reportOverlaps(boxes, diagnostics, ignoredPairs);
546
+ reportOverlaps(boxes, diagnostics, ignoredPairs, locks);
449
547
  }
450
548
  function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
451
549
  for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
@@ -501,29 +599,56 @@ function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
501
599
  }
502
600
  }
503
601
  }
504
- function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
602
+ function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set(), locks = /* @__PURE__ */ new Map()) {
505
603
  const ids = [...boxes.keys()].sort();
506
604
  const reported = new Set(
507
605
  diagnostics.filter(
508
- (diagnostic) => diagnostic.code === "constraints.overlap.unresolved"
606
+ (diagnostic) => diagnostic.code === "constraints.overlap.unresolved" || diagnostic.code === "constraints.overlap.locked-conflict"
509
607
  ).map((diagnostic) => {
510
608
  const firstId = diagnostic.detail?.firstId;
511
609
  const secondId = diagnostic.detail?.secondId;
512
610
  return typeof firstId === "string" && typeof secondId === "string" ? overlapKey(firstId, secondId) : void 0;
513
611
  }).filter((key) => key !== void 0)
514
612
  );
613
+ const index = createBoxSpatialIndex(
614
+ ids.flatMap((id) => {
615
+ const box = boxes.get(id);
616
+ return box === void 0 ? [] : [{ id, box }];
617
+ }),
618
+ 40
619
+ );
515
620
  for (const firstId of ids) {
516
- for (const secondId of ids) {
517
- if (firstId >= secondId) {
518
- continue;
519
- }
621
+ const first = boxes.get(firstId);
622
+ if (first === void 0) {
623
+ continue;
624
+ }
625
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
626
+ for (const secondId of candidateIds) {
520
627
  const key = overlapKey(firstId, secondId);
521
628
  if (reported.has(key) || ignoredPairs.has(key)) {
522
629
  continue;
523
630
  }
524
- const first = boxes.get(firstId);
525
631
  const second = boxes.get(secondId);
526
- if (first !== void 0 && second !== void 0 && intersectsAabb(first, second)) {
632
+ if (second !== void 0 && intersectsAabb(first, second)) {
633
+ const firstLock = locks.get(firstId);
634
+ const secondLock = locks.get(secondId);
635
+ if (firstLock !== void 0 && secondLock !== void 0) {
636
+ diagnostics.push({
637
+ severity: "warning",
638
+ code: "constraints.overlap.locked-conflict",
639
+ message: `Locked boxes ${firstId} (${firstLock.source}) and ${secondId} (${secondLock.source}) overlap and cannot be repaired.`,
640
+ path: ["boxes"],
641
+ detail: {
642
+ firstId,
643
+ secondId,
644
+ firstLockSource: firstLock.source,
645
+ secondLockSource: secondLock.source,
646
+ overlapArea: overlapArea(first, second)
647
+ }
648
+ });
649
+ reported.add(key);
650
+ continue;
651
+ }
527
652
  diagnostics.push({
528
653
  severity: "warning",
529
654
  code: "constraints.overlap.unresolved",
@@ -679,12 +804,17 @@ function setUnlockedBox(id, next, boxes, locks, diagnostics, constraint) {
679
804
  return;
680
805
  }
681
806
  if (locks.has(id) && !samePosition(current, next)) {
807
+ const lock = locks.get(id);
682
808
  diagnostics.push({
683
809
  severity: "warning",
684
810
  code: "constraints.locked-target-not-moved",
685
811
  message: `Locked target ${id} was not moved by ${constraint.kind}.`,
686
812
  path: ["constraints", constraint.id ?? id],
687
- detail: { nodeId: id, constraintKind: constraint.kind }
813
+ detail: {
814
+ nodeId: id,
815
+ constraintKind: constraint.kind,
816
+ ...lock === void 0 ? {} : { lockSource: lock.source }
817
+ }
688
818
  });
689
819
  return;
690
820
  }
@@ -853,7 +983,28 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
853
983
  if (distributable.length < 2) {
854
984
  continue;
855
985
  }
986
+ const spread = typeof input.distributeContainedChildren === "string";
987
+ let effectiveGap = minGap;
856
988
  let pos = content[axis];
989
+ if (spread) {
990
+ let totalChildSpan = 0;
991
+ for (const child of distributable) {
992
+ totalChildSpan += child.box[mainSize];
993
+ }
994
+ let reservedSpan = 0;
995
+ const contentEnd = content[axis] + content[mainSize];
996
+ for (const r of reserved) {
997
+ const rStart = Math.max(r.start, content[axis]);
998
+ const rEnd = Math.min(r.end, contentEnd);
999
+ if (rEnd > rStart) {
1000
+ reservedSpan += rEnd - rStart + minGap;
1001
+ }
1002
+ }
1003
+ const remaining = content[mainSize] - totalChildSpan - reservedSpan - minGap * (distributable.length - 1);
1004
+ if (remaining > 0) {
1005
+ effectiveGap = minGap + remaining / (distributable.length - 1);
1006
+ }
1007
+ }
857
1008
  for (const child of distributable) {
858
1009
  pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
859
1010
  const crossPos = content[crossAxis] + Math.max(0, (content[crossSize] - child.box[crossSize]) / 2);
@@ -872,7 +1023,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
872
1023
  }
873
1024
  boxes.set(child.id, clamped);
874
1025
  locks.delete(child.id);
875
- pos = clamped[axis] + clamped[mainSize] + minGap;
1026
+ pos = clamped[axis] + clamped[mainSize] + effectiveGap;
876
1027
  }
877
1028
  diagnostics.push({
878
1029
  severity: "info",
@@ -1628,6 +1779,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1628
1779
  const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1629
1780
  const routeKind = dsl.routing?.kind ?? "orthogonal";
1630
1781
  const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1782
+ const initialLayout = dsl.layout?.mode;
1631
1783
  const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
1632
1784
  const matrices = normalizeMatrices(dsl);
1633
1785
  const tables = normalizeTables(dsl);
@@ -1648,6 +1800,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1648
1800
  ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
1649
1801
  metadata: {
1650
1802
  routeKind,
1803
+ ...initialLayout === void 0 ? {} : { initialLayout },
1651
1804
  ...primaryReadingDirection === void 0 ? {} : { primaryReadingDirection },
1652
1805
  ...portShifting === void 0 ? {} : { portShifting }
1653
1806
  }
@@ -2197,6 +2350,7 @@ function point(value) {
2197
2350
  return { x: value.x, y: value.y };
2198
2351
  }
2199
2352
  var directionSchema = zod.z.enum(["TB", "LR", "BT", "RL"]);
2353
+ var layoutModeSchema = zod.z.enum(["dagre", "positions"]);
2200
2354
  var routeKindSchema = zod.z.enum(["orthogonal", "straight", "obstacle-avoiding"]);
2201
2355
  var outputFormatSchema = zod.z.enum(["svg", "excalidraw"]);
2202
2356
  var edgeStrokeStyleSchema = zod.z.enum(["solid", "dashed"]);
@@ -2507,6 +2661,7 @@ var diagramDslSchema = zod.z.object({
2507
2661
  direction: directionSchema.optional(),
2508
2662
  layout: zod.z.object({
2509
2663
  direction: directionSchema.optional(),
2664
+ mode: layoutModeSchema.optional(),
2510
2665
  primaryReadingDirection: primaryReadingDirectionSchema.optional()
2511
2666
  }).optional(),
2512
2667
  routing: zod.z.object({
@@ -2843,13 +2998,22 @@ function exportExcalidraw(diagram, options = {}) {
2843
2998
  appState: {
2844
2999
  name: options.title ?? diagram.title ?? diagram.id,
2845
3000
  viewBackgroundColor: "#ffffff",
2846
- gridSize: null
3001
+ gridSize: null,
3002
+ ...options.viewportPadding === void 0 ? {} : viewportAppState(diagram.bounds, options.viewportPadding)
2847
3003
  },
2848
3004
  files: {}
2849
3005
  };
2850
3006
  return `${JSON.stringify(scene, null, 2)}
2851
3007
  `;
2852
3008
  }
3009
+ function viewportAppState(bounds, padding) {
3010
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3011
+ return {
3012
+ scrollX: finite(-bounds.x + safePadding),
3013
+ scrollY: finite(-bounds.y + safePadding),
3014
+ zoom: { value: 1 }
3015
+ };
3016
+ }
2853
3017
  function renderGroup(group) {
2854
3018
  return {
2855
3019
  ...baseElement(`group:${group.id}`, "rectangle", group.box),
@@ -3173,6 +3337,9 @@ function exportSvg(diagram, options = {}) {
3173
3337
  return `${[
3174
3338
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
3175
3339
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
3340
+ ...options.viewportPadding === void 0 ? [] : [
3341
+ ` <metadata data-dge-viewport="${escapeAttribute(viewportMetadata(diagram.bounds, options.viewportPadding))}"></metadata>`
3342
+ ],
3176
3343
  ` <rect class="background" x="${formatNumber(diagram.bounds.x)}" y="${formatNumber(diagram.bounds.y)}" width="${formatNumber(diagram.bounds.width)}" height="${formatNumber(diagram.bounds.height)}" fill="#ffffff"/>`,
3177
3344
  ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
3178
3345
  ...(diagram.swimlanes ?? []).flatMap(
@@ -3206,6 +3373,16 @@ function exportSvg(diagram, options = {}) {
3206
3373
  ].join("\n")}
3207
3374
  `;
3208
3375
  }
3376
+ function viewportMetadata(bounds, padding) {
3377
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3378
+ return JSON.stringify({
3379
+ x: bounds.x - safePadding,
3380
+ y: bounds.y - safePadding,
3381
+ width: bounds.width + safePadding * 2,
3382
+ height: bounds.height + safePadding * 2,
3383
+ padding: safePadding
3384
+ });
3385
+ }
3209
3386
  function renderGroup2(group) {
3210
3387
  return `<rect class="group" data-id="${escapeAttribute(group.id)}" x="${formatNumber(group.box.x)}" y="${formatNumber(group.box.y)}" width="${formatNumber(group.box.width)}" height="${formatNumber(group.box.height)}" fill="${GROUP_FILL}" stroke="${STROKE}" stroke-dasharray="6 4"/>`;
3211
3388
  }
@@ -3880,6 +4057,7 @@ function indentLines(values) {
3880
4057
  // src/ir/diagnostics.ts
3881
4058
  var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
3882
4059
  "constraints.locked-target-not-moved",
4060
+ "constraints.overlap.locked-conflict",
3883
4061
  "routing.evidence.crossing_forbidden",
3884
4062
  "routing.obstacle.unavoidable",
3885
4063
  "route_obstacle_fallback",
@@ -3891,6 +4069,7 @@ var DEFAULT_OPTIONS = {
3891
4069
  edgesep: 40,
3892
4070
  marginx: 0,
3893
4071
  marginy: 0,
4072
+ componentGap: 160,
3894
4073
  ranker: "network-simplex"
3895
4074
  };
3896
4075
  function runDagreInitialLayout(input) {
@@ -3979,20 +4158,137 @@ function runDagreInitialLayout(input) {
3979
4158
  }
3980
4159
  return { boxes, diagnostics };
3981
4160
  }
4161
+ function runComponentAwareDagreInitialLayout(input) {
4162
+ const options = { ...DEFAULT_OPTIONS, ...input.options };
4163
+ const diagnostics = reportMissingEdgeReferences(input);
4164
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4165
+ const validEdges = input.edges.filter(
4166
+ (edge) => validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)
4167
+ );
4168
+ const components = connectedComponents(input.nodes, validEdges);
4169
+ if (components.length <= 1) {
4170
+ const layout2 = runDagreInitialLayout({ ...input, edges: validEdges });
4171
+ return {
4172
+ boxes: layout2.boxes,
4173
+ diagnostics: [...diagnostics, ...layout2.diagnostics]
4174
+ };
4175
+ }
4176
+ const boxes = /* @__PURE__ */ new Map();
4177
+ let cursor = 0;
4178
+ for (const component of components) {
4179
+ const componentNodeIds = new Set(component.map((node) => node.id));
4180
+ const componentLayout = runDagreInitialLayout({
4181
+ ...input,
4182
+ nodes: component,
4183
+ edges: validEdges.filter(
4184
+ (edge) => componentNodeIds.has(edge.sourceId) && componentNodeIds.has(edge.targetId)
4185
+ )
4186
+ });
4187
+ diagnostics.push(...componentLayout.diagnostics);
4188
+ if (componentLayout.boxes.size === 0) {
4189
+ continue;
4190
+ }
4191
+ const bounds = unionBoxes([...componentLayout.boxes.values()]);
4192
+ const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
4193
+ const dx = axis === "x" ? cursor - bounds.x : -bounds.x;
4194
+ const dy = axis === "y" ? cursor - bounds.y : -bounds.y;
4195
+ for (const [id, box] of componentLayout.boxes) {
4196
+ boxes.set(id, { ...box, x: box.x + dx, y: box.y + dy });
4197
+ }
4198
+ cursor += (axis === "x" ? bounds.width : bounds.height) + options.componentGap;
4199
+ }
4200
+ return { boxes, diagnostics };
4201
+ }
4202
+ function reportMissingEdgeReferences(input) {
4203
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4204
+ return input.edges.flatMap((edge) => {
4205
+ if (validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)) {
4206
+ return [];
4207
+ }
4208
+ return [
4209
+ {
4210
+ severity: "error",
4211
+ code: "layout.edge-reference.missing",
4212
+ message: `Edge ${edge.id} references a missing layout node.`,
4213
+ path: ["edges", edge.id],
4214
+ detail: {
4215
+ edgeId: edge.id,
4216
+ sourceId: edge.sourceId,
4217
+ targetId: edge.targetId
4218
+ }
4219
+ }
4220
+ ];
4221
+ });
4222
+ }
3982
4223
  function isValidDimension(value) {
3983
4224
  return Number.isFinite(value) && value >= 0;
3984
4225
  }
4226
+ function connectedComponents(nodes, edges) {
4227
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
4228
+ const adjacency = new Map(nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
4229
+ for (const edge of edges) {
4230
+ if (!nodeById.has(edge.sourceId) || !nodeById.has(edge.targetId)) {
4231
+ continue;
4232
+ }
4233
+ adjacency.get(edge.sourceId)?.add(edge.targetId);
4234
+ adjacency.get(edge.targetId)?.add(edge.sourceId);
4235
+ }
4236
+ const visited = /* @__PURE__ */ new Set();
4237
+ const components = [];
4238
+ for (const node of [...nodes].sort((a, b) => a.id.localeCompare(b.id))) {
4239
+ if (visited.has(node.id)) {
4240
+ continue;
4241
+ }
4242
+ const ids = [];
4243
+ const stack = [node.id];
4244
+ visited.add(node.id);
4245
+ while (stack.length > 0) {
4246
+ const id = stack.pop();
4247
+ if (id === void 0) {
4248
+ continue;
4249
+ }
4250
+ ids.push(id);
4251
+ for (const neighbor of [...adjacency.get(id) ?? []].sort().reverse()) {
4252
+ if (!visited.has(neighbor)) {
4253
+ visited.add(neighbor);
4254
+ stack.push(neighbor);
4255
+ }
4256
+ }
4257
+ }
4258
+ components.push(
4259
+ ids.sort().flatMap((id) => {
4260
+ const componentNode = nodeById.get(id);
4261
+ return componentNode === void 0 ? [] : [componentNode];
4262
+ })
4263
+ );
4264
+ }
4265
+ return components.sort((a, b) => {
4266
+ const left = a[0]?.id ?? "";
4267
+ const right = b[0]?.id ?? "";
4268
+ return left.localeCompare(right);
4269
+ });
4270
+ }
3985
4271
 
3986
4272
  // src/routing/astar.ts
3987
- function findObstacleFreePath(source, target, obstacles, options = {}) {
4273
+ function findObstacleFreePath(source, target, obstacles, options = {}, diagnostics) {
3988
4274
  const margin = options.margin ?? 0;
3989
4275
  const turnPenalty = options.turnPenalty ?? 50;
3990
4276
  const segmentPenalty = options.segmentPenalty ?? 1;
3991
4277
  const endpointObstacles = options.endpointObstacles ?? [];
3992
- const maxNodes = options.maxNodes ?? 4e3;
4278
+ const maxNodes = options.maxNodes ?? (obstacles.length > 30 ? 16e3 : 4e3);
3993
4279
  const xs = collectXs(source, target, obstacles, margin);
3994
4280
  const ys = collectYs(source, target, obstacles, margin);
3995
4281
  if (xs.length * ys.length > maxNodes) {
4282
+ diagnostics?.push({
4283
+ severity: "warning",
4284
+ code: "routing.astar.grid_overflow",
4285
+ message: `A* grid overflow: ${xs.length * ys.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
4286
+ detail: {
4287
+ xsCount: xs.length,
4288
+ ysCount: ys.length,
4289
+ maxNodes
4290
+ }
4291
+ });
3996
4292
  return null;
3997
4293
  }
3998
4294
  const { nodes, nodeIndex } = buildGraph(xs, ys);
@@ -4010,24 +4306,54 @@ function findObstacleFreePath(source, target, obstacles, options = {}) {
4010
4306
  return simplifyRoute(path);
4011
4307
  }
4012
4308
  function collectXs(source, target, obstacles, margin) {
4013
- const set = /* @__PURE__ */ new Set();
4014
- set.add(source.x);
4015
- set.add(target.x);
4309
+ const raw = [];
4016
4310
  for (const obs of obstacles) {
4017
- set.add(obs.x - margin - 2);
4018
- set.add(obs.x + obs.width + margin + 2);
4311
+ raw.push(obs.x - margin - 2, obs.x + obs.width + margin + 2);
4312
+ }
4313
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4314
+ for (const v of [source.x, target.x]) {
4315
+ if (!deduped.includes(v)) {
4316
+ deduped.push(v);
4317
+ }
4019
4318
  }
4020
- return [...set].sort((a, b) => a - b);
4319
+ return deduped.sort((a, b) => a - b);
4021
4320
  }
4022
4321
  function collectYs(source, target, obstacles, margin) {
4023
- const set = /* @__PURE__ */ new Set();
4024
- set.add(source.y);
4025
- set.add(target.y);
4322
+ const raw = [];
4026
4323
  for (const obs of obstacles) {
4027
- set.add(obs.y - margin - 2);
4028
- set.add(obs.y + obs.height + margin + 2);
4324
+ raw.push(obs.y - margin - 2, obs.y + obs.height + margin + 2);
4029
4325
  }
4030
- return [...set].sort((a, b) => a - b);
4326
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4327
+ for (const v of [source.y, target.y]) {
4328
+ if (!deduped.includes(v)) {
4329
+ deduped.push(v);
4330
+ }
4331
+ }
4332
+ return deduped.sort((a, b) => a - b);
4333
+ }
4334
+ function dedupSorted(values) {
4335
+ const sorted = [...values].sort((a, b) => a - b);
4336
+ const result = [];
4337
+ for (const v of sorted) {
4338
+ const last = result[result.length - 1];
4339
+ if (last === void 0 || v - last > 2) {
4340
+ result.push(v);
4341
+ }
4342
+ }
4343
+ return result;
4344
+ }
4345
+ function insertChannelMidpoints(sorted, minGap = 8) {
4346
+ const result = [];
4347
+ for (let i = 0; i < sorted.length - 1; i++) {
4348
+ const a = sorted[i];
4349
+ const b = sorted[i + 1];
4350
+ result.push(a);
4351
+ if (b - a > minGap) {
4352
+ result.push((a + b) / 2);
4353
+ }
4354
+ }
4355
+ result.push(sorted[sorted.length - 1]);
4356
+ return result.sort((a, b) => a - b);
4031
4357
  }
4032
4358
  function buildGraph(xs, ys) {
4033
4359
  const nodes = [];
@@ -4191,10 +4517,36 @@ function areCollinear(a, b, c) {
4191
4517
  }
4192
4518
 
4193
4519
  // src/routing/routes.ts
4520
+ function checkBacktracking(points, source, target, diagnostics) {
4521
+ if (points.length < 2) return;
4522
+ const direct = Math.hypot(target.x - source.x, target.y - source.y);
4523
+ if (direct <= 0) return;
4524
+ let routeLen = 0;
4525
+ for (let i = 0; i < points.length - 1; i++) {
4526
+ const a = points[i];
4527
+ const b = points[i + 1];
4528
+ routeLen += Math.hypot(b.x - a.x, b.y - a.y);
4529
+ }
4530
+ const threshold = 10;
4531
+ if (routeLen > direct * threshold) {
4532
+ diagnostics.push({
4533
+ severity: "warning",
4534
+ code: "routing.backtracking_excessive",
4535
+ message: `Route length ${Math.round(routeLen)} px exceeds ${threshold}\xD7 direct distance ${Math.round(direct)} px.`,
4536
+ detail: {
4537
+ routeLength: Math.round(routeLen),
4538
+ directDistance: Math.round(direct),
4539
+ threshold
4540
+ }
4541
+ });
4542
+ }
4543
+ }
4194
4544
  function routeEdge(input) {
4195
4545
  const diagnostics = [];
4196
4546
  const softObstacles = input.obstacles ?? [];
4197
4547
  const hardObstacles = input.hardObstacles ?? [];
4548
+ const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
4549
+ const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
4198
4550
  const maxAttempts = input.maxRoutingAttempts ?? 5;
4199
4551
  const defaultAnchors = defaultAnchorsForGeometry(
4200
4552
  input.source.box,
@@ -4216,9 +4568,11 @@ function routeEdge(input) {
4216
4568
  [source, target],
4217
4569
  softObstacles,
4218
4570
  hardObstacles,
4219
- diagnostics
4571
+ diagnostics,
4572
+ softObstacleIndex,
4573
+ hardObstacleIndex
4220
4574
  );
4221
- if (routeCrossesBoxes(points, hardObstacles)) {
4575
+ if (routeCrossesBoxes(points, hardObstacles, hardObstacleIndex)) {
4222
4576
  diagnostics.push({
4223
4577
  severity: "error",
4224
4578
  code: "routing.evidence.crossing_forbidden",
@@ -4226,7 +4580,7 @@ function routeEdge(input) {
4226
4580
  });
4227
4581
  return { points, diagnostics };
4228
4582
  }
4229
- if (routeCrossesBoxes(points, softObstacles)) {
4583
+ if (routeCrossesBoxes(points, softObstacles, softObstacleIndex)) {
4230
4584
  diagnostics.push({
4231
4585
  severity: "warning",
4232
4586
  code: "routing.obstacle.unavoidable",
@@ -4257,16 +4611,24 @@ function routeEdge(input) {
4257
4611
  [...softObstacles, ...hardObstacles],
4258
4612
  {
4259
4613
  endpointObstacles
4260
- }
4614
+ },
4615
+ diagnostics
4261
4616
  );
4262
4617
  if (path !== null && path.length >= 2) {
4263
4618
  const finalized = finalizeRoute(
4264
4619
  path,
4265
4620
  softObstacles,
4266
4621
  hardObstacles,
4267
- diagnostics
4622
+ diagnostics,
4623
+ softObstacleIndex,
4624
+ hardObstacleIndex
4268
4625
  );
4269
- if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
4626
+ if (!routeIntersectsObstacles(
4627
+ finalized,
4628
+ softObstacles,
4629
+ softObstacleIndex
4630
+ ) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
4631
+ checkBacktracking(finalized, source, target, diagnostics);
4270
4632
  return { points: finalized, diagnostics };
4271
4633
  }
4272
4634
  }
@@ -4306,23 +4668,41 @@ function routeEdge(input) {
4306
4668
  }
4307
4669
  );
4308
4670
  for (const candidate of candidateRoutes) {
4309
- if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4671
+ if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(
4672
+ candidate.points,
4673
+ softObstacles,
4674
+ softObstacleIndex
4675
+ ) && !routeIntersectsObstacles(
4676
+ candidate.points,
4677
+ hardObstacles,
4678
+ hardObstacleIndex
4679
+ ) && !routeIntersectsEndpointInteriors(
4310
4680
  candidate.points,
4311
4681
  candidate.endpointObstacles
4312
4682
  )) {
4313
- return {
4314
- points: finalizeRoute(
4315
- candidate.points,
4316
- softObstacles,
4317
- hardObstacles,
4318
- diagnostics
4319
- ),
4683
+ const finalizedClean = finalizeRoute(
4684
+ candidate.points,
4685
+ softObstacles,
4686
+ hardObstacles,
4687
+ diagnostics,
4688
+ softObstacleIndex,
4689
+ hardObstacleIndex
4690
+ );
4691
+ checkBacktracking(
4692
+ finalizedClean,
4693
+ candidate.points[0],
4694
+ candidate.points[candidate.points.length - 1],
4320
4695
  diagnostics
4321
- };
4696
+ );
4697
+ return { points: finalizedClean, diagnostics };
4322
4698
  }
4323
4699
  }
4324
4700
  const hardClearCandidate = candidateRoutes.find(
4325
- (candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4701
+ (candidate) => !routeIntersectsObstacles(
4702
+ candidate.points,
4703
+ hardObstacles,
4704
+ hardObstacleIndex
4705
+ ) && !routeIntersectsEndpointInteriors(
4326
4706
  candidate.points,
4327
4707
  candidate.endpointObstacles
4328
4708
  )
@@ -4473,13 +4853,21 @@ function routeEdge(input) {
4473
4853
  diagnostics
4474
4854
  };
4475
4855
  }
4476
- function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4856
+ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics, softObstacleIndex, hardObstacleIndex) {
4477
4857
  const simplified = simplifyRoute2(points);
4478
4858
  if (simplified.length >= 3) {
4479
4859
  return simplified;
4480
4860
  }
4481
- const crossesHardObstacles = routeCrossesBoxes(simplified, hardObstacles);
4482
- const crossesSoftObstacles = routeCrossesBoxes(simplified, softObstacles);
4861
+ const crossesHardObstacles = routeCrossesBoxes(
4862
+ simplified,
4863
+ hardObstacles,
4864
+ hardObstacleIndex
4865
+ );
4866
+ const crossesSoftObstacles = routeCrossesBoxes(
4867
+ simplified,
4868
+ softObstacles,
4869
+ softObstacleIndex
4870
+ );
4483
4871
  if (!crossesHardObstacles && !crossesSoftObstacles) {
4484
4872
  return simplified;
4485
4873
  }
@@ -4487,8 +4875,16 @@ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4487
4875
  ...softObstacles,
4488
4876
  ...hardObstacles
4489
4877
  ]);
4490
- const expandedCrossesHard = routeCrossesBoxes(expanded, hardObstacles);
4491
- const expandedCrossesSoft = routeCrossesBoxes(expanded, softObstacles);
4878
+ const expandedCrossesHard = routeCrossesBoxes(
4879
+ expanded,
4880
+ hardObstacles,
4881
+ hardObstacleIndex
4882
+ );
4883
+ const expandedCrossesSoft = routeCrossesBoxes(
4884
+ expanded,
4885
+ softObstacles,
4886
+ softObstacleIndex
4887
+ );
4492
4888
  if (expandedCrossesHard || expandedCrossesSoft) {
4493
4889
  diagnostics.push({
4494
4890
  severity: expandedCrossesHard ? "error" : "warning",
@@ -4930,15 +5326,20 @@ function sortedUniqueLanes(lanes, midpoint) {
4930
5326
  return distance === 0 ? left - right : distance;
4931
5327
  });
4932
5328
  }
4933
- function routeIntersectsObstacles(points, obstacles) {
4934
- for (let index = 0; index < points.length - 1; index += 1) {
4935
- const a = points[index];
4936
- const b = points[index + 1];
5329
+ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
5330
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5331
+ const a = points[pointIndex];
5332
+ const b = points[pointIndex + 1];
4937
5333
  if (a === void 0 || b === void 0) {
4938
5334
  continue;
4939
5335
  }
4940
- const segment = segmentBox(a, b);
4941
- for (const obstacle of obstacles) {
5336
+ const segment = segmentBox2(a, b);
5337
+ for (const obstacle of candidateBoxesForSegment(
5338
+ obstacles,
5339
+ a,
5340
+ b,
5341
+ spatialIndex
5342
+ )) {
4942
5343
  validateBox(obstacle);
4943
5344
  if (intersectsAabb(segment, obstacle)) {
4944
5345
  return true;
@@ -4954,7 +5355,7 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4954
5355
  if (a === void 0 || b === void 0) {
4955
5356
  continue;
4956
5357
  }
4957
- const segment = segmentBox(a, b);
5358
+ const segment = segmentBox2(a, b);
4958
5359
  for (const endpointInterior of endpointInteriors) {
4959
5360
  validateBox(endpointInterior);
4960
5361
  if (intersectsAabb(segment, endpointInterior)) {
@@ -4964,14 +5365,19 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4964
5365
  }
4965
5366
  return false;
4966
5367
  }
4967
- function routeCrossesBoxes(points, obstacles) {
4968
- for (let index = 0; index < points.length - 1; index += 1) {
4969
- const a = points[index];
4970
- const b = points[index + 1];
5368
+ function routeCrossesBoxes(points, obstacles, spatialIndex) {
5369
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5370
+ const a = points[pointIndex];
5371
+ const b = points[pointIndex + 1];
4971
5372
  if (a === void 0 || b === void 0) {
4972
5373
  continue;
4973
5374
  }
4974
- for (const obstacle of obstacles) {
5375
+ for (const obstacle of candidateBoxesForSegment(
5376
+ obstacles,
5377
+ a,
5378
+ b,
5379
+ spatialIndex
5380
+ )) {
4975
5381
  validateBox(obstacle);
4976
5382
  if (segmentIntersectsBox(a, b, obstacle)) {
4977
5383
  return true;
@@ -4980,6 +5386,12 @@ function routeCrossesBoxes(points, obstacles) {
4980
5386
  }
4981
5387
  return false;
4982
5388
  }
5389
+ function candidateBoxesForSegment(obstacles, start, end, index) {
5390
+ return index === void 0 ? obstacles : querySegmentSpatialIndex(index, start, end).map((entry) => entry.box);
5391
+ }
5392
+ function indexedBoxes(obstacles) {
5393
+ return obstacles.map((box, index) => ({ id: `obstacle:${index}`, box }));
5394
+ }
4983
5395
  function segmentIntersectsBox(start, end, box) {
4984
5396
  const left = box.x;
4985
5397
  const right = box.x + box.width;
@@ -5013,7 +5425,7 @@ function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
5013
5425
  const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
5014
5426
  return t > 0 && t < 1 && u > 0 && u < 1;
5015
5427
  }
5016
- function segmentBox(a, b) {
5428
+ function segmentBox2(a, b) {
5017
5429
  const minX = Math.min(a.x, b.x);
5018
5430
  const minY = Math.min(a.y, b.y);
5019
5431
  return {
@@ -5085,17 +5497,16 @@ function solveDiagram(diagram, options = {}) {
5085
5497
  (swimlane) => enhanceSwimlaneCjkTypography(swimlane, cjkTypography, diagnostics)
5086
5498
  );
5087
5499
  const constraints = stableByConstraintId(diagram.constraints);
5088
- const layout2 = runDagreInitialLayout({
5500
+ const initialLayoutMode = options.initialLayout ?? "dagre";
5501
+ const layout2 = runInitialLayout({
5502
+ mode: initialLayoutMode,
5503
+ componentAware: options.maxStackDepth === void 0,
5089
5504
  direction: diagram.direction,
5090
- nodes: styledNodes.map((node) => ({ id: node.id, size: node.size })),
5091
- edges: styledEdges.map((edge) => ({
5092
- id: edge.id,
5093
- sourceId: edge.source.nodeId,
5094
- targetId: edge.target.nodeId
5095
- }))
5505
+ nodes: styledNodes,
5506
+ edges: styledEdges
5096
5507
  });
5097
5508
  diagnostics.push(...layout2.diagnostics);
5098
- const initialNodeBoxes = wrapVerticalStackIfNeeded(
5509
+ const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
5099
5510
  layout2.boxes,
5100
5511
  styledNodes,
5101
5512
  styledEdges,
@@ -5108,7 +5519,7 @@ function solveDiagram(diagram, options = {}) {
5108
5519
  direction: diagram.direction,
5109
5520
  overlapSpacing: options?.overlapSpacing ?? 40,
5110
5521
  ...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
5111
- ...options.distributeContainedChildren === void 0 ? {} : { distributeContainedChildren: options.distributeContainedChildren },
5522
+ distributeContainedChildren: options.distributeContainedChildren ?? true,
5112
5523
  boxes: initialNodeBoxes,
5113
5524
  nodes: styledNodes,
5114
5525
  constraints
@@ -5355,6 +5766,84 @@ function solveDiagram(diagram, options = {}) {
5355
5766
  function solveDiagramSafe(diagram, options = {}) {
5356
5767
  return solveDiagram(diagram, { ...options, prefitLabelSize: true });
5357
5768
  }
5769
+ function runInitialLayout(input) {
5770
+ if (input.mode === "positions") {
5771
+ return runPositionSeededInitialLayout(input);
5772
+ }
5773
+ const runAutoLayout = input.componentAware ? runComponentAwareDagreInitialLayout : runDagreInitialLayout;
5774
+ return runAutoLayout({
5775
+ direction: input.direction,
5776
+ nodes: input.nodes.map((node) => ({ id: node.id, size: node.size })),
5777
+ edges: input.edges.map((edge) => ({
5778
+ id: edge.id,
5779
+ sourceId: edge.source.nodeId,
5780
+ targetId: edge.target.nodeId
5781
+ }))
5782
+ });
5783
+ }
5784
+ function runPositionSeededInitialLayout(input) {
5785
+ const diagnostics = [];
5786
+ const boxes = /* @__PURE__ */ new Map();
5787
+ const autoNodes = [];
5788
+ for (const node of input.nodes) {
5789
+ if (!isValidInitialDimension(node.size.width) || !isValidInitialDimension(node.size.height)) {
5790
+ diagnostics.push({
5791
+ severity: "error",
5792
+ code: "layout.node-size.invalid",
5793
+ message: `Node ${node.id} has invalid layout dimensions.`,
5794
+ path: ["nodes", node.id, "size"],
5795
+ detail: { nodeId: node.id }
5796
+ });
5797
+ continue;
5798
+ }
5799
+ if (node.position === void 0) {
5800
+ autoNodes.push(node);
5801
+ continue;
5802
+ }
5803
+ if (!isFiniteInitialPoint(node.position)) {
5804
+ diagnostics.push({
5805
+ severity: "error",
5806
+ code: "layout.node-position.invalid",
5807
+ message: `Node ${node.id} has an invalid seeded position.`,
5808
+ path: ["nodes", node.id, "position"],
5809
+ detail: { nodeId: node.id }
5810
+ });
5811
+ continue;
5812
+ }
5813
+ boxes.set(node.id, {
5814
+ x: node.position.x,
5815
+ y: node.position.y,
5816
+ width: node.size.width,
5817
+ height: node.size.height
5818
+ });
5819
+ }
5820
+ if (autoNodes.length === 0) {
5821
+ return { boxes, diagnostics };
5822
+ }
5823
+ const autoNodeIds = new Set(autoNodes.map((node) => node.id));
5824
+ const autoLayout = runComponentAwareDagreInitialLayout({
5825
+ direction: input.direction,
5826
+ nodes: autoNodes.map((node) => ({ id: node.id, size: node.size })),
5827
+ edges: input.edges.filter(
5828
+ (edge) => autoNodeIds.has(edge.source.nodeId) && autoNodeIds.has(edge.target.nodeId)
5829
+ ).map((edge) => ({
5830
+ id: edge.id,
5831
+ sourceId: edge.source.nodeId,
5832
+ targetId: edge.target.nodeId
5833
+ }))
5834
+ });
5835
+ diagnostics.push(...autoLayout.diagnostics);
5836
+ for (const [id, box] of autoLayout.boxes) {
5837
+ boxes.set(id, box);
5838
+ }
5839
+ return { boxes, diagnostics };
5840
+ }
5841
+ function isValidInitialDimension(value) {
5842
+ return Number.isFinite(value) && value >= 0;
5843
+ }
5844
+ function isFiniteInitialPoint(point2) {
5845
+ return Number.isFinite(point2.x) && Number.isFinite(point2.y);
5846
+ }
5358
5847
  function prefitNodeLabelSize(node, options, diagnostics) {
5359
5848
  if (node.label === void 0) {
5360
5849
  return node;
@@ -7106,6 +7595,10 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7106
7595
  const coordinatedNodeById = new Map(
7107
7596
  coordinatedNodes.map((node) => [node.id, node])
7108
7597
  );
7598
+ const nodeObstacleIndex = createBoxSpatialIndex(
7599
+ obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
7600
+ options.routingGutter ?? 160
7601
+ );
7109
7602
  for (const edge of edges) {
7110
7603
  const source = nodes.get(edge.source.nodeId);
7111
7604
  const target = nodes.get(edge.target.nodeId);
@@ -7126,6 +7619,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7126
7619
  const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
7127
7620
  const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
7128
7621
  const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
7622
+ const corridor = edgeCorridorBox(
7623
+ source.box,
7624
+ target.box,
7625
+ options.routingGutter ?? 160
7626
+ );
7627
+ const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
7628
+ (obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
7629
+ );
7129
7630
  const route = routeEdge({
7130
7631
  kind: options.routeKind ?? "orthogonal",
7131
7632
  direction,
@@ -7134,9 +7635,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7134
7635
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
7135
7636
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
7136
7637
  obstacles: [
7137
- ...obstacles.filter(
7138
- (obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
7139
- ),
7638
+ ...routeNodeObstacles,
7140
7639
  ...softObstacles,
7141
7640
  ...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
7142
7641
  ...routeTextObstacles
@@ -7157,6 +7656,19 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7157
7656
  }
7158
7657
  return coordinated;
7159
7658
  }
7659
+ function edgeCorridorBox(source, target, margin) {
7660
+ const minX = Math.min(source.x, target.x);
7661
+ const minY = Math.min(source.y, target.y);
7662
+ const maxX = Math.max(source.x + source.width, target.x + target.width);
7663
+ const maxY = Math.max(source.y + source.height, target.y + target.height);
7664
+ return expandBoxForQuery(
7665
+ { x: minX, y: minY, width: maxX - minX, height: maxY - minY },
7666
+ margin
7667
+ );
7668
+ }
7669
+ function sameBox(first, second) {
7670
+ return first.x === second.x && first.y === second.y && first.width === second.width && first.height === second.height;
7671
+ }
7160
7672
  function isEdgeConnectedTextAnnotation(edge, annotation) {
7161
7673
  switch (annotation.surfaceKind) {
7162
7674
  case "edge-label":
@@ -8045,6 +8557,7 @@ function renderDiagramDsl(source, options = {}) {
8045
8557
  return { diagnostics };
8046
8558
  }
8047
8559
  const solved = solveDiagram(normalized.diagram, {
8560
+ ...solveInitialLayoutOption(normalized.diagram.metadata?.initialLayout),
8048
8561
  routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : normalized.diagram.metadata?.routeKind === "obstacle-avoiding" ? "obstacle-avoiding" : "orthogonal",
8049
8562
  ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
8050
8563
  ...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
@@ -8086,6 +8599,9 @@ function renderDiagramDsl(source, options = {}) {
8086
8599
  function toSolveDiagnostic(diagnostic) {
8087
8600
  return { ...diagnostic, layer: "solve" };
8088
8601
  }
8602
+ function solveInitialLayoutOption(value) {
8603
+ return value === "positions" ? { initialLayout: "positions" } : {};
8604
+ }
8089
8605
  function solvePortShiftingOption(value) {
8090
8606
  if (!isJsonObject(value)) {
8091
8607
  return {};
@@ -8249,8 +8765,10 @@ exports.canonicalize = canonicalize;
8249
8765
  exports.computeArrowhead = computeArrowhead;
8250
8766
  exports.computeContainerGeometry = computeContainerGeometry;
8251
8767
  exports.computeShapeGeometry = computeShapeGeometry;
8768
+ exports.createBoxSpatialIndex = createBoxSpatialIndex;
8252
8769
  exports.createDefaultTextMeasurer = createDefaultTextMeasurer;
8253
8770
  exports.expandBox = expandBox;
8771
+ exports.expandBoxForQuery = expandBoxForQuery;
8254
8772
  exports.exportExcalidraw = exportExcalidraw;
8255
8773
  exports.exportSvg = exportSvg;
8256
8774
  exports.fitLabel = fitLabel;
@@ -8260,12 +8778,16 @@ exports.intersectsAabb = intersectsAabb;
8260
8778
  exports.isPretextRuntimeAvailable = isPretextRuntimeAvailable;
8261
8779
  exports.normalizeDiagramDsl = normalizeDiagramDsl;
8262
8780
  exports.normalizeInsets = normalizeInsets;
8781
+ exports.overlapArea = overlapArea;
8263
8782
  exports.parseDiagramDsl = parseDiagramDsl;
8264
8783
  exports.parseEdgeShorthand = parseEdgeShorthand;
8784
+ exports.queryBoxSpatialIndex = queryBoxSpatialIndex;
8785
+ exports.querySegmentSpatialIndex = querySegmentSpatialIndex;
8265
8786
  exports.renderDiagramDsl = renderDiagramDsl;
8266
8787
  exports.resolveLineHeight = resolveLineHeight;
8267
8788
  exports.resolveOutputFormat = resolveOutputFormat;
8268
8789
  exports.routeEdge = routeEdge;
8790
+ exports.runComponentAwareDagreInitialLayout = runComponentAwareDagreInitialLayout;
8269
8791
  exports.runDagreInitialLayout = runDagreInitialLayout;
8270
8792
  exports.simplifyRoute = simplifyRoute2;
8271
8793
  exports.solveDiagram = solveDiagram;