@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.js CHANGED
@@ -76,6 +76,17 @@ function intersectsAabb(a, b) {
76
76
  validateBox(b, "b");
77
77
  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;
78
78
  }
79
+ function overlapArea(first, second) {
80
+ const x = Math.max(
81
+ 0,
82
+ Math.min(first.x + first.width, second.x + second.width) - Math.max(first.x, second.x)
83
+ );
84
+ const y = Math.max(
85
+ 0,
86
+ Math.min(first.y + first.height, second.y + second.height) - Math.max(first.y, second.y)
87
+ );
88
+ return x * y;
89
+ }
79
90
  function validateMargin(value, label) {
80
91
  validateFinite(value, label);
81
92
  if (value < 0) {
@@ -88,6 +99,72 @@ function validateFinite(value, label) {
88
99
  }
89
100
  }
90
101
 
102
+ // src/geometry/spatial-index.ts
103
+ function createBoxSpatialIndex(entries, cellSize = 128) {
104
+ const normalizedCellSize = Number.isFinite(cellSize) && cellSize > 0 ? cellSize : 128;
105
+ const boxes = /* @__PURE__ */ new Map();
106
+ const mutableCells = /* @__PURE__ */ new Map();
107
+ for (const entry of entries) {
108
+ boxes.set(entry.id, { ...entry.box });
109
+ for (const key of cellKeysForBox(entry.box, normalizedCellSize)) {
110
+ const ids = mutableCells.get(key) ?? [];
111
+ ids.push(entry.id);
112
+ mutableCells.set(key, ids);
113
+ }
114
+ }
115
+ const cells = /* @__PURE__ */ new Map();
116
+ for (const [key, ids] of mutableCells) {
117
+ cells.set(key, [...new Set(ids)].sort());
118
+ }
119
+ return { cellSize: normalizedCellSize, entries: boxes, cells };
120
+ }
121
+ function queryBoxSpatialIndex(index, box) {
122
+ const ids = /* @__PURE__ */ new Set();
123
+ for (const key of cellKeysForBox(box, index.cellSize)) {
124
+ for (const id of index.cells.get(key) ?? []) {
125
+ ids.add(id);
126
+ }
127
+ }
128
+ return [...ids].sort().flatMap((id) => {
129
+ const candidate = index.entries.get(id);
130
+ return candidate !== void 0 && intersectsAabb(candidate, box) ? [{ id, box: candidate }] : [];
131
+ });
132
+ }
133
+ function querySegmentSpatialIndex(index, start, end) {
134
+ return queryBoxSpatialIndex(index, segmentBox(start, end));
135
+ }
136
+ function expandBoxForQuery(box, margin) {
137
+ return {
138
+ x: box.x - margin,
139
+ y: box.y - margin,
140
+ width: box.width + margin * 2,
141
+ height: box.height + margin * 2
142
+ };
143
+ }
144
+ function cellKeysForBox(box, cellSize) {
145
+ const minCol = Math.floor(box.x / cellSize);
146
+ const maxCol = Math.floor((box.x + Math.max(1, box.width)) / cellSize);
147
+ const minRow = Math.floor(box.y / cellSize);
148
+ const maxRow = Math.floor((box.y + Math.max(1, box.height)) / cellSize);
149
+ const keys = [];
150
+ for (let col = minCol; col <= maxCol; col += 1) {
151
+ for (let row = minRow; row <= maxRow; row += 1) {
152
+ keys.push(`${col}:${row}`);
153
+ }
154
+ }
155
+ return keys;
156
+ }
157
+ function segmentBox(start, end) {
158
+ const x = Math.min(start.x, end.x);
159
+ const y = Math.min(start.y, end.y);
160
+ return {
161
+ x,
162
+ y,
163
+ width: Math.max(1, Math.abs(start.x - end.x)),
164
+ height: Math.max(1, Math.abs(start.y - end.y))
165
+ };
166
+ }
167
+
91
168
  // src/constraints/solver.ts
92
169
  function applyLayoutConstraints(input) {
93
170
  const diagnostics = [];
@@ -119,7 +196,12 @@ function applyLayoutConstraints(input) {
119
196
  dedupReplayDiagnostics(diagnostics, diagBefore);
120
197
  }
121
198
  removeResolvedConstraintDiagnostics(input.constraints, boxes, diagnostics);
122
- reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
199
+ reportOverlaps(
200
+ boxes,
201
+ diagnostics,
202
+ containmentOverlapKeys(input.constraints),
203
+ locks
204
+ );
123
205
  reportIntraContainerOverflow(input, boxes, diagnostics);
124
206
  return { boxes, locks, diagnostics };
125
207
  }
@@ -285,14 +367,19 @@ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow
285
367
  if (samePosition(child, next)) {
286
368
  continue;
287
369
  }
288
- if (locks.has(childId)) {
370
+ const lock = locks.get(childId);
371
+ if (lock !== void 0) {
289
372
  if (!reportOverflow) {
290
373
  diagnostics.push({
291
374
  severity: "warning",
292
375
  code: "constraints.locked-target-not-moved",
293
376
  message: `Locked child ${childId} was not moved into containment.`,
294
377
  path: ["constraints", constraint.id ?? constraint.containerId],
295
- detail: { nodeId: childId, containerId: constraint.containerId }
378
+ detail: {
379
+ nodeId: childId,
380
+ containerId: constraint.containerId,
381
+ lockSource: lock.source
382
+ }
296
383
  });
297
384
  if (!isInside(child, content)) {
298
385
  diagnostics.push({
@@ -407,18 +494,29 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
407
494
  const secondaryAxis = axis === "x" ? "y" : "x";
408
495
  const ignoredPairs = containmentOverlapKeys(input.constraints);
409
496
  const ids = [...boxes.keys()].sort();
497
+ const index = createBoxSpatialIndex(
498
+ ids.flatMap((id) => {
499
+ const box = boxes.get(id);
500
+ return box === void 0 ? [] : [{ id, box }];
501
+ }),
502
+ spacing
503
+ );
410
504
  for (let pass = 0; pass < 2; pass += 1) {
411
505
  for (const firstId of ids) {
412
- for (const secondId of ids) {
506
+ const first = boxes.get(firstId);
507
+ if (first === void 0) {
508
+ continue;
509
+ }
510
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
511
+ for (const secondId of candidateIds) {
413
512
  if (firstId >= secondId) {
414
513
  continue;
415
514
  }
416
515
  if (ignoredPairs.has(overlapKey(firstId, secondId))) {
417
516
  continue;
418
517
  }
419
- const first = boxes.get(firstId);
420
518
  const second = boxes.get(secondId);
421
- if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
519
+ if (second === void 0 || !intersectsAabb(first, second)) {
422
520
  continue;
423
521
  }
424
522
  const firstLocked = locks.has(firstId);
@@ -442,7 +540,7 @@ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
442
540
  }
443
541
  }
444
542
  }
445
- reportOverlaps(boxes, diagnostics, ignoredPairs);
543
+ reportOverlaps(boxes, diagnostics, ignoredPairs, locks);
446
544
  }
447
545
  function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
448
546
  for (let i = diagnostics.length - 1; i >= 0; i -= 1) {
@@ -498,29 +596,56 @@ function removeResolvedConstraintDiagnostics(constraints, boxes, diagnostics) {
498
596
  }
499
597
  }
500
598
  }
501
- function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
599
+ function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set(), locks = /* @__PURE__ */ new Map()) {
502
600
  const ids = [...boxes.keys()].sort();
503
601
  const reported = new Set(
504
602
  diagnostics.filter(
505
- (diagnostic) => diagnostic.code === "constraints.overlap.unresolved"
603
+ (diagnostic) => diagnostic.code === "constraints.overlap.unresolved" || diagnostic.code === "constraints.overlap.locked-conflict"
506
604
  ).map((diagnostic) => {
507
605
  const firstId = diagnostic.detail?.firstId;
508
606
  const secondId = diagnostic.detail?.secondId;
509
607
  return typeof firstId === "string" && typeof secondId === "string" ? overlapKey(firstId, secondId) : void 0;
510
608
  }).filter((key) => key !== void 0)
511
609
  );
610
+ const index = createBoxSpatialIndex(
611
+ ids.flatMap((id) => {
612
+ const box = boxes.get(id);
613
+ return box === void 0 ? [] : [{ id, box }];
614
+ }),
615
+ 40
616
+ );
512
617
  for (const firstId of ids) {
513
- for (const secondId of ids) {
514
- if (firstId >= secondId) {
515
- continue;
516
- }
618
+ const first = boxes.get(firstId);
619
+ if (first === void 0) {
620
+ continue;
621
+ }
622
+ const candidateIds = queryBoxSpatialIndex(index, first).map((candidate) => candidate.id).filter((id) => id > firstId).sort();
623
+ for (const secondId of candidateIds) {
517
624
  const key = overlapKey(firstId, secondId);
518
625
  if (reported.has(key) || ignoredPairs.has(key)) {
519
626
  continue;
520
627
  }
521
- const first = boxes.get(firstId);
522
628
  const second = boxes.get(secondId);
523
- if (first !== void 0 && second !== void 0 && intersectsAabb(first, second)) {
629
+ if (second !== void 0 && intersectsAabb(first, second)) {
630
+ const firstLock = locks.get(firstId);
631
+ const secondLock = locks.get(secondId);
632
+ if (firstLock !== void 0 && secondLock !== void 0) {
633
+ diagnostics.push({
634
+ severity: "warning",
635
+ code: "constraints.overlap.locked-conflict",
636
+ message: `Locked boxes ${firstId} (${firstLock.source}) and ${secondId} (${secondLock.source}) overlap and cannot be repaired.`,
637
+ path: ["boxes"],
638
+ detail: {
639
+ firstId,
640
+ secondId,
641
+ firstLockSource: firstLock.source,
642
+ secondLockSource: secondLock.source,
643
+ overlapArea: overlapArea(first, second)
644
+ }
645
+ });
646
+ reported.add(key);
647
+ continue;
648
+ }
524
649
  diagnostics.push({
525
650
  severity: "warning",
526
651
  code: "constraints.overlap.unresolved",
@@ -676,12 +801,17 @@ function setUnlockedBox(id, next, boxes, locks, diagnostics, constraint) {
676
801
  return;
677
802
  }
678
803
  if (locks.has(id) && !samePosition(current, next)) {
804
+ const lock = locks.get(id);
679
805
  diagnostics.push({
680
806
  severity: "warning",
681
807
  code: "constraints.locked-target-not-moved",
682
808
  message: `Locked target ${id} was not moved by ${constraint.kind}.`,
683
809
  path: ["constraints", constraint.id ?? id],
684
- detail: { nodeId: id, constraintKind: constraint.kind }
810
+ detail: {
811
+ nodeId: id,
812
+ constraintKind: constraint.kind,
813
+ ...lock === void 0 ? {} : { lockSource: lock.source }
814
+ }
685
815
  });
686
816
  return;
687
817
  }
@@ -850,7 +980,28 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
850
980
  if (distributable.length < 2) {
851
981
  continue;
852
982
  }
983
+ const spread = typeof input.distributeContainedChildren === "string";
984
+ let effectiveGap = minGap;
853
985
  let pos = content[axis];
986
+ if (spread) {
987
+ let totalChildSpan = 0;
988
+ for (const child of distributable) {
989
+ totalChildSpan += child.box[mainSize];
990
+ }
991
+ let reservedSpan = 0;
992
+ const contentEnd = content[axis] + content[mainSize];
993
+ for (const r of reserved) {
994
+ const rStart = Math.max(r.start, content[axis]);
995
+ const rEnd = Math.min(r.end, contentEnd);
996
+ if (rEnd > rStart) {
997
+ reservedSpan += rEnd - rStart + minGap;
998
+ }
999
+ }
1000
+ const remaining = content[mainSize] - totalChildSpan - reservedSpan - minGap * (distributable.length - 1);
1001
+ if (remaining > 0) {
1002
+ effectiveGap = minGap + remaining / (distributable.length - 1);
1003
+ }
1004
+ }
854
1005
  for (const child of distributable) {
855
1006
  pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
856
1007
  const crossPos = content[crossAxis] + Math.max(0, (content[crossSize] - child.box[crossSize]) / 2);
@@ -869,7 +1020,7 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
869
1020
  }
870
1021
  boxes.set(child.id, clamped);
871
1022
  locks.delete(child.id);
872
- pos = clamped[axis] + clamped[mainSize] + minGap;
1023
+ pos = clamped[axis] + clamped[mainSize] + effectiveGap;
873
1024
  }
874
1025
  diagnostics.push({
875
1026
  severity: "info",
@@ -1625,6 +1776,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1625
1776
  const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1626
1777
  const routeKind = dsl.routing?.kind ?? "orthogonal";
1627
1778
  const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1779
+ const initialLayout = dsl.layout?.mode;
1628
1780
  const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
1629
1781
  const matrices = normalizeMatrices(dsl);
1630
1782
  const tables = normalizeTables(dsl);
@@ -1645,6 +1797,7 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1645
1797
  ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
1646
1798
  metadata: {
1647
1799
  routeKind,
1800
+ ...initialLayout === void 0 ? {} : { initialLayout },
1648
1801
  ...primaryReadingDirection === void 0 ? {} : { primaryReadingDirection },
1649
1802
  ...portShifting === void 0 ? {} : { portShifting }
1650
1803
  }
@@ -2194,6 +2347,7 @@ function point(value) {
2194
2347
  return { x: value.x, y: value.y };
2195
2348
  }
2196
2349
  var directionSchema = z.enum(["TB", "LR", "BT", "RL"]);
2350
+ var layoutModeSchema = z.enum(["dagre", "positions"]);
2197
2351
  var routeKindSchema = z.enum(["orthogonal", "straight", "obstacle-avoiding"]);
2198
2352
  var outputFormatSchema = z.enum(["svg", "excalidraw"]);
2199
2353
  var edgeStrokeStyleSchema = z.enum(["solid", "dashed"]);
@@ -2504,6 +2658,7 @@ var diagramDslSchema = z.object({
2504
2658
  direction: directionSchema.optional(),
2505
2659
  layout: z.object({
2506
2660
  direction: directionSchema.optional(),
2661
+ mode: layoutModeSchema.optional(),
2507
2662
  primaryReadingDirection: primaryReadingDirectionSchema.optional()
2508
2663
  }).optional(),
2509
2664
  routing: z.object({
@@ -2840,13 +2995,22 @@ function exportExcalidraw(diagram, options = {}) {
2840
2995
  appState: {
2841
2996
  name: options.title ?? diagram.title ?? diagram.id,
2842
2997
  viewBackgroundColor: "#ffffff",
2843
- gridSize: null
2998
+ gridSize: null,
2999
+ ...options.viewportPadding === void 0 ? {} : viewportAppState(diagram.bounds, options.viewportPadding)
2844
3000
  },
2845
3001
  files: {}
2846
3002
  };
2847
3003
  return `${JSON.stringify(scene, null, 2)}
2848
3004
  `;
2849
3005
  }
3006
+ function viewportAppState(bounds, padding) {
3007
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3008
+ return {
3009
+ scrollX: finite(-bounds.x + safePadding),
3010
+ scrollY: finite(-bounds.y + safePadding),
3011
+ zoom: { value: 1 }
3012
+ };
3013
+ }
2850
3014
  function renderGroup(group) {
2851
3015
  return {
2852
3016
  ...baseElement(`group:${group.id}`, "rectangle", group.box),
@@ -3170,6 +3334,9 @@ function exportSvg(diagram, options = {}) {
3170
3334
  return `${[
3171
3335
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
3172
3336
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
3337
+ ...options.viewportPadding === void 0 ? [] : [
3338
+ ` <metadata data-dge-viewport="${escapeAttribute(viewportMetadata(diagram.bounds, options.viewportPadding))}"></metadata>`
3339
+ ],
3173
3340
  ` <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"/>`,
3174
3341
  ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
3175
3342
  ...(diagram.swimlanes ?? []).flatMap(
@@ -3203,6 +3370,16 @@ function exportSvg(diagram, options = {}) {
3203
3370
  ].join("\n")}
3204
3371
  `;
3205
3372
  }
3373
+ function viewportMetadata(bounds, padding) {
3374
+ const safePadding = Number.isFinite(padding) ? Math.max(0, padding) : 0;
3375
+ return JSON.stringify({
3376
+ x: bounds.x - safePadding,
3377
+ y: bounds.y - safePadding,
3378
+ width: bounds.width + safePadding * 2,
3379
+ height: bounds.height + safePadding * 2,
3380
+ padding: safePadding
3381
+ });
3382
+ }
3206
3383
  function renderGroup2(group) {
3207
3384
  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"/>`;
3208
3385
  }
@@ -3877,6 +4054,7 @@ function indentLines(values) {
3877
4054
  // src/ir/diagnostics.ts
3878
4055
  var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
3879
4056
  "constraints.locked-target-not-moved",
4057
+ "constraints.overlap.locked-conflict",
3880
4058
  "routing.evidence.crossing_forbidden",
3881
4059
  "routing.obstacle.unavoidable",
3882
4060
  "route_obstacle_fallback",
@@ -3888,6 +4066,7 @@ var DEFAULT_OPTIONS = {
3888
4066
  edgesep: 40,
3889
4067
  marginx: 0,
3890
4068
  marginy: 0,
4069
+ componentGap: 160,
3891
4070
  ranker: "network-simplex"
3892
4071
  };
3893
4072
  function runDagreInitialLayout(input) {
@@ -3976,20 +4155,137 @@ function runDagreInitialLayout(input) {
3976
4155
  }
3977
4156
  return { boxes, diagnostics };
3978
4157
  }
4158
+ function runComponentAwareDagreInitialLayout(input) {
4159
+ const options = { ...DEFAULT_OPTIONS, ...input.options };
4160
+ const diagnostics = reportMissingEdgeReferences(input);
4161
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4162
+ const validEdges = input.edges.filter(
4163
+ (edge) => validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)
4164
+ );
4165
+ const components = connectedComponents(input.nodes, validEdges);
4166
+ if (components.length <= 1) {
4167
+ const layout2 = runDagreInitialLayout({ ...input, edges: validEdges });
4168
+ return {
4169
+ boxes: layout2.boxes,
4170
+ diagnostics: [...diagnostics, ...layout2.diagnostics]
4171
+ };
4172
+ }
4173
+ const boxes = /* @__PURE__ */ new Map();
4174
+ let cursor = 0;
4175
+ for (const component of components) {
4176
+ const componentNodeIds = new Set(component.map((node) => node.id));
4177
+ const componentLayout = runDagreInitialLayout({
4178
+ ...input,
4179
+ nodes: component,
4180
+ edges: validEdges.filter(
4181
+ (edge) => componentNodeIds.has(edge.sourceId) && componentNodeIds.has(edge.targetId)
4182
+ )
4183
+ });
4184
+ diagnostics.push(...componentLayout.diagnostics);
4185
+ if (componentLayout.boxes.size === 0) {
4186
+ continue;
4187
+ }
4188
+ const bounds = unionBoxes([...componentLayout.boxes.values()]);
4189
+ const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
4190
+ const dx = axis === "x" ? cursor - bounds.x : -bounds.x;
4191
+ const dy = axis === "y" ? cursor - bounds.y : -bounds.y;
4192
+ for (const [id, box] of componentLayout.boxes) {
4193
+ boxes.set(id, { ...box, x: box.x + dx, y: box.y + dy });
4194
+ }
4195
+ cursor += (axis === "x" ? bounds.width : bounds.height) + options.componentGap;
4196
+ }
4197
+ return { boxes, diagnostics };
4198
+ }
4199
+ function reportMissingEdgeReferences(input) {
4200
+ const validNodeIds = new Set(input.nodes.map((node) => node.id));
4201
+ return input.edges.flatMap((edge) => {
4202
+ if (validNodeIds.has(edge.sourceId) && validNodeIds.has(edge.targetId)) {
4203
+ return [];
4204
+ }
4205
+ return [
4206
+ {
4207
+ severity: "error",
4208
+ code: "layout.edge-reference.missing",
4209
+ message: `Edge ${edge.id} references a missing layout node.`,
4210
+ path: ["edges", edge.id],
4211
+ detail: {
4212
+ edgeId: edge.id,
4213
+ sourceId: edge.sourceId,
4214
+ targetId: edge.targetId
4215
+ }
4216
+ }
4217
+ ];
4218
+ });
4219
+ }
3979
4220
  function isValidDimension(value) {
3980
4221
  return Number.isFinite(value) && value >= 0;
3981
4222
  }
4223
+ function connectedComponents(nodes, edges) {
4224
+ const nodeById = new Map(nodes.map((node) => [node.id, node]));
4225
+ const adjacency = new Map(nodes.map((node) => [node.id, /* @__PURE__ */ new Set()]));
4226
+ for (const edge of edges) {
4227
+ if (!nodeById.has(edge.sourceId) || !nodeById.has(edge.targetId)) {
4228
+ continue;
4229
+ }
4230
+ adjacency.get(edge.sourceId)?.add(edge.targetId);
4231
+ adjacency.get(edge.targetId)?.add(edge.sourceId);
4232
+ }
4233
+ const visited = /* @__PURE__ */ new Set();
4234
+ const components = [];
4235
+ for (const node of [...nodes].sort((a, b) => a.id.localeCompare(b.id))) {
4236
+ if (visited.has(node.id)) {
4237
+ continue;
4238
+ }
4239
+ const ids = [];
4240
+ const stack = [node.id];
4241
+ visited.add(node.id);
4242
+ while (stack.length > 0) {
4243
+ const id = stack.pop();
4244
+ if (id === void 0) {
4245
+ continue;
4246
+ }
4247
+ ids.push(id);
4248
+ for (const neighbor of [...adjacency.get(id) ?? []].sort().reverse()) {
4249
+ if (!visited.has(neighbor)) {
4250
+ visited.add(neighbor);
4251
+ stack.push(neighbor);
4252
+ }
4253
+ }
4254
+ }
4255
+ components.push(
4256
+ ids.sort().flatMap((id) => {
4257
+ const componentNode = nodeById.get(id);
4258
+ return componentNode === void 0 ? [] : [componentNode];
4259
+ })
4260
+ );
4261
+ }
4262
+ return components.sort((a, b) => {
4263
+ const left = a[0]?.id ?? "";
4264
+ const right = b[0]?.id ?? "";
4265
+ return left.localeCompare(right);
4266
+ });
4267
+ }
3982
4268
 
3983
4269
  // src/routing/astar.ts
3984
- function findObstacleFreePath(source, target, obstacles, options = {}) {
4270
+ function findObstacleFreePath(source, target, obstacles, options = {}, diagnostics) {
3985
4271
  const margin = options.margin ?? 0;
3986
4272
  const turnPenalty = options.turnPenalty ?? 50;
3987
4273
  const segmentPenalty = options.segmentPenalty ?? 1;
3988
4274
  const endpointObstacles = options.endpointObstacles ?? [];
3989
- const maxNodes = options.maxNodes ?? 4e3;
4275
+ const maxNodes = options.maxNodes ?? (obstacles.length > 30 ? 16e3 : 4e3);
3990
4276
  const xs = collectXs(source, target, obstacles, margin);
3991
4277
  const ys = collectYs(source, target, obstacles, margin);
3992
4278
  if (xs.length * ys.length > maxNodes) {
4279
+ diagnostics?.push({
4280
+ severity: "warning",
4281
+ code: "routing.astar.grid_overflow",
4282
+ message: `A* grid overflow: ${xs.length * ys.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
4283
+ detail: {
4284
+ xsCount: xs.length,
4285
+ ysCount: ys.length,
4286
+ maxNodes
4287
+ }
4288
+ });
3993
4289
  return null;
3994
4290
  }
3995
4291
  const { nodes, nodeIndex } = buildGraph(xs, ys);
@@ -4007,24 +4303,54 @@ function findObstacleFreePath(source, target, obstacles, options = {}) {
4007
4303
  return simplifyRoute(path);
4008
4304
  }
4009
4305
  function collectXs(source, target, obstacles, margin) {
4010
- const set = /* @__PURE__ */ new Set();
4011
- set.add(source.x);
4012
- set.add(target.x);
4306
+ const raw = [];
4013
4307
  for (const obs of obstacles) {
4014
- set.add(obs.x - margin - 2);
4015
- set.add(obs.x + obs.width + margin + 2);
4308
+ raw.push(obs.x - margin - 2, obs.x + obs.width + margin + 2);
4309
+ }
4310
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4311
+ for (const v of [source.x, target.x]) {
4312
+ if (!deduped.includes(v)) {
4313
+ deduped.push(v);
4314
+ }
4016
4315
  }
4017
- return [...set].sort((a, b) => a - b);
4316
+ return deduped.sort((a, b) => a - b);
4018
4317
  }
4019
4318
  function collectYs(source, target, obstacles, margin) {
4020
- const set = /* @__PURE__ */ new Set();
4021
- set.add(source.y);
4022
- set.add(target.y);
4319
+ const raw = [];
4023
4320
  for (const obs of obstacles) {
4024
- set.add(obs.y - margin - 2);
4025
- set.add(obs.y + obs.height + margin + 2);
4321
+ raw.push(obs.y - margin - 2, obs.y + obs.height + margin + 2);
4026
4322
  }
4027
- return [...set].sort((a, b) => a - b);
4323
+ const deduped = insertChannelMidpoints(dedupSorted(raw));
4324
+ for (const v of [source.y, target.y]) {
4325
+ if (!deduped.includes(v)) {
4326
+ deduped.push(v);
4327
+ }
4328
+ }
4329
+ return deduped.sort((a, b) => a - b);
4330
+ }
4331
+ function dedupSorted(values) {
4332
+ const sorted = [...values].sort((a, b) => a - b);
4333
+ const result = [];
4334
+ for (const v of sorted) {
4335
+ const last = result[result.length - 1];
4336
+ if (last === void 0 || v - last > 2) {
4337
+ result.push(v);
4338
+ }
4339
+ }
4340
+ return result;
4341
+ }
4342
+ function insertChannelMidpoints(sorted, minGap = 8) {
4343
+ const result = [];
4344
+ for (let i = 0; i < sorted.length - 1; i++) {
4345
+ const a = sorted[i];
4346
+ const b = sorted[i + 1];
4347
+ result.push(a);
4348
+ if (b - a > minGap) {
4349
+ result.push((a + b) / 2);
4350
+ }
4351
+ }
4352
+ result.push(sorted[sorted.length - 1]);
4353
+ return result.sort((a, b) => a - b);
4028
4354
  }
4029
4355
  function buildGraph(xs, ys) {
4030
4356
  const nodes = [];
@@ -4188,10 +4514,36 @@ function areCollinear(a, b, c) {
4188
4514
  }
4189
4515
 
4190
4516
  // src/routing/routes.ts
4517
+ function checkBacktracking(points, source, target, diagnostics) {
4518
+ if (points.length < 2) return;
4519
+ const direct = Math.hypot(target.x - source.x, target.y - source.y);
4520
+ if (direct <= 0) return;
4521
+ let routeLen = 0;
4522
+ for (let i = 0; i < points.length - 1; i++) {
4523
+ const a = points[i];
4524
+ const b = points[i + 1];
4525
+ routeLen += Math.hypot(b.x - a.x, b.y - a.y);
4526
+ }
4527
+ const threshold = 10;
4528
+ if (routeLen > direct * threshold) {
4529
+ diagnostics.push({
4530
+ severity: "warning",
4531
+ code: "routing.backtracking_excessive",
4532
+ message: `Route length ${Math.round(routeLen)} px exceeds ${threshold}\xD7 direct distance ${Math.round(direct)} px.`,
4533
+ detail: {
4534
+ routeLength: Math.round(routeLen),
4535
+ directDistance: Math.round(direct),
4536
+ threshold
4537
+ }
4538
+ });
4539
+ }
4540
+ }
4191
4541
  function routeEdge(input) {
4192
4542
  const diagnostics = [];
4193
4543
  const softObstacles = input.obstacles ?? [];
4194
4544
  const hardObstacles = input.hardObstacles ?? [];
4545
+ const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
4546
+ const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
4195
4547
  const maxAttempts = input.maxRoutingAttempts ?? 5;
4196
4548
  const defaultAnchors = defaultAnchorsForGeometry(
4197
4549
  input.source.box,
@@ -4213,9 +4565,11 @@ function routeEdge(input) {
4213
4565
  [source, target],
4214
4566
  softObstacles,
4215
4567
  hardObstacles,
4216
- diagnostics
4568
+ diagnostics,
4569
+ softObstacleIndex,
4570
+ hardObstacleIndex
4217
4571
  );
4218
- if (routeCrossesBoxes(points, hardObstacles)) {
4572
+ if (routeCrossesBoxes(points, hardObstacles, hardObstacleIndex)) {
4219
4573
  diagnostics.push({
4220
4574
  severity: "error",
4221
4575
  code: "routing.evidence.crossing_forbidden",
@@ -4223,7 +4577,7 @@ function routeEdge(input) {
4223
4577
  });
4224
4578
  return { points, diagnostics };
4225
4579
  }
4226
- if (routeCrossesBoxes(points, softObstacles)) {
4580
+ if (routeCrossesBoxes(points, softObstacles, softObstacleIndex)) {
4227
4581
  diagnostics.push({
4228
4582
  severity: "warning",
4229
4583
  code: "routing.obstacle.unavoidable",
@@ -4254,16 +4608,24 @@ function routeEdge(input) {
4254
4608
  [...softObstacles, ...hardObstacles],
4255
4609
  {
4256
4610
  endpointObstacles
4257
- }
4611
+ },
4612
+ diagnostics
4258
4613
  );
4259
4614
  if (path !== null && path.length >= 2) {
4260
4615
  const finalized = finalizeRoute(
4261
4616
  path,
4262
4617
  softObstacles,
4263
4618
  hardObstacles,
4264
- diagnostics
4619
+ diagnostics,
4620
+ softObstacleIndex,
4621
+ hardObstacleIndex
4265
4622
  );
4266
- if (!routeIntersectsObstacles(finalized, softObstacles) && !routeIntersectsObstacles(finalized, hardObstacles)) {
4623
+ if (!routeIntersectsObstacles(
4624
+ finalized,
4625
+ softObstacles,
4626
+ softObstacleIndex
4627
+ ) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
4628
+ checkBacktracking(finalized, source, target, diagnostics);
4267
4629
  return { points: finalized, diagnostics };
4268
4630
  }
4269
4631
  }
@@ -4303,23 +4665,41 @@ function routeEdge(input) {
4303
4665
  }
4304
4666
  );
4305
4667
  for (const candidate of candidateRoutes) {
4306
- if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4668
+ if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(
4669
+ candidate.points,
4670
+ softObstacles,
4671
+ softObstacleIndex
4672
+ ) && !routeIntersectsObstacles(
4673
+ candidate.points,
4674
+ hardObstacles,
4675
+ hardObstacleIndex
4676
+ ) && !routeIntersectsEndpointInteriors(
4307
4677
  candidate.points,
4308
4678
  candidate.endpointObstacles
4309
4679
  )) {
4310
- return {
4311
- points: finalizeRoute(
4312
- candidate.points,
4313
- softObstacles,
4314
- hardObstacles,
4315
- diagnostics
4316
- ),
4680
+ const finalizedClean = finalizeRoute(
4681
+ candidate.points,
4682
+ softObstacles,
4683
+ hardObstacles,
4684
+ diagnostics,
4685
+ softObstacleIndex,
4686
+ hardObstacleIndex
4687
+ );
4688
+ checkBacktracking(
4689
+ finalizedClean,
4690
+ candidate.points[0],
4691
+ candidate.points[candidate.points.length - 1],
4317
4692
  diagnostics
4318
- };
4693
+ );
4694
+ return { points: finalizedClean, diagnostics };
4319
4695
  }
4320
4696
  }
4321
4697
  const hardClearCandidate = candidateRoutes.find(
4322
- (candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
4698
+ (candidate) => !routeIntersectsObstacles(
4699
+ candidate.points,
4700
+ hardObstacles,
4701
+ hardObstacleIndex
4702
+ ) && !routeIntersectsEndpointInteriors(
4323
4703
  candidate.points,
4324
4704
  candidate.endpointObstacles
4325
4705
  )
@@ -4470,13 +4850,21 @@ function routeEdge(input) {
4470
4850
  diagnostics
4471
4851
  };
4472
4852
  }
4473
- function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4853
+ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics, softObstacleIndex, hardObstacleIndex) {
4474
4854
  const simplified = simplifyRoute2(points);
4475
4855
  if (simplified.length >= 3) {
4476
4856
  return simplified;
4477
4857
  }
4478
- const crossesHardObstacles = routeCrossesBoxes(simplified, hardObstacles);
4479
- const crossesSoftObstacles = routeCrossesBoxes(simplified, softObstacles);
4858
+ const crossesHardObstacles = routeCrossesBoxes(
4859
+ simplified,
4860
+ hardObstacles,
4861
+ hardObstacleIndex
4862
+ );
4863
+ const crossesSoftObstacles = routeCrossesBoxes(
4864
+ simplified,
4865
+ softObstacles,
4866
+ softObstacleIndex
4867
+ );
4480
4868
  if (!crossesHardObstacles && !crossesSoftObstacles) {
4481
4869
  return simplified;
4482
4870
  }
@@ -4484,8 +4872,16 @@ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4484
4872
  ...softObstacles,
4485
4873
  ...hardObstacles
4486
4874
  ]);
4487
- const expandedCrossesHard = routeCrossesBoxes(expanded, hardObstacles);
4488
- const expandedCrossesSoft = routeCrossesBoxes(expanded, softObstacles);
4875
+ const expandedCrossesHard = routeCrossesBoxes(
4876
+ expanded,
4877
+ hardObstacles,
4878
+ hardObstacleIndex
4879
+ );
4880
+ const expandedCrossesSoft = routeCrossesBoxes(
4881
+ expanded,
4882
+ softObstacles,
4883
+ softObstacleIndex
4884
+ );
4489
4885
  if (expandedCrossesHard || expandedCrossesSoft) {
4490
4886
  diagnostics.push({
4491
4887
  severity: expandedCrossesHard ? "error" : "warning",
@@ -4927,15 +5323,20 @@ function sortedUniqueLanes(lanes, midpoint) {
4927
5323
  return distance === 0 ? left - right : distance;
4928
5324
  });
4929
5325
  }
4930
- function routeIntersectsObstacles(points, obstacles) {
4931
- for (let index = 0; index < points.length - 1; index += 1) {
4932
- const a = points[index];
4933
- const b = points[index + 1];
5326
+ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
5327
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5328
+ const a = points[pointIndex];
5329
+ const b = points[pointIndex + 1];
4934
5330
  if (a === void 0 || b === void 0) {
4935
5331
  continue;
4936
5332
  }
4937
- const segment = segmentBox(a, b);
4938
- for (const obstacle of obstacles) {
5333
+ const segment = segmentBox2(a, b);
5334
+ for (const obstacle of candidateBoxesForSegment(
5335
+ obstacles,
5336
+ a,
5337
+ b,
5338
+ spatialIndex
5339
+ )) {
4939
5340
  validateBox(obstacle);
4940
5341
  if (intersectsAabb(segment, obstacle)) {
4941
5342
  return true;
@@ -4951,7 +5352,7 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4951
5352
  if (a === void 0 || b === void 0) {
4952
5353
  continue;
4953
5354
  }
4954
- const segment = segmentBox(a, b);
5355
+ const segment = segmentBox2(a, b);
4955
5356
  for (const endpointInterior of endpointInteriors) {
4956
5357
  validateBox(endpointInterior);
4957
5358
  if (intersectsAabb(segment, endpointInterior)) {
@@ -4961,14 +5362,19 @@ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4961
5362
  }
4962
5363
  return false;
4963
5364
  }
4964
- function routeCrossesBoxes(points, obstacles) {
4965
- for (let index = 0; index < points.length - 1; index += 1) {
4966
- const a = points[index];
4967
- const b = points[index + 1];
5365
+ function routeCrossesBoxes(points, obstacles, spatialIndex) {
5366
+ for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
5367
+ const a = points[pointIndex];
5368
+ const b = points[pointIndex + 1];
4968
5369
  if (a === void 0 || b === void 0) {
4969
5370
  continue;
4970
5371
  }
4971
- for (const obstacle of obstacles) {
5372
+ for (const obstacle of candidateBoxesForSegment(
5373
+ obstacles,
5374
+ a,
5375
+ b,
5376
+ spatialIndex
5377
+ )) {
4972
5378
  validateBox(obstacle);
4973
5379
  if (segmentIntersectsBox(a, b, obstacle)) {
4974
5380
  return true;
@@ -4977,6 +5383,12 @@ function routeCrossesBoxes(points, obstacles) {
4977
5383
  }
4978
5384
  return false;
4979
5385
  }
5386
+ function candidateBoxesForSegment(obstacles, start, end, index) {
5387
+ return index === void 0 ? obstacles : querySegmentSpatialIndex(index, start, end).map((entry) => entry.box);
5388
+ }
5389
+ function indexedBoxes(obstacles) {
5390
+ return obstacles.map((box, index) => ({ id: `obstacle:${index}`, box }));
5391
+ }
4980
5392
  function segmentIntersectsBox(start, end, box) {
4981
5393
  const left = box.x;
4982
5394
  const right = box.x + box.width;
@@ -5010,7 +5422,7 @@ function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
5010
5422
  const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
5011
5423
  return t > 0 && t < 1 && u > 0 && u < 1;
5012
5424
  }
5013
- function segmentBox(a, b) {
5425
+ function segmentBox2(a, b) {
5014
5426
  const minX = Math.min(a.x, b.x);
5015
5427
  const minY = Math.min(a.y, b.y);
5016
5428
  return {
@@ -5082,17 +5494,16 @@ function solveDiagram(diagram, options = {}) {
5082
5494
  (swimlane) => enhanceSwimlaneCjkTypography(swimlane, cjkTypography, diagnostics)
5083
5495
  );
5084
5496
  const constraints = stableByConstraintId(diagram.constraints);
5085
- const layout2 = runDagreInitialLayout({
5497
+ const initialLayoutMode = options.initialLayout ?? "dagre";
5498
+ const layout2 = runInitialLayout({
5499
+ mode: initialLayoutMode,
5500
+ componentAware: options.maxStackDepth === void 0,
5086
5501
  direction: diagram.direction,
5087
- nodes: styledNodes.map((node) => ({ id: node.id, size: node.size })),
5088
- edges: styledEdges.map((edge) => ({
5089
- id: edge.id,
5090
- sourceId: edge.source.nodeId,
5091
- targetId: edge.target.nodeId
5092
- }))
5502
+ nodes: styledNodes,
5503
+ edges: styledEdges
5093
5504
  });
5094
5505
  diagnostics.push(...layout2.diagnostics);
5095
- const initialNodeBoxes = wrapVerticalStackIfNeeded(
5506
+ const initialNodeBoxes = initialLayoutMode === "positions" ? layout2.boxes : wrapVerticalStackIfNeeded(
5096
5507
  layout2.boxes,
5097
5508
  styledNodes,
5098
5509
  styledEdges,
@@ -5105,7 +5516,7 @@ function solveDiagram(diagram, options = {}) {
5105
5516
  direction: diagram.direction,
5106
5517
  overlapSpacing: options?.overlapSpacing ?? 40,
5107
5518
  ...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
5108
- ...options.distributeContainedChildren === void 0 ? {} : { distributeContainedChildren: options.distributeContainedChildren },
5519
+ distributeContainedChildren: options.distributeContainedChildren ?? true,
5109
5520
  boxes: initialNodeBoxes,
5110
5521
  nodes: styledNodes,
5111
5522
  constraints
@@ -5352,6 +5763,84 @@ function solveDiagram(diagram, options = {}) {
5352
5763
  function solveDiagramSafe(diagram, options = {}) {
5353
5764
  return solveDiagram(diagram, { ...options, prefitLabelSize: true });
5354
5765
  }
5766
+ function runInitialLayout(input) {
5767
+ if (input.mode === "positions") {
5768
+ return runPositionSeededInitialLayout(input);
5769
+ }
5770
+ const runAutoLayout = input.componentAware ? runComponentAwareDagreInitialLayout : runDagreInitialLayout;
5771
+ return runAutoLayout({
5772
+ direction: input.direction,
5773
+ nodes: input.nodes.map((node) => ({ id: node.id, size: node.size })),
5774
+ edges: input.edges.map((edge) => ({
5775
+ id: edge.id,
5776
+ sourceId: edge.source.nodeId,
5777
+ targetId: edge.target.nodeId
5778
+ }))
5779
+ });
5780
+ }
5781
+ function runPositionSeededInitialLayout(input) {
5782
+ const diagnostics = [];
5783
+ const boxes = /* @__PURE__ */ new Map();
5784
+ const autoNodes = [];
5785
+ for (const node of input.nodes) {
5786
+ if (!isValidInitialDimension(node.size.width) || !isValidInitialDimension(node.size.height)) {
5787
+ diagnostics.push({
5788
+ severity: "error",
5789
+ code: "layout.node-size.invalid",
5790
+ message: `Node ${node.id} has invalid layout dimensions.`,
5791
+ path: ["nodes", node.id, "size"],
5792
+ detail: { nodeId: node.id }
5793
+ });
5794
+ continue;
5795
+ }
5796
+ if (node.position === void 0) {
5797
+ autoNodes.push(node);
5798
+ continue;
5799
+ }
5800
+ if (!isFiniteInitialPoint(node.position)) {
5801
+ diagnostics.push({
5802
+ severity: "error",
5803
+ code: "layout.node-position.invalid",
5804
+ message: `Node ${node.id} has an invalid seeded position.`,
5805
+ path: ["nodes", node.id, "position"],
5806
+ detail: { nodeId: node.id }
5807
+ });
5808
+ continue;
5809
+ }
5810
+ boxes.set(node.id, {
5811
+ x: node.position.x,
5812
+ y: node.position.y,
5813
+ width: node.size.width,
5814
+ height: node.size.height
5815
+ });
5816
+ }
5817
+ if (autoNodes.length === 0) {
5818
+ return { boxes, diagnostics };
5819
+ }
5820
+ const autoNodeIds = new Set(autoNodes.map((node) => node.id));
5821
+ const autoLayout = runComponentAwareDagreInitialLayout({
5822
+ direction: input.direction,
5823
+ nodes: autoNodes.map((node) => ({ id: node.id, size: node.size })),
5824
+ edges: input.edges.filter(
5825
+ (edge) => autoNodeIds.has(edge.source.nodeId) && autoNodeIds.has(edge.target.nodeId)
5826
+ ).map((edge) => ({
5827
+ id: edge.id,
5828
+ sourceId: edge.source.nodeId,
5829
+ targetId: edge.target.nodeId
5830
+ }))
5831
+ });
5832
+ diagnostics.push(...autoLayout.diagnostics);
5833
+ for (const [id, box] of autoLayout.boxes) {
5834
+ boxes.set(id, box);
5835
+ }
5836
+ return { boxes, diagnostics };
5837
+ }
5838
+ function isValidInitialDimension(value) {
5839
+ return Number.isFinite(value) && value >= 0;
5840
+ }
5841
+ function isFiniteInitialPoint(point2) {
5842
+ return Number.isFinite(point2.x) && Number.isFinite(point2.y);
5843
+ }
5355
5844
  function prefitNodeLabelSize(node, options, diagnostics) {
5356
5845
  if (node.label === void 0) {
5357
5846
  return node;
@@ -7103,6 +7592,10 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7103
7592
  const coordinatedNodeById = new Map(
7104
7593
  coordinatedNodes.map((node) => [node.id, node])
7105
7594
  );
7595
+ const nodeObstacleIndex = createBoxSpatialIndex(
7596
+ obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
7597
+ options.routingGutter ?? 160
7598
+ );
7106
7599
  for (const edge of edges) {
7107
7600
  const source = nodes.get(edge.source.nodeId);
7108
7601
  const target = nodes.get(edge.target.nodeId);
@@ -7123,6 +7616,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7123
7616
  const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
7124
7617
  const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
7125
7618
  const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
7619
+ const corridor = edgeCorridorBox(
7620
+ source.box,
7621
+ target.box,
7622
+ options.routingGutter ?? 160
7623
+ );
7624
+ const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
7625
+ (obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
7626
+ );
7126
7627
  const route = routeEdge({
7127
7628
  kind: options.routeKind ?? "orthogonal",
7128
7629
  direction,
@@ -7131,9 +7632,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7131
7632
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
7132
7633
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
7133
7634
  obstacles: [
7134
- ...obstacles.filter(
7135
- (obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
7136
- ),
7635
+ ...routeNodeObstacles,
7137
7636
  ...softObstacles,
7138
7637
  ...groupObstaclesForEdge(edge, groups, options.obstacleMargin ?? 0),
7139
7638
  ...routeTextObstacles
@@ -7154,6 +7653,19 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
7154
7653
  }
7155
7654
  return coordinated;
7156
7655
  }
7656
+ function edgeCorridorBox(source, target, margin) {
7657
+ const minX = Math.min(source.x, target.x);
7658
+ const minY = Math.min(source.y, target.y);
7659
+ const maxX = Math.max(source.x + source.width, target.x + target.width);
7660
+ const maxY = Math.max(source.y + source.height, target.y + target.height);
7661
+ return expandBoxForQuery(
7662
+ { x: minX, y: minY, width: maxX - minX, height: maxY - minY },
7663
+ margin
7664
+ );
7665
+ }
7666
+ function sameBox(first, second) {
7667
+ return first.x === second.x && first.y === second.y && first.width === second.width && first.height === second.height;
7668
+ }
7157
7669
  function isEdgeConnectedTextAnnotation(edge, annotation) {
7158
7670
  switch (annotation.surfaceKind) {
7159
7671
  case "edge-label":
@@ -8042,6 +8554,7 @@ function renderDiagramDsl(source, options = {}) {
8042
8554
  return { diagnostics };
8043
8555
  }
8044
8556
  const solved = solveDiagram(normalized.diagram, {
8557
+ ...solveInitialLayoutOption(normalized.diagram.metadata?.initialLayout),
8045
8558
  routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : normalized.diagram.metadata?.routeKind === "obstacle-avoiding" ? "obstacle-avoiding" : "orthogonal",
8046
8559
  ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
8047
8560
  ...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
@@ -8083,6 +8596,9 @@ function renderDiagramDsl(source, options = {}) {
8083
8596
  function toSolveDiagnostic(diagnostic) {
8084
8597
  return { ...diagnostic, layer: "solve" };
8085
8598
  }
8599
+ function solveInitialLayoutOption(value) {
8600
+ return value === "positions" ? { initialLayout: "positions" } : {};
8601
+ }
8086
8602
  function solvePortShiftingOption(value) {
8087
8603
  if (!isJsonObject(value)) {
8088
8604
  return {};
@@ -8232,6 +8748,6 @@ function isPointLikeRecord(value) {
8232
8748
  return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
8233
8749
  }
8234
8750
 
8235
- export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
8751
+ export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, simplifyRoute2 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
8236
8752
  //# sourceMappingURL=index.js.map
8237
8753
  //# sourceMappingURL=index.js.map