@crazyhappyone/auto-graph 0.2.4 → 0.2.6
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/cli/index.cjs +901 -38
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +901 -38
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +904 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -1
- package/dist/index.d.ts +51 -1
- package/dist/index.js +902 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1034,6 +1034,69 @@ function applyDistributeContained(input, boxes, locks, diagnostics) {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
});
|
|
1036
1036
|
}
|
|
1037
|
+
if (input.swimlanes !== void 0 && input.swimlanes.length > 0 && input.distributeSwimlaneChildren) {
|
|
1038
|
+
distributeSwimlaneChildren(input, boxes, locks, diagnostics);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function distributeSwimlaneChildren(input, boxes, locks, diagnostics) {
|
|
1042
|
+
const spread = input.distributeSwimlaneChildren === "spread";
|
|
1043
|
+
const minGap = input.minSiblingGap ?? 8;
|
|
1044
|
+
for (const swimlane of input.swimlanes) {
|
|
1045
|
+
if (swimlane.layout === "contract") continue;
|
|
1046
|
+
const isVertical = swimlane.orientation !== "horizontal";
|
|
1047
|
+
const axis = isVertical ? "x" : "y";
|
|
1048
|
+
const mainSize = isVertical ? "width" : "height";
|
|
1049
|
+
for (const lane of swimlane.lanes) {
|
|
1050
|
+
if (lane.children.length < 2) continue;
|
|
1051
|
+
const unlocked = [];
|
|
1052
|
+
const reserved = [];
|
|
1053
|
+
for (const childId of lane.children) {
|
|
1054
|
+
const box = boxes.get(childId);
|
|
1055
|
+
if (box === void 0) continue;
|
|
1056
|
+
if (locks.has(childId)) {
|
|
1057
|
+
const lock = locks.get(childId);
|
|
1058
|
+
if (lock.source === "fixed-position") {
|
|
1059
|
+
unlocked.push({ id: childId, box });
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
reserved.push(intervalForBox(box, axis, mainSize));
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
1065
|
+
unlocked.push({ id: childId, box });
|
|
1066
|
+
}
|
|
1067
|
+
if (unlocked.length < 2) continue;
|
|
1068
|
+
const contentStart = isVertical ? Math.min(...unlocked.map((c) => c.box.x)) : Math.min(...unlocked.map((c) => c.box.y));
|
|
1069
|
+
const contentEnd = isVertical ? Math.max(...unlocked.map((c) => c.box.x + c.box.width)) : Math.max(...unlocked.map((c) => c.box.y + c.box.height));
|
|
1070
|
+
const contentSpan = contentEnd - contentStart;
|
|
1071
|
+
const totalChildSpan = unlocked.reduce((s, c) => s + c.box[mainSize], 0);
|
|
1072
|
+
const reservedSpan = reserved.reduce((s, r) => s + (r.end - r.start), 0);
|
|
1073
|
+
let effectiveGap = minGap;
|
|
1074
|
+
const remaining = contentSpan - totalChildSpan - reservedSpan - minGap * (unlocked.length - 1);
|
|
1075
|
+
if (spread && remaining > 0) {
|
|
1076
|
+
effectiveGap = minGap + remaining / (unlocked.length - 1);
|
|
1077
|
+
}
|
|
1078
|
+
unlocked.sort((a, b) => a.box[axis] - b.box[axis]);
|
|
1079
|
+
let pos = contentStart;
|
|
1080
|
+
for (const child of unlocked) {
|
|
1081
|
+
pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
|
|
1082
|
+
const newBox = { ...child.box };
|
|
1083
|
+
if (axis === "x") {
|
|
1084
|
+
newBox.x = pos;
|
|
1085
|
+
} else {
|
|
1086
|
+
newBox.y = pos;
|
|
1087
|
+
}
|
|
1088
|
+
boxes.set(child.id, newBox);
|
|
1089
|
+
locks.delete(child.id);
|
|
1090
|
+
pos += child.box[mainSize] + effectiveGap;
|
|
1091
|
+
}
|
|
1092
|
+
diagnostics.push({
|
|
1093
|
+
severity: "info",
|
|
1094
|
+
code: "intra_container_distributed",
|
|
1095
|
+
message: `Distributed ${unlocked.length} children in swimlane ${lane.id} along ${axis}.`,
|
|
1096
|
+
detail: { containerId: lane.id, count: unlocked.length, axis }
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1037
1100
|
}
|
|
1038
1101
|
function intervalForBox(box, axis, mainSize) {
|
|
1039
1102
|
return { start: box[axis], end: box[axis] + box[mainSize] };
|
|
@@ -4305,6 +4368,397 @@ function connectedComponents(nodes, edges) {
|
|
|
4305
4368
|
});
|
|
4306
4369
|
}
|
|
4307
4370
|
|
|
4371
|
+
// src/layout/recursive.ts
|
|
4372
|
+
function buildContainerTree(groups, constraints, edges) {
|
|
4373
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
4374
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
4375
|
+
for (const group of groups) {
|
|
4376
|
+
const children = [];
|
|
4377
|
+
for (const nodeId of group.nodeIds) {
|
|
4378
|
+
children.push(nodeId);
|
|
4379
|
+
parentOf.set(nodeId, group.id);
|
|
4380
|
+
}
|
|
4381
|
+
for (const childGroupId of group.groupIds) {
|
|
4382
|
+
children.push(childGroupId);
|
|
4383
|
+
parentOf.set(childGroupId, group.id);
|
|
4384
|
+
}
|
|
4385
|
+
childrenOf.set(group.id, children);
|
|
4386
|
+
}
|
|
4387
|
+
for (const c of constraints) {
|
|
4388
|
+
if (c.kind !== "containment") continue;
|
|
4389
|
+
for (const childId of c.childIds) {
|
|
4390
|
+
const existing = parentOf.get(childId);
|
|
4391
|
+
if (existing !== void 0) {
|
|
4392
|
+
if (existing === c.containerId) continue;
|
|
4393
|
+
const oldSiblings = childrenOf.get(existing) ?? [];
|
|
4394
|
+
const pruned = oldSiblings.filter((id) => id !== childId);
|
|
4395
|
+
if (pruned.length === 0) {
|
|
4396
|
+
childrenOf.delete(existing);
|
|
4397
|
+
} else {
|
|
4398
|
+
childrenOf.set(existing, pruned);
|
|
4399
|
+
}
|
|
4400
|
+
const newSiblings = childrenOf.get(c.containerId) ?? [];
|
|
4401
|
+
newSiblings.push(childId);
|
|
4402
|
+
childrenOf.set(c.containerId, newSiblings);
|
|
4403
|
+
parentOf.set(childId, c.containerId);
|
|
4404
|
+
} else {
|
|
4405
|
+
const list = childrenOf.get(c.containerId) ?? [];
|
|
4406
|
+
list.push(childId);
|
|
4407
|
+
childrenOf.set(c.containerId, list);
|
|
4408
|
+
parentOf.set(childId, c.containerId);
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
const rootIds = /* @__PURE__ */ new Set();
|
|
4413
|
+
for (const group of groups) {
|
|
4414
|
+
if (!parentOf.has(group.id)) {
|
|
4415
|
+
rootIds.add(group.id);
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
const edgesInGroup = /* @__PURE__ */ new Map();
|
|
4419
|
+
for (const edge of edges) {
|
|
4420
|
+
const srcParent = parentOf.get(edge.source.nodeId);
|
|
4421
|
+
const tgtParent = parentOf.get(edge.target.nodeId);
|
|
4422
|
+
if (srcParent !== void 0 && srcParent === tgtParent) {
|
|
4423
|
+
const list = edgesInGroup.get(srcParent) ?? [];
|
|
4424
|
+
list.push(edge);
|
|
4425
|
+
edgesInGroup.set(srcParent, list);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
const treeDiagnostics = [];
|
|
4429
|
+
return { childrenOf, rootIds, edgesInGroup, diagnostics: treeDiagnostics };
|
|
4430
|
+
}
|
|
4431
|
+
function runRecursiveContainerLayout(input) {
|
|
4432
|
+
const diagnostics = [];
|
|
4433
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
4434
|
+
const groupBoxes = /* @__PURE__ */ new Map();
|
|
4435
|
+
const nodeById = new Map(input.nodes.map((n) => [n.id, n]));
|
|
4436
|
+
const groupById = new Map(input.groups.map((g) => [g.id, g]));
|
|
4437
|
+
const groupIdSet = new Set(input.groups.map((g) => g.id));
|
|
4438
|
+
const {
|
|
4439
|
+
childrenOf,
|
|
4440
|
+
rootIds,
|
|
4441
|
+
edgesInGroup,
|
|
4442
|
+
diagnostics: treeDiagnostics
|
|
4443
|
+
} = buildContainerTree(input.groups, input.constraints, input.edges);
|
|
4444
|
+
diagnostics.push(...treeDiagnostics);
|
|
4445
|
+
if (input.groups.length === 0) {
|
|
4446
|
+
const flat = runComponentAwareDagreInitialLayout({
|
|
4447
|
+
direction: input.direction,
|
|
4448
|
+
nodes: input.nodes.map((n) => ({ id: n.id, size: n.size })),
|
|
4449
|
+
edges: input.edges.map((e) => ({
|
|
4450
|
+
id: e.id,
|
|
4451
|
+
sourceId: e.source.nodeId,
|
|
4452
|
+
targetId: e.target.nodeId
|
|
4453
|
+
})),
|
|
4454
|
+
...input.options === void 0 ? {} : { options: input.options }
|
|
4455
|
+
});
|
|
4456
|
+
diagnostics.push(...flat.diagnostics);
|
|
4457
|
+
for (const [id, box] of flat.boxes) {
|
|
4458
|
+
boxes.set(id, box);
|
|
4459
|
+
}
|
|
4460
|
+
return { boxes, groupBoxes, diagnostics };
|
|
4461
|
+
}
|
|
4462
|
+
const descendants = /* @__PURE__ */ new Map();
|
|
4463
|
+
function collectDescendants(groupId) {
|
|
4464
|
+
const cached = descendants.get(groupId);
|
|
4465
|
+
if (cached !== void 0) return cached;
|
|
4466
|
+
const result = /* @__PURE__ */ new Set();
|
|
4467
|
+
const children = childrenOf.get(groupId) ?? [];
|
|
4468
|
+
for (const childId of children) {
|
|
4469
|
+
result.add(childId);
|
|
4470
|
+
const childDesc = collectDescendants(childId);
|
|
4471
|
+
for (const d of childDesc) result.add(d);
|
|
4472
|
+
}
|
|
4473
|
+
descendants.set(groupId, result);
|
|
4474
|
+
return result;
|
|
4475
|
+
}
|
|
4476
|
+
const groupOrder = topologicalSort(input.groups, childrenOf, groupIdSet);
|
|
4477
|
+
for (const groupId of groupOrder) {
|
|
4478
|
+
const group = groupById.get(groupId);
|
|
4479
|
+
if (group === void 0) continue;
|
|
4480
|
+
const children = childrenOf.get(groupId) ?? [];
|
|
4481
|
+
if (children.length === 0) {
|
|
4482
|
+
const box = {
|
|
4483
|
+
x: 0,
|
|
4484
|
+
y: 0,
|
|
4485
|
+
width: (group.padding?.left ?? 8) + (group.padding?.right ?? 8) + 40,
|
|
4486
|
+
height: (group.padding?.top ?? 8) + (group.padding?.bottom ?? 8) + 20
|
|
4487
|
+
};
|
|
4488
|
+
groupBoxes.set(groupId, box);
|
|
4489
|
+
boxes.set(groupId, box);
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
const leafNodeIds = [];
|
|
4493
|
+
const nestedGroupIds = [];
|
|
4494
|
+
for (const childId of children) {
|
|
4495
|
+
if (groupIdSet.has(childId)) {
|
|
4496
|
+
nestedGroupIds.push(childId);
|
|
4497
|
+
} else {
|
|
4498
|
+
leafNodeIds.push(childId);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
const childSizes = /* @__PURE__ */ new Map();
|
|
4502
|
+
for (const nodeId of leafNodeIds) {
|
|
4503
|
+
const node = nodeById.get(nodeId);
|
|
4504
|
+
if (node !== void 0) {
|
|
4505
|
+
childSizes.set(nodeId, {
|
|
4506
|
+
width: node.size.width,
|
|
4507
|
+
height: node.size.height
|
|
4508
|
+
});
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
for (const nestedId of nestedGroupIds) {
|
|
4512
|
+
const nestedBox = groupBoxes.get(nestedId);
|
|
4513
|
+
if (nestedBox !== void 0) {
|
|
4514
|
+
childSizes.set(nestedId, {
|
|
4515
|
+
width: nestedBox.width,
|
|
4516
|
+
height: nestedBox.height
|
|
4517
|
+
});
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
const groupEdges = edgesInGroup.get(groupId) ?? [];
|
|
4521
|
+
const childLayout = runDagreInitialLayout({
|
|
4522
|
+
direction: input.direction,
|
|
4523
|
+
nodes: children.flatMap((childId) => {
|
|
4524
|
+
const size = childSizes.get(childId);
|
|
4525
|
+
return size === void 0 ? [] : [{ id: childId, size }];
|
|
4526
|
+
}),
|
|
4527
|
+
edges: groupEdges.map((e) => ({
|
|
4528
|
+
id: e.id,
|
|
4529
|
+
sourceId: e.source.nodeId,
|
|
4530
|
+
targetId: e.target.nodeId
|
|
4531
|
+
})),
|
|
4532
|
+
options: {
|
|
4533
|
+
...input.options ?? {},
|
|
4534
|
+
ranksep: (input.options?.ranksep ?? 100) * 0.6,
|
|
4535
|
+
// tighter inside containers
|
|
4536
|
+
nodesep: (input.options?.nodesep ?? 80) * 0.6
|
|
4537
|
+
}
|
|
4538
|
+
});
|
|
4539
|
+
diagnostics.push(...childLayout.diagnostics);
|
|
4540
|
+
if (childLayout.boxes.size === 0) continue;
|
|
4541
|
+
const childBoxes = [...childLayout.boxes.values()];
|
|
4542
|
+
const contentBounds = unionBoxes(childBoxes);
|
|
4543
|
+
const padding = group.padding ?? {
|
|
4544
|
+
top: 8,
|
|
4545
|
+
right: 8,
|
|
4546
|
+
bottom: 8,
|
|
4547
|
+
left: 8
|
|
4548
|
+
};
|
|
4549
|
+
const containerBox = {
|
|
4550
|
+
x: 0,
|
|
4551
|
+
y: 0,
|
|
4552
|
+
width: contentBounds.width + (padding.left ?? 8) + (padding.right ?? 8),
|
|
4553
|
+
height: contentBounds.height + (padding.top ?? 8) + (padding.bottom ?? 8)
|
|
4554
|
+
};
|
|
4555
|
+
const offsetX = padding.left ?? 8;
|
|
4556
|
+
const offsetY = padding.top ?? 8;
|
|
4557
|
+
for (const [childId, childBox] of childLayout.boxes) {
|
|
4558
|
+
boxes.set(childId, {
|
|
4559
|
+
...childBox,
|
|
4560
|
+
x: childBox.x + offsetX,
|
|
4561
|
+
y: childBox.y + offsetY
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
groupBoxes.set(groupId, containerBox);
|
|
4565
|
+
boxes.set(groupId, containerBox);
|
|
4566
|
+
}
|
|
4567
|
+
const allContainedIds = /* @__PURE__ */ new Set();
|
|
4568
|
+
for (const [, childIds] of childrenOf) {
|
|
4569
|
+
for (const cid of childIds) allContainedIds.add(cid);
|
|
4570
|
+
}
|
|
4571
|
+
const topLevelNodeIds = /* @__PURE__ */ new Set();
|
|
4572
|
+
for (const node of input.nodes) {
|
|
4573
|
+
if (!allContainedIds.has(node.id)) {
|
|
4574
|
+
topLevelNodeIds.add(node.id);
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
const globalNodes = [];
|
|
4578
|
+
for (const nodeId of topLevelNodeIds) {
|
|
4579
|
+
const node = nodeById.get(nodeId);
|
|
4580
|
+
if (node !== void 0) {
|
|
4581
|
+
globalNodes.push({ id: nodeId, size: node.size });
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
for (const rootId of rootIds) {
|
|
4585
|
+
const gb = groupBoxes.get(rootId);
|
|
4586
|
+
if (gb !== void 0) {
|
|
4587
|
+
globalNodes.push({
|
|
4588
|
+
id: rootId,
|
|
4589
|
+
size: { width: gb.width, height: gb.height }
|
|
4590
|
+
});
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
function rootContainerOf(id) {
|
|
4594
|
+
for (const rootId of rootIds) {
|
|
4595
|
+
const desc = collectDescendants(rootId);
|
|
4596
|
+
if (desc.has(id)) return rootId;
|
|
4597
|
+
}
|
|
4598
|
+
return void 0;
|
|
4599
|
+
}
|
|
4600
|
+
const globalEdges = input.edges.filter((e) => {
|
|
4601
|
+
for (const group of input.groups) {
|
|
4602
|
+
const desc = collectDescendants(group.id);
|
|
4603
|
+
if (desc.has(e.source.nodeId) && desc.has(e.target.nodeId)) {
|
|
4604
|
+
return false;
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
return true;
|
|
4608
|
+
}).map((e) => ({
|
|
4609
|
+
id: e.id,
|
|
4610
|
+
sourceId: rootContainerOf(e.source.nodeId) ?? e.source.nodeId,
|
|
4611
|
+
targetId: rootContainerOf(e.target.nodeId) ?? e.target.nodeId
|
|
4612
|
+
}));
|
|
4613
|
+
if (globalNodes.length > 0) {
|
|
4614
|
+
const globalLayout = runDagreInitialLayout({
|
|
4615
|
+
direction: input.direction,
|
|
4616
|
+
nodes: globalNodes,
|
|
4617
|
+
edges: globalEdges,
|
|
4618
|
+
...input.options === void 0 ? {} : { options: input.options }
|
|
4619
|
+
});
|
|
4620
|
+
diagnostics.push(...globalLayout.diagnostics);
|
|
4621
|
+
for (const [id, box] of globalLayout.boxes) {
|
|
4622
|
+
if (groupBoxes.has(id)) {
|
|
4623
|
+
groupBoxes.set(id, box);
|
|
4624
|
+
boxes.set(id, box);
|
|
4625
|
+
} else if (topLevelNodeIds.has(id)) {
|
|
4626
|
+
boxes.set(id, box);
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
for (const groupId of groupOrder) {
|
|
4630
|
+
const containerBox = groupBoxes.get(groupId);
|
|
4631
|
+
if (containerBox === void 0) continue;
|
|
4632
|
+
const offsetX = containerBox.x;
|
|
4633
|
+
const offsetY = containerBox.y;
|
|
4634
|
+
const children = childrenOf.get(groupId) ?? [];
|
|
4635
|
+
for (const childId of children) {
|
|
4636
|
+
const childBox = boxes.get(childId);
|
|
4637
|
+
if (childBox !== void 0) {
|
|
4638
|
+
boxes.set(childId, {
|
|
4639
|
+
...childBox,
|
|
4640
|
+
x: childBox.x + offsetX,
|
|
4641
|
+
y: childBox.y + offsetY
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
translateDescendants(childId, offsetX, offsetY, boxes, childrenOf);
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
return { boxes, groupBoxes, diagnostics };
|
|
4649
|
+
}
|
|
4650
|
+
function translateDescendants(groupId, dx, dy, boxes, childrenOf) {
|
|
4651
|
+
const children = childrenOf.get(groupId) ?? [];
|
|
4652
|
+
for (const childId of children) {
|
|
4653
|
+
const box = boxes.get(childId);
|
|
4654
|
+
if (box !== void 0) {
|
|
4655
|
+
boxes.set(childId, { ...box, x: box.x + dx, y: box.y + dy });
|
|
4656
|
+
}
|
|
4657
|
+
translateDescendants(childId, dx, dy, boxes, childrenOf);
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4660
|
+
function topologicalSort(groups, childrenOf, groupIdSet) {
|
|
4661
|
+
const visited = /* @__PURE__ */ new Set();
|
|
4662
|
+
const result = [];
|
|
4663
|
+
function visit(id) {
|
|
4664
|
+
if (visited.has(id)) return;
|
|
4665
|
+
visited.add(id);
|
|
4666
|
+
const children = childrenOf.get(id) ?? [];
|
|
4667
|
+
for (const childId of children) {
|
|
4668
|
+
if (groupIdSet.has(childId)) {
|
|
4669
|
+
visit(childId);
|
|
4670
|
+
}
|
|
4671
|
+
}
|
|
4672
|
+
result.push(id);
|
|
4673
|
+
}
|
|
4674
|
+
for (const group of groups) {
|
|
4675
|
+
visit(group.id);
|
|
4676
|
+
}
|
|
4677
|
+
return result;
|
|
4678
|
+
}
|
|
4679
|
+
|
|
4680
|
+
// src/routing/binary-heap.ts
|
|
4681
|
+
var BinaryHeap = class {
|
|
4682
|
+
_data = [];
|
|
4683
|
+
_nextOrder = 0;
|
|
4684
|
+
get size() {
|
|
4685
|
+
return this._data.length;
|
|
4686
|
+
}
|
|
4687
|
+
push(value, priority) {
|
|
4688
|
+
const entry = { value, priority, order: this._nextOrder++ };
|
|
4689
|
+
this._data.push(entry);
|
|
4690
|
+
this._siftUp(this._data.length - 1);
|
|
4691
|
+
}
|
|
4692
|
+
pop() {
|
|
4693
|
+
if (this._data.length === 0) return void 0;
|
|
4694
|
+
const top = this._data[0];
|
|
4695
|
+
const last = this._data.pop();
|
|
4696
|
+
if (this._data.length > 0) {
|
|
4697
|
+
this._data[0] = last;
|
|
4698
|
+
this._siftDown(0);
|
|
4699
|
+
}
|
|
4700
|
+
return top.value;
|
|
4701
|
+
}
|
|
4702
|
+
peek() {
|
|
4703
|
+
return this._data.length > 0 ? this._data[0].value : void 0;
|
|
4704
|
+
}
|
|
4705
|
+
// -----------------------------------------------------------------------
|
|
4706
|
+
// Internals
|
|
4707
|
+
// -----------------------------------------------------------------------
|
|
4708
|
+
_siftUp(idx) {
|
|
4709
|
+
const entry = this._data[idx];
|
|
4710
|
+
while (idx > 0) {
|
|
4711
|
+
const parentIdx = idx - 1 >> 1;
|
|
4712
|
+
const parent = this._data[parentIdx];
|
|
4713
|
+
if (this._less(entry, parent)) {
|
|
4714
|
+
this._data[idx] = parent;
|
|
4715
|
+
idx = parentIdx;
|
|
4716
|
+
} else {
|
|
4717
|
+
break;
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
this._data[idx] = entry;
|
|
4721
|
+
}
|
|
4722
|
+
_siftDown(idx) {
|
|
4723
|
+
const entry = this._data[idx];
|
|
4724
|
+
const size = this._data.length;
|
|
4725
|
+
while (true) {
|
|
4726
|
+
let smallestIdx = idx;
|
|
4727
|
+
const leftIdx = (idx << 1) + 1;
|
|
4728
|
+
const rightIdx = leftIdx + 1;
|
|
4729
|
+
if (leftIdx < size && this._less(
|
|
4730
|
+
this._data[leftIdx],
|
|
4731
|
+
this._data[smallestIdx]
|
|
4732
|
+
)) {
|
|
4733
|
+
smallestIdx = leftIdx;
|
|
4734
|
+
}
|
|
4735
|
+
if (rightIdx < size && this._less(
|
|
4736
|
+
this._data[rightIdx],
|
|
4737
|
+
this._data[smallestIdx]
|
|
4738
|
+
)) {
|
|
4739
|
+
smallestIdx = rightIdx;
|
|
4740
|
+
}
|
|
4741
|
+
if (smallestIdx !== idx) {
|
|
4742
|
+
this._data[idx] = this._data[smallestIdx];
|
|
4743
|
+
idx = smallestIdx;
|
|
4744
|
+
} else {
|
|
4745
|
+
break;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
this._data[idx] = entry;
|
|
4749
|
+
}
|
|
4750
|
+
/**
|
|
4751
|
+
* Two entries are compared first by priority, then by insertion order
|
|
4752
|
+
* when priorities are equal. The insertion-order tie-break makes the
|
|
4753
|
+
* heap deterministic: for a given sequence of {value, priority} pushes,
|
|
4754
|
+
* the extraction order is always the same.
|
|
4755
|
+
*/
|
|
4756
|
+
_less(a, b) {
|
|
4757
|
+
if (a.priority !== b.priority) return a.priority < b.priority;
|
|
4758
|
+
return a.order < b.order;
|
|
4759
|
+
}
|
|
4760
|
+
};
|
|
4761
|
+
|
|
4308
4762
|
// src/routing/astar.ts
|
|
4309
4763
|
function findObstacleFreePath(source, target, obstacles, options = {}, diagnostics) {
|
|
4310
4764
|
const margin = options.margin ?? 0;
|
|
@@ -4312,8 +4766,17 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4312
4766
|
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
4313
4767
|
const endpointObstacles = options.endpointObstacles ?? [];
|
|
4314
4768
|
const maxNodes = options.maxNodes ?? (obstacles.length > 30 ? 16e3 : 4e3);
|
|
4315
|
-
const
|
|
4316
|
-
const
|
|
4769
|
+
const useCorridor = options.corridorPrefilter ?? true;
|
|
4770
|
+
const corridorMargin = options.corridorMargin ?? 32;
|
|
4771
|
+
const filtered = useCorridor ? filterObstaclesByCorridor(
|
|
4772
|
+
source,
|
|
4773
|
+
target,
|
|
4774
|
+
obstacles,
|
|
4775
|
+
endpointObstacles,
|
|
4776
|
+
corridorMargin
|
|
4777
|
+
) : obstacles;
|
|
4778
|
+
const xs = collectXs(source, target, filtered, margin);
|
|
4779
|
+
const ys = collectYs(source, target, filtered, margin);
|
|
4317
4780
|
if (xs.length * ys.length > maxNodes) {
|
|
4318
4781
|
diagnostics?.push({
|
|
4319
4782
|
severity: "warning",
|
|
@@ -4328,8 +4791,8 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4328
4791
|
return null;
|
|
4329
4792
|
}
|
|
4330
4793
|
const { nodes, nodeIndex } = buildGraph(xs, ys);
|
|
4331
|
-
connectHorizontalEdges(nodes, ys,
|
|
4332
|
-
connectVerticalEdges(nodes, xs,
|
|
4794
|
+
connectHorizontalEdges(nodes, ys, filtered, endpointObstacles, margin);
|
|
4795
|
+
connectVerticalEdges(nodes, xs, filtered, endpointObstacles, margin);
|
|
4333
4796
|
const path = aStarSearch(
|
|
4334
4797
|
nodes,
|
|
4335
4798
|
nodeIndex,
|
|
@@ -4341,6 +4804,22 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4341
4804
|
if (path === null) return null;
|
|
4342
4805
|
return simplifyRoute(path);
|
|
4343
4806
|
}
|
|
4807
|
+
function filterObstaclesByCorridor(source, target, obstacles, endpointObstacles, margin) {
|
|
4808
|
+
const cx1 = Math.min(source.x, target.x) - margin;
|
|
4809
|
+
const cx2 = Math.max(source.x, target.x) + margin;
|
|
4810
|
+
const cy1 = Math.min(source.y, target.y) - margin;
|
|
4811
|
+
const cy2 = Math.max(source.y, target.y) + margin;
|
|
4812
|
+
const result = [];
|
|
4813
|
+
for (const obs of obstacles) {
|
|
4814
|
+
if (obs.x + obs.width >= cx1 && obs.x <= cx2 && obs.y + obs.height >= cy1 && obs.y <= cy2) {
|
|
4815
|
+
result.push(obs);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
for (const ep of endpointObstacles) {
|
|
4819
|
+
result.push(ep);
|
|
4820
|
+
}
|
|
4821
|
+
return result;
|
|
4822
|
+
}
|
|
4344
4823
|
function collectXs(source, target, obstacles, margin) {
|
|
4345
4824
|
const raw = [];
|
|
4346
4825
|
for (const obs of obstacles) {
|
|
@@ -4484,25 +4963,17 @@ function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenal
|
|
|
4484
4963
|
gScore.set(startId, 0);
|
|
4485
4964
|
const cameFrom = /* @__PURE__ */ new Map();
|
|
4486
4965
|
const cameFromDir = /* @__PURE__ */ new Map();
|
|
4487
|
-
const openSet =
|
|
4488
|
-
openSet.push(
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
for (let i = 1; i < openSet.length; i++) {
|
|
4495
|
-
if (openSet[i].f < openSet[bestIdx].f) {
|
|
4496
|
-
bestIdx = i;
|
|
4497
|
-
}
|
|
4498
|
-
}
|
|
4499
|
-
const current = openSet.splice(bestIdx, 1)[0];
|
|
4500
|
-
if (current.id === goalId) {
|
|
4966
|
+
const openSet = new BinaryHeap();
|
|
4967
|
+
openSet.push(startId, manhattan(source, target));
|
|
4968
|
+
while (openSet.size > 0) {
|
|
4969
|
+
const currentId = openSet.pop();
|
|
4970
|
+
const currentG = gScore.get(currentId);
|
|
4971
|
+
if (currentG === void 0) continue;
|
|
4972
|
+
if (currentId === goalId) {
|
|
4501
4973
|
return reconstructPath(nodes, cameFrom, goalId);
|
|
4502
4974
|
}
|
|
4503
|
-
const node = nodes[
|
|
4504
|
-
const
|
|
4505
|
-
const prevDir = cameFromDir.get(current.id);
|
|
4975
|
+
const node = nodes[currentId];
|
|
4976
|
+
const prevDir = cameFromDir.get(currentId);
|
|
4506
4977
|
for (const [neighborId, edgeCost] of node.neighbors) {
|
|
4507
4978
|
const neighbor = nodes[neighborId];
|
|
4508
4979
|
const tentativeG = currentG + edgeCost * segmentPenalty;
|
|
@@ -4512,10 +4983,10 @@ function aStarSearch(nodes, nodeIndex, source, target, turnPenalty, segmentPenal
|
|
|
4512
4983
|
const existingG = gScore.get(neighborId);
|
|
4513
4984
|
if (existingG === void 0 || totalG < existingG) {
|
|
4514
4985
|
gScore.set(neighborId, totalG);
|
|
4515
|
-
cameFrom.set(neighborId,
|
|
4986
|
+
cameFrom.set(neighborId, currentId);
|
|
4516
4987
|
cameFromDir.set(neighborId, newDir);
|
|
4517
4988
|
const f = totalG + manhattan(neighbor, target);
|
|
4518
|
-
openSet.push(
|
|
4989
|
+
openSet.push(neighborId, f);
|
|
4519
4990
|
}
|
|
4520
4991
|
}
|
|
4521
4992
|
}
|
|
@@ -4552,6 +5023,292 @@ function areCollinear(a, b, c) {
|
|
|
4552
5023
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4553
5024
|
}
|
|
4554
5025
|
|
|
5026
|
+
// src/routing/visibility-router.ts
|
|
5027
|
+
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
5028
|
+
const margin = options.margin ?? 0;
|
|
5029
|
+
const turnPenalty = options.turnPenalty ?? 50;
|
|
5030
|
+
const segmentPenalty = options.segmentPenalty ?? 1;
|
|
5031
|
+
const endpointObstacles = options.endpointObstacles ?? [];
|
|
5032
|
+
const maxCorners = options.maxCorners ?? 300;
|
|
5033
|
+
const vertices = collectCornerVertices(source, target, obstacles, margin);
|
|
5034
|
+
if (vertices.length > maxCorners) {
|
|
5035
|
+
diagnostics?.push({
|
|
5036
|
+
severity: "warning",
|
|
5037
|
+
code: "routing.visibility.corner_overflow",
|
|
5038
|
+
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
5039
|
+
detail: { vertexCount: vertices.length, maxCorners }
|
|
5040
|
+
});
|
|
5041
|
+
return null;
|
|
5042
|
+
}
|
|
5043
|
+
if (obstacles.length === 0) {
|
|
5044
|
+
return simplifyRoute2([source, target]);
|
|
5045
|
+
}
|
|
5046
|
+
const expandedObstacles = margin === 0 ? obstacles : obstacles.map((o) => expandBox2(o, margin));
|
|
5047
|
+
const edges = buildVisibilityEdges(
|
|
5048
|
+
vertices,
|
|
5049
|
+
expandedObstacles,
|
|
5050
|
+
endpointObstacles
|
|
5051
|
+
);
|
|
5052
|
+
const path = aStarSearch2(
|
|
5053
|
+
vertices,
|
|
5054
|
+
edges,
|
|
5055
|
+
source,
|
|
5056
|
+
target,
|
|
5057
|
+
segmentPenalty,
|
|
5058
|
+
turnPenalty
|
|
5059
|
+
);
|
|
5060
|
+
if (path === null) return null;
|
|
5061
|
+
return simplifyRoute2(path, expandedObstacles);
|
|
5062
|
+
}
|
|
5063
|
+
function collectCornerVertices(source, target, obstacles, margin = 0) {
|
|
5064
|
+
const vertices = [
|
|
5065
|
+
{ point: { x: source.x, y: source.y }, obstacleIndex: -1 },
|
|
5066
|
+
{ point: { x: target.x, y: target.y }, obstacleIndex: -1 }
|
|
5067
|
+
];
|
|
5068
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5069
|
+
seen.add(`${source.x},${source.y}`);
|
|
5070
|
+
seen.add(`${target.x},${target.y}`);
|
|
5071
|
+
const addVertex = (p, obstacleIndex) => {
|
|
5072
|
+
const key = `${p.x},${p.y}`;
|
|
5073
|
+
if (seen.has(key)) return;
|
|
5074
|
+
seen.add(key);
|
|
5075
|
+
vertices.push({ point: p, obstacleIndex });
|
|
5076
|
+
};
|
|
5077
|
+
for (let i = 0; i < obstacles.length; i++) {
|
|
5078
|
+
const obs = obstacles[i];
|
|
5079
|
+
const c0 = { x: obs.x - margin, y: obs.y - margin };
|
|
5080
|
+
const c1 = { x: obs.x + obs.width + margin, y: obs.y - margin };
|
|
5081
|
+
const c2 = {
|
|
5082
|
+
x: obs.x + obs.width + margin,
|
|
5083
|
+
y: obs.y + obs.height + margin
|
|
5084
|
+
};
|
|
5085
|
+
const c3 = { x: obs.x - margin, y: obs.y + obs.height + margin };
|
|
5086
|
+
for (const c of [c0, c1, c2, c3]) {
|
|
5087
|
+
addVertex(c, i);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
for (const p of [source, target]) {
|
|
5091
|
+
for (let i = 0; i < obstacles.length; i++) {
|
|
5092
|
+
const obs = obstacles[i];
|
|
5093
|
+
addVertex({ x: obs.x - margin, y: p.y }, i);
|
|
5094
|
+
addVertex({ x: obs.x + obs.width + margin, y: p.y }, i);
|
|
5095
|
+
addVertex({ x: p.x, y: obs.y - margin }, i);
|
|
5096
|
+
addVertex({ x: p.x, y: obs.y + obs.height + margin }, i);
|
|
5097
|
+
if (p.x > obs.x && p.x < obs.x + obs.width) {
|
|
5098
|
+
addVertex({ x: p.x, y: obs.y - margin }, i);
|
|
5099
|
+
addVertex({ x: p.x, y: obs.y + obs.height + margin }, i);
|
|
5100
|
+
}
|
|
5101
|
+
if (p.y > obs.y && p.y < obs.y + obs.height) {
|
|
5102
|
+
addVertex({ x: obs.x - margin, y: p.y }, i);
|
|
5103
|
+
addVertex({ x: obs.x + obs.width + margin, y: p.y }, i);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
return vertices;
|
|
5108
|
+
}
|
|
5109
|
+
function buildVisibilityEdges(vertices, obstacles, endpointObstacles) {
|
|
5110
|
+
const edges = [];
|
|
5111
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
5112
|
+
const v = vertices[i];
|
|
5113
|
+
const right = visibleInDirection(
|
|
5114
|
+
v,
|
|
5115
|
+
vertices,
|
|
5116
|
+
obstacles,
|
|
5117
|
+
endpointObstacles,
|
|
5118
|
+
"right"
|
|
5119
|
+
);
|
|
5120
|
+
const left = visibleInDirection(
|
|
5121
|
+
v,
|
|
5122
|
+
vertices,
|
|
5123
|
+
obstacles,
|
|
5124
|
+
endpointObstacles,
|
|
5125
|
+
"left"
|
|
5126
|
+
);
|
|
5127
|
+
const down = visibleInDirection(
|
|
5128
|
+
v,
|
|
5129
|
+
vertices,
|
|
5130
|
+
obstacles,
|
|
5131
|
+
endpointObstacles,
|
|
5132
|
+
"down"
|
|
5133
|
+
);
|
|
5134
|
+
const up = visibleInDirection(
|
|
5135
|
+
v,
|
|
5136
|
+
vertices,
|
|
5137
|
+
obstacles,
|
|
5138
|
+
endpointObstacles,
|
|
5139
|
+
"up"
|
|
5140
|
+
);
|
|
5141
|
+
for (const neighbor of [right, left, down, up]) {
|
|
5142
|
+
if (neighbor !== null && neighbor.index > i) {
|
|
5143
|
+
edges.push({
|
|
5144
|
+
from: i,
|
|
5145
|
+
to: neighbor.index,
|
|
5146
|
+
cost: neighbor.distance
|
|
5147
|
+
});
|
|
5148
|
+
}
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5151
|
+
return edges;
|
|
5152
|
+
}
|
|
5153
|
+
function visibleInDirection(origin, vertices, obstacles, endpointObstacles, dir) {
|
|
5154
|
+
const candidates = [];
|
|
5155
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
5156
|
+
const v = vertices[i];
|
|
5157
|
+
const dx = v.point.x - origin.point.x;
|
|
5158
|
+
const dy = v.point.y - origin.point.y;
|
|
5159
|
+
switch (dir) {
|
|
5160
|
+
case "right":
|
|
5161
|
+
if (dx > 0 && dy === 0) candidates.push({ index: i, dist: dx });
|
|
5162
|
+
break;
|
|
5163
|
+
case "left":
|
|
5164
|
+
if (dx < 0 && dy === 0) candidates.push({ index: i, dist: -dx });
|
|
5165
|
+
break;
|
|
5166
|
+
case "down":
|
|
5167
|
+
if (dy > 0 && dx === 0) candidates.push({ index: i, dist: dy });
|
|
5168
|
+
break;
|
|
5169
|
+
case "up":
|
|
5170
|
+
if (dy < 0 && dx === 0) candidates.push({ index: i, dist: -dy });
|
|
5171
|
+
break;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
candidates.sort((a, b) => a.dist - b.dist);
|
|
5175
|
+
for (const c of candidates) {
|
|
5176
|
+
if (isSegmentVisible(
|
|
5177
|
+
origin.point,
|
|
5178
|
+
vertices[c.index].point,
|
|
5179
|
+
obstacles,
|
|
5180
|
+
endpointObstacles,
|
|
5181
|
+
origin.obstacleIndex,
|
|
5182
|
+
vertices[c.index].obstacleIndex
|
|
5183
|
+
)) {
|
|
5184
|
+
return { index: c.index, distance: c.dist };
|
|
5185
|
+
}
|
|
5186
|
+
}
|
|
5187
|
+
return null;
|
|
5188
|
+
}
|
|
5189
|
+
function isSegmentVisible(a, b, obstacles, endpointObstacles, _aObsIdx, _bObsIdx) {
|
|
5190
|
+
for (let i = 0; i < obstacles.length; i++) {
|
|
5191
|
+
if (segmentEntersBox(a, b, obstacles[i])) return false;
|
|
5192
|
+
}
|
|
5193
|
+
for (const ep of endpointObstacles) {
|
|
5194
|
+
if (segmentEntersBox(a, b, ep)) return false;
|
|
5195
|
+
}
|
|
5196
|
+
return true;
|
|
5197
|
+
}
|
|
5198
|
+
function segmentEntersBox(start, end, box) {
|
|
5199
|
+
const left = box.x;
|
|
5200
|
+
const right = box.x + box.width;
|
|
5201
|
+
const top = box.y;
|
|
5202
|
+
const bottom = box.y + box.height;
|
|
5203
|
+
const inside = (p) => p.x > left && p.x < right && p.y > top && p.y < bottom;
|
|
5204
|
+
if (inside(start) || inside(end)) return true;
|
|
5205
|
+
if (start.x === end.x) {
|
|
5206
|
+
return start.x > left && start.x < right && Math.max(start.y, end.y) > top && Math.min(start.y, end.y) < bottom;
|
|
5207
|
+
}
|
|
5208
|
+
if (start.y === end.y) {
|
|
5209
|
+
return start.y > top && start.y < bottom && Math.max(start.x, end.x) > left && Math.min(start.x, end.x) < right;
|
|
5210
|
+
}
|
|
5211
|
+
return false;
|
|
5212
|
+
}
|
|
5213
|
+
function expandBox2(box, margin) {
|
|
5214
|
+
return {
|
|
5215
|
+
x: box.x - margin,
|
|
5216
|
+
y: box.y - margin,
|
|
5217
|
+
width: box.width + margin * 2,
|
|
5218
|
+
height: box.height + margin * 2
|
|
5219
|
+
};
|
|
5220
|
+
}
|
|
5221
|
+
function aStarSearch2(vertices, edges, source, target, segmentPenalty, turnPenalty) {
|
|
5222
|
+
const startId = 0;
|
|
5223
|
+
const goalId = 1;
|
|
5224
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
5225
|
+
gScore.set(startId, 0);
|
|
5226
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
5227
|
+
const cameFromDir = /* @__PURE__ */ new Map();
|
|
5228
|
+
const openSet = new BinaryHeap();
|
|
5229
|
+
openSet.push(startId, manhattan2(source, target));
|
|
5230
|
+
const neighborMap = /* @__PURE__ */ new Map();
|
|
5231
|
+
for (const e of edges) {
|
|
5232
|
+
let list = neighborMap.get(e.from);
|
|
5233
|
+
if (list === void 0) {
|
|
5234
|
+
list = [];
|
|
5235
|
+
neighborMap.set(e.from, list);
|
|
5236
|
+
}
|
|
5237
|
+
list.push({ to: e.to, cost: e.cost });
|
|
5238
|
+
list = neighborMap.get(e.to);
|
|
5239
|
+
if (list === void 0) {
|
|
5240
|
+
list = [];
|
|
5241
|
+
neighborMap.set(e.to, list);
|
|
5242
|
+
}
|
|
5243
|
+
list.push({ to: e.from, cost: e.cost });
|
|
5244
|
+
}
|
|
5245
|
+
while (openSet.size > 0) {
|
|
5246
|
+
const currentId = openSet.pop();
|
|
5247
|
+
const currentG = gScore.get(currentId);
|
|
5248
|
+
if (currentG === void 0) continue;
|
|
5249
|
+
if (currentId === goalId) {
|
|
5250
|
+
return reconstructPath2(vertices, cameFrom, goalId);
|
|
5251
|
+
}
|
|
5252
|
+
const prevDir = cameFromDir.get(currentId);
|
|
5253
|
+
const neighbors = neighborMap.get(currentId) ?? [];
|
|
5254
|
+
for (const { to, cost } of neighbors) {
|
|
5255
|
+
const tentativeG = currentG + cost * segmentPenalty;
|
|
5256
|
+
const toV = vertices[to];
|
|
5257
|
+
const curV = vertices[currentId];
|
|
5258
|
+
const newDir = toV.point.y === curV.point.y ? "h" : "v";
|
|
5259
|
+
const turnCost = prevDir !== void 0 && prevDir !== newDir ? turnPenalty : 0;
|
|
5260
|
+
const totalG = tentativeG + turnCost;
|
|
5261
|
+
const existingG = gScore.get(to);
|
|
5262
|
+
if (existingG === void 0 || totalG < existingG) {
|
|
5263
|
+
gScore.set(to, totalG);
|
|
5264
|
+
cameFrom.set(to, currentId);
|
|
5265
|
+
cameFromDir.set(to, newDir);
|
|
5266
|
+
const f = totalG + manhattan2(toV.point, target);
|
|
5267
|
+
openSet.push(to, f);
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
return null;
|
|
5272
|
+
}
|
|
5273
|
+
function manhattan2(a, b) {
|
|
5274
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
5275
|
+
}
|
|
5276
|
+
function reconstructPath2(vertices, cameFrom, goalId) {
|
|
5277
|
+
const path = [];
|
|
5278
|
+
let current = goalId;
|
|
5279
|
+
while (current !== void 0) {
|
|
5280
|
+
const v = vertices[current];
|
|
5281
|
+
path.unshift({ x: v.point.x, y: v.point.y });
|
|
5282
|
+
current = cameFrom.get(current);
|
|
5283
|
+
}
|
|
5284
|
+
return path;
|
|
5285
|
+
}
|
|
5286
|
+
function simplifyRoute2(points, obstacles = []) {
|
|
5287
|
+
if (points.length <= 2) return [...points];
|
|
5288
|
+
const result = [points[0]];
|
|
5289
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
5290
|
+
const prev = result[result.length - 1];
|
|
5291
|
+
const curr = points[i];
|
|
5292
|
+
const next = points[i + 1];
|
|
5293
|
+
const collinear = prev.x === curr.x && curr.x === next.x || prev.y === curr.y && curr.y === next.y;
|
|
5294
|
+
if (!collinear) {
|
|
5295
|
+
result.push(curr);
|
|
5296
|
+
continue;
|
|
5297
|
+
}
|
|
5298
|
+
if (segmentCrossesAnyObstacle(prev, next, obstacles)) {
|
|
5299
|
+
result.push(curr);
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
result.push(points[points.length - 1]);
|
|
5303
|
+
return result;
|
|
5304
|
+
}
|
|
5305
|
+
function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
5306
|
+
for (const obs of obstacles) {
|
|
5307
|
+
if (segmentEntersBox(a, b, obs)) return true;
|
|
5308
|
+
}
|
|
5309
|
+
return false;
|
|
5310
|
+
}
|
|
5311
|
+
|
|
4555
5312
|
// src/routing/routes.ts
|
|
4556
5313
|
function checkBacktracking(points, source, target, diagnostics) {
|
|
4557
5314
|
if (points.length < 2) return;
|
|
@@ -4641,13 +5398,18 @@ function routeEdge(input) {
|
|
|
4641
5398
|
input.source.center,
|
|
4642
5399
|
targetAnchor
|
|
4643
5400
|
);
|
|
4644
|
-
const
|
|
5401
|
+
const cornerPath = findCornerGraphPath(
|
|
4645
5402
|
source,
|
|
4646
5403
|
target,
|
|
4647
5404
|
[...softObstacles, ...hardObstacles],
|
|
4648
|
-
{
|
|
4649
|
-
|
|
4650
|
-
|
|
5405
|
+
{ endpointObstacles, margin: 2 },
|
|
5406
|
+
diagnostics
|
|
5407
|
+
);
|
|
5408
|
+
const path = cornerPath ?? findObstacleFreePath(
|
|
5409
|
+
source,
|
|
5410
|
+
target,
|
|
5411
|
+
[...softObstacles, ...hardObstacles],
|
|
5412
|
+
{ endpointObstacles, margin: 0 },
|
|
4651
5413
|
diagnostics
|
|
4652
5414
|
);
|
|
4653
5415
|
if (path !== null && path.length >= 2) {
|
|
@@ -4890,7 +5652,7 @@ function routeEdge(input) {
|
|
|
4890
5652
|
};
|
|
4891
5653
|
}
|
|
4892
5654
|
function finalizeRoute(points, softObstacles, hardObstacles, diagnostics, softObstacleIndex, hardObstacleIndex) {
|
|
4893
|
-
const simplified =
|
|
5655
|
+
const simplified = simplifyRoute3(points);
|
|
4894
5656
|
if (simplified.length >= 3) {
|
|
4895
5657
|
return simplified;
|
|
4896
5658
|
}
|
|
@@ -5194,7 +5956,7 @@ function squaredDistance2(a, b) {
|
|
|
5194
5956
|
const dy = a.y - b.y;
|
|
5195
5957
|
return dx * dx + dy * dy;
|
|
5196
5958
|
}
|
|
5197
|
-
function
|
|
5959
|
+
function simplifyRoute3(points) {
|
|
5198
5960
|
const withoutDuplicates = [];
|
|
5199
5961
|
for (const point2 of points) {
|
|
5200
5962
|
const previous = withoutDuplicates.at(-1);
|
|
@@ -5745,7 +6507,21 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5745
6507
|
);
|
|
5746
6508
|
const constraints = stableByConstraintId(diagram.constraints);
|
|
5747
6509
|
const initialLayoutMode = options.initialLayout ?? "dagre";
|
|
5748
|
-
const
|
|
6510
|
+
const useRecursive = options.recursiveLayout === true;
|
|
6511
|
+
if (useRecursive && initialLayoutMode === "positions") {
|
|
6512
|
+
diagnostics.push({
|
|
6513
|
+
severity: "warning",
|
|
6514
|
+
code: "layout.recursive-ignores-positions",
|
|
6515
|
+
message: 'recursiveLayout overrides initialLayout "positions" \u2014 seed positions are ignored for bottom-up container layout.'
|
|
6516
|
+
});
|
|
6517
|
+
}
|
|
6518
|
+
const layout2 = useRecursive ? runRecursiveContainerLayout({
|
|
6519
|
+
direction: diagram.direction,
|
|
6520
|
+
nodes: styledNodes,
|
|
6521
|
+
groups: styledGroups,
|
|
6522
|
+
edges: styledEdges,
|
|
6523
|
+
constraints
|
|
6524
|
+
}) : runInitialLayout({
|
|
5749
6525
|
mode: initialLayoutMode,
|
|
5750
6526
|
componentAware: options.maxStackDepth === void 0,
|
|
5751
6527
|
direction: diagram.direction,
|
|
@@ -5761,12 +6537,32 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5761
6537
|
options,
|
|
5762
6538
|
diagnostics
|
|
5763
6539
|
);
|
|
6540
|
+
if ((diagram.direction === "TB" || diagram.direction === "BT") && options.maxRowDepth !== void 0) {
|
|
6541
|
+
const rewrapped = wrapHorizontalStackIfNeeded(
|
|
6542
|
+
initialNodeBoxes,
|
|
6543
|
+
styledNodes,
|
|
6544
|
+
diagram.direction,
|
|
6545
|
+
options,
|
|
6546
|
+
diagnostics
|
|
6547
|
+
);
|
|
6548
|
+
for (const [id, box] of rewrapped) {
|
|
6549
|
+
initialNodeBoxes.set(id, box);
|
|
6550
|
+
}
|
|
6551
|
+
}
|
|
6552
|
+
if (useRecursive && "groupBoxes" in layout2) {
|
|
6553
|
+
const recursiveLayout = layout2;
|
|
6554
|
+
for (const [groupId, groupBox] of recursiveLayout.groupBoxes) {
|
|
6555
|
+
initialNodeBoxes.set(groupId, groupBox);
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
5764
6558
|
expandNodeBoxesForPorts(styledNodes, initialNodeBoxes, options, diagnostics);
|
|
5765
6559
|
const constrained = applyLayoutConstraints({
|
|
5766
6560
|
direction: diagram.direction,
|
|
5767
6561
|
overlapSpacing: options?.overlapSpacing ?? 40,
|
|
5768
6562
|
...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
|
|
5769
6563
|
distributeContainedChildren: options.distributeContainedChildren ?? true,
|
|
6564
|
+
distributeSwimlaneChildren: options.distributeSwimlaneChildren ?? "spread",
|
|
6565
|
+
swimlanes: styledSwimlanes,
|
|
5770
6566
|
boxes: initialNodeBoxes,
|
|
5771
6567
|
nodes: styledNodes,
|
|
5772
6568
|
constraints
|
|
@@ -6429,7 +7225,7 @@ function wrapVerticalStackIfNeeded(boxes, nodes, edges, direction, options, diag
|
|
|
6429
7225
|
);
|
|
6430
7226
|
return wrapped;
|
|
6431
7227
|
}
|
|
6432
|
-
if (edges.length > 0 || !
|
|
7228
|
+
if (edges.length > 0 || !isStackRunaway(wrapped, nodes, direction, options)) {
|
|
6433
7229
|
reportVerticalRunaway(
|
|
6434
7230
|
wrapped,
|
|
6435
7231
|
nodes,
|
|
@@ -6480,8 +7276,57 @@ function wrapVerticalStackIfNeeded(boxes, nodes, edges, direction, options, diag
|
|
|
6480
7276
|
});
|
|
6481
7277
|
return wrapped;
|
|
6482
7278
|
}
|
|
7279
|
+
function wrapHorizontalStackIfNeeded(boxes, nodes, direction, options, diagnostics) {
|
|
7280
|
+
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
7281
|
+
return new Map(boxes);
|
|
7282
|
+
}
|
|
7283
|
+
const maxRowDepth = options.maxRowDepth;
|
|
7284
|
+
if (maxRowDepth === void 0 || nodes.length <= maxRowDepth) {
|
|
7285
|
+
return new Map(boxes);
|
|
7286
|
+
}
|
|
7287
|
+
const ordered = [...nodes].sort((a, b) => {
|
|
7288
|
+
const ba = boxes.get(a.id);
|
|
7289
|
+
const bb = boxes.get(b.id);
|
|
7290
|
+
if (ba === void 0 || bb === void 0) return 0;
|
|
7291
|
+
const dx = ba.x - bb.x;
|
|
7292
|
+
return dx !== 0 ? dx : ba.y - bb.y;
|
|
7293
|
+
});
|
|
7294
|
+
const rows = Math.ceil(ordered.length / maxRowDepth);
|
|
7295
|
+
const wrapped = new Map(boxes);
|
|
7296
|
+
const rowSpacing = options.overlapSpacing ?? 40;
|
|
7297
|
+
let minX = Infinity;
|
|
7298
|
+
let minY = Infinity;
|
|
7299
|
+
let maxH = 0;
|
|
7300
|
+
for (const n of ordered) {
|
|
7301
|
+
const b = boxes.get(n.id);
|
|
7302
|
+
if (b !== void 0) {
|
|
7303
|
+
minX = Math.min(minX, b.x);
|
|
7304
|
+
minY = Math.min(minY, b.y);
|
|
7305
|
+
maxH = Math.max(maxH, b.height);
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
for (let ri = 0; ri < rows; ri++) {
|
|
7309
|
+
const rowNodes = ordered.slice(ri * maxRowDepth, (ri + 1) * maxRowDepth);
|
|
7310
|
+
let x = minX;
|
|
7311
|
+
const y = minY + ri * (maxH + rowSpacing);
|
|
7312
|
+
for (const node of rowNodes) {
|
|
7313
|
+
const box = boxes.get(node.id);
|
|
7314
|
+
if (box === void 0) continue;
|
|
7315
|
+
wrapped.set(node.id, { ...box, x, y });
|
|
7316
|
+
x += box.width + rowSpacing;
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
diagnostics.push({
|
|
7320
|
+
severity: "warning",
|
|
7321
|
+
code: "horizontal_runaway",
|
|
7322
|
+
message: `Single-row layout exceeded maxRowDepth ${maxRowDepth}; wrapped into ${rows} rows.`,
|
|
7323
|
+
path: ["nodes"],
|
|
7324
|
+
detail: { nodeCount: ordered.length, maxRowDepth, rows }
|
|
7325
|
+
});
|
|
7326
|
+
return wrapped;
|
|
7327
|
+
}
|
|
6483
7328
|
function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnostics) {
|
|
6484
|
-
if (!
|
|
7329
|
+
if (!isStackRunaway(boxes, nodes, direction, options)) {
|
|
6485
7330
|
return;
|
|
6486
7331
|
}
|
|
6487
7332
|
diagnostics.push({
|
|
@@ -6497,7 +7342,7 @@ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnost
|
|
|
6497
7342
|
}
|
|
6498
7343
|
});
|
|
6499
7344
|
}
|
|
6500
|
-
function
|
|
7345
|
+
function isStackRunaway(boxes, nodes, direction, options) {
|
|
6501
7346
|
if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
|
|
6502
7347
|
return false;
|
|
6503
7348
|
}
|
|
@@ -7088,7 +7933,7 @@ function coordinateNodes(nodes, boxes, options, diagnostics) {
|
|
|
7088
7933
|
});
|
|
7089
7934
|
continue;
|
|
7090
7935
|
}
|
|
7091
|
-
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting);
|
|
7936
|
+
const ports = node.ports === void 0 ? void 0 : coordinatePorts(node, box, options.portShifting, diagnostics);
|
|
7092
7937
|
const geometry = computeShapeGeometry({
|
|
7093
7938
|
shape: node.shape,
|
|
7094
7939
|
box,
|
|
@@ -7182,7 +8027,7 @@ function expandNodeBoxesForPorts(nodes, boxes, options, diagnostics) {
|
|
|
7182
8027
|
}
|
|
7183
8028
|
}
|
|
7184
8029
|
}
|
|
7185
|
-
function coordinatePorts(node, nodeBox, portShifting) {
|
|
8030
|
+
function coordinatePorts(node, nodeBox, portShifting, diagnostics) {
|
|
7186
8031
|
const portsBySide = /* @__PURE__ */ new Map();
|
|
7187
8032
|
for (const port of node.ports ?? []) {
|
|
7188
8033
|
const ports = portsBySide.get(port.side) ?? [];
|
|
@@ -7205,7 +8050,9 @@ function coordinatePorts(node, nodeBox, portShifting) {
|
|
|
7205
8050
|
side,
|
|
7206
8051
|
index,
|
|
7207
8052
|
sorted.length,
|
|
7208
|
-
portShifting
|
|
8053
|
+
portShifting,
|
|
8054
|
+
diagnostics,
|
|
8055
|
+
node.id
|
|
7209
8056
|
);
|
|
7210
8057
|
const box = portBox(anchor);
|
|
7211
8058
|
coordinated.push({ ...port, box, anchor });
|
|
@@ -7213,16 +8060,32 @@ function coordinatePorts(node, nodeBox, portShifting) {
|
|
|
7213
8060
|
}
|
|
7214
8061
|
return coordinated.sort((a, b) => a.id.localeCompare(b.id));
|
|
7215
8062
|
}
|
|
7216
|
-
function portAnchor(nodeBox, side, index, count, portShifting) {
|
|
8063
|
+
function portAnchor(nodeBox, side, index, count, portShifting, diagnostics, nodeId) {
|
|
7217
8064
|
const shiftingEnabled = portShifting?.enabled ?? true;
|
|
7218
8065
|
const requestedSpacing = portShifting?.spacing ?? 24;
|
|
7219
8066
|
const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
|
|
7220
8067
|
const availableSpan = 2 * maxOffset;
|
|
7221
8068
|
const minSpacing = PORT_BOX_SIZE + MIN_PORT_EDGE_GAP;
|
|
7222
|
-
const
|
|
8069
|
+
const effectiveSpacing = shiftingEnabled && count > 1 ? Math.max(
|
|
7223
8070
|
Math.min(requestedSpacing, availableSpan / (count - 1)),
|
|
7224
8071
|
minSpacing
|
|
7225
8072
|
) : requestedSpacing;
|
|
8073
|
+
if (shiftingEnabled && count > 1 && effectiveSpacing < requestedSpacing && diagnostics !== void 0 && nodeId !== void 0) {
|
|
8074
|
+
diagnostics.push({
|
|
8075
|
+
severity: "warning",
|
|
8076
|
+
code: "port_constraint_overlap",
|
|
8077
|
+
message: `Port spacing on ${nodeId} ${side} compressed from ${requestedSpacing}px to ${Math.round(effectiveSpacing)}px for ${count} ports.`,
|
|
8078
|
+
path: ["nodes", nodeId, "ports"],
|
|
8079
|
+
detail: {
|
|
8080
|
+
nodeId,
|
|
8081
|
+
side,
|
|
8082
|
+
requestedSpacing,
|
|
8083
|
+
effectiveSpacing: Math.round(effectiveSpacing),
|
|
8084
|
+
portCount: count
|
|
8085
|
+
}
|
|
8086
|
+
});
|
|
8087
|
+
}
|
|
8088
|
+
const spacing = effectiveSpacing;
|
|
7226
8089
|
const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
|
|
7227
8090
|
switch (side) {
|
|
7228
8091
|
case "left":
|
|
@@ -9022,6 +9885,6 @@ function isPointLikeRecord(value) {
|
|
|
9022
9885
|
return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
|
|
9023
9886
|
}
|
|
9024
9887
|
|
|
9025
|
-
export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, LayoutPipeline, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultPipeline, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout,
|
|
9888
|
+
export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, LayoutPipeline, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, buildContainerTree, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createBoxSpatialIndex, createDefaultPipeline, createDefaultTextMeasurer, expandBox, expandBoxForQuery, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, overlapArea, parseDiagramDsl, parseEdgeShorthand, queryBoxSpatialIndex, querySegmentSpatialIndex, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runComponentAwareDagreInitialLayout, runDagreInitialLayout, runRecursiveContainerLayout, simplifyRoute3 as simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
|
|
9026
9889
|
//# sourceMappingURL=index.js.map
|
|
9027
9890
|
//# sourceMappingURL=index.js.map
|