@markdy/core 1.4.2 → 1.4.3
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 +179 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1023,17 +1023,32 @@ function dedupeEdges(edges) {
|
|
|
1023
1023
|
function assignRanks(nodeIds, edges, direction) {
|
|
1024
1024
|
const ranks = /* @__PURE__ */ new Map();
|
|
1025
1025
|
for (const id of nodeIds) ranks.set(id, 0);
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1026
|
+
const outgoing = new Map(nodeIds.map((id) => [id, []]));
|
|
1027
|
+
const indegree = new Map(nodeIds.map((id) => [id, 0]));
|
|
1028
|
+
for (const edge of edges) {
|
|
1029
|
+
if (edge.kind === "response" || edge.selfLoop || !outgoing.has(edge.from) || !outgoing.has(edge.to)) continue;
|
|
1030
|
+
outgoing.get(edge.from).push(edge.to);
|
|
1031
|
+
indegree.set(edge.to, indegree.get(edge.to) + 1);
|
|
1032
|
+
}
|
|
1033
|
+
const queue = nodeIds.filter((id) => indegree.get(id) === 0);
|
|
1034
|
+
const processed = /* @__PURE__ */ new Set();
|
|
1035
|
+
let cursor = 0;
|
|
1036
|
+
let cycleRoot = 0;
|
|
1037
|
+
while (processed.size < nodeIds.length) {
|
|
1038
|
+
if (cursor === queue.length) {
|
|
1039
|
+
while (processed.has(nodeIds[cycleRoot])) cycleRoot++;
|
|
1040
|
+
queue.push(nodeIds[cycleRoot]);
|
|
1041
|
+
}
|
|
1042
|
+
const id = queue[cursor++];
|
|
1043
|
+
if (processed.has(id)) continue;
|
|
1044
|
+
processed.add(id);
|
|
1045
|
+
for (const target of outgoing.get(id)) {
|
|
1046
|
+
if (processed.has(target)) continue;
|
|
1047
|
+
ranks.set(target, Math.max(ranks.get(target), ranks.get(id) + 1));
|
|
1048
|
+
const remaining = indegree.get(target) - 1;
|
|
1049
|
+
indegree.set(target, remaining);
|
|
1050
|
+
if (remaining === 0) queue.push(target);
|
|
1035
1051
|
}
|
|
1036
|
-
if (!changed) break;
|
|
1037
1052
|
}
|
|
1038
1053
|
if (direction === "RL" || direction === "BT") {
|
|
1039
1054
|
const max = Math.max(...ranks.values(), 0);
|
|
@@ -1047,7 +1062,7 @@ function snapGrid(n, step = 4) {
|
|
|
1047
1062
|
function layoutRanked(ast, edges, opts) {
|
|
1048
1063
|
const nodeIds = Object.keys(ast.nodes);
|
|
1049
1064
|
const dtype = diagramType(ast);
|
|
1050
|
-
const direction = opts.forceVertical ? "TB" : ast.meta.direction;
|
|
1065
|
+
const direction = opts.forceVertical ? ast.meta.direction === "BT" ? "BT" : "TB" : ast.meta.direction;
|
|
1051
1066
|
const ranks = assignRanks(nodeIds, edges, direction);
|
|
1052
1067
|
const byRank = /* @__PURE__ */ new Map();
|
|
1053
1068
|
for (const id of nodeIds) {
|
|
@@ -1072,6 +1087,7 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1072
1087
|
}
|
|
1073
1088
|
const rankOrder = /* @__PURE__ */ new Map();
|
|
1074
1089
|
const sortedRanks = [...byRank.keys()].sort((a, b) => a - b);
|
|
1090
|
+
if (direction === "RL" || direction === "BT") sortedRanks.reverse();
|
|
1075
1091
|
for (const r of sortedRanks) {
|
|
1076
1092
|
const ids = byRank.get(r);
|
|
1077
1093
|
ids.sort((a, b) => {
|
|
@@ -1091,6 +1107,39 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1091
1107
|
rankOrder.set(id, idx);
|
|
1092
1108
|
});
|
|
1093
1109
|
}
|
|
1110
|
+
const neighbors = new Map(nodeIds.map((id) => [id, []]));
|
|
1111
|
+
for (const edge of edges) {
|
|
1112
|
+
if (edge.kind === "response" || edge.selfLoop || !neighbors.has(edge.from) || !neighbors.has(edge.to)) continue;
|
|
1113
|
+
neighbors.get(edge.from).push(edge.to);
|
|
1114
|
+
neighbors.get(edge.to).push(edge.from);
|
|
1115
|
+
}
|
|
1116
|
+
for (let sweep = 0; sweep < 4; sweep++) {
|
|
1117
|
+
let changed = false;
|
|
1118
|
+
const order = sweep % 2 === 0 ? [...sortedRanks].reverse() : sortedRanks;
|
|
1119
|
+
for (const rank of order) {
|
|
1120
|
+
const ids = byRank.get(rank);
|
|
1121
|
+
for (let index = 0; index < ids.length - 1; index++) {
|
|
1122
|
+
const left = ids[index];
|
|
1123
|
+
const right = ids[index + 1];
|
|
1124
|
+
if (nodeGroupIndex.get(left) !== nodeGroupIndex.get(right)) continue;
|
|
1125
|
+
let improvement = 0;
|
|
1126
|
+
for (const leftNeighbor of neighbors.get(left)) {
|
|
1127
|
+
for (const rightNeighbor of neighbors.get(right)) {
|
|
1128
|
+
const neighborRank = ranks.get(leftNeighbor);
|
|
1129
|
+
if (neighborRank === rank || neighborRank !== ranks.get(rightNeighbor)) continue;
|
|
1130
|
+
improvement += Math.sign(rankOrder.get(leftNeighbor) - rankOrder.get(rightNeighbor));
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (improvement <= 0) continue;
|
|
1134
|
+
ids[index] = right;
|
|
1135
|
+
ids[index + 1] = left;
|
|
1136
|
+
rankOrder.set(right, index);
|
|
1137
|
+
rankOrder.set(left, index + 1);
|
|
1138
|
+
changed = true;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (!changed) break;
|
|
1142
|
+
}
|
|
1094
1143
|
const isVertical = direction === "TB" || direction === "BT";
|
|
1095
1144
|
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
1096
1145
|
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
@@ -1225,6 +1274,7 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1225
1274
|
function layoutTree(ast, edges) {
|
|
1226
1275
|
const nodeIds = Object.keys(ast.nodes);
|
|
1227
1276
|
if (nodeIds.length === 0) return [];
|
|
1277
|
+
edges = cycleSafeEdges(nodeIds, edges);
|
|
1228
1278
|
const nodeDims = /* @__PURE__ */ new Map();
|
|
1229
1279
|
for (const id of nodeIds) {
|
|
1230
1280
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
@@ -1395,19 +1445,21 @@ function cycleSafeEdges(nodeIds, edges) {
|
|
|
1395
1445
|
}
|
|
1396
1446
|
const visited = /* @__PURE__ */ new Set();
|
|
1397
1447
|
const safe = [];
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
visited.
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1448
|
+
const roots = nodeIds.filter((id) => !incoming.has(id));
|
|
1449
|
+
for (const root of [...roots, ...nodeIds]) {
|
|
1450
|
+
if (visited.has(root)) continue;
|
|
1451
|
+
const stack = [{ id: root }];
|
|
1452
|
+
while (stack.length > 0) {
|
|
1453
|
+
const current = stack.pop();
|
|
1454
|
+
if (visited.has(current.id)) continue;
|
|
1455
|
+
visited.add(current.id);
|
|
1456
|
+
if (current.edge) safe.push(current.edge);
|
|
1457
|
+
const children = outgoing.get(current.id);
|
|
1458
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
1459
|
+
stack.push({ id: children[index].to, edge: children[index] });
|
|
1460
|
+
}
|
|
1405
1461
|
}
|
|
1406
|
-
};
|
|
1407
|
-
for (const id of nodeIds) {
|
|
1408
|
-
if (!incoming.has(id)) visit(id);
|
|
1409
1462
|
}
|
|
1410
|
-
for (const id of nodeIds) visit(id);
|
|
1411
1463
|
return safe;
|
|
1412
1464
|
}
|
|
1413
1465
|
function layoutConstellation(ast) {
|
|
@@ -2549,7 +2601,8 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2549
2601
|
const children = /* @__PURE__ */ new Map();
|
|
2550
2602
|
const hasParent = /* @__PURE__ */ new Set();
|
|
2551
2603
|
for (const id of nodeIds) children.set(id, []);
|
|
2552
|
-
const
|
|
2604
|
+
const treeCandidates = effectiveEdges.some((e) => e.structural) ? effectiveEdges.filter((e) => e.structural) : effectiveEdges;
|
|
2605
|
+
const treeEdges = cycleSafeEdges(nodeIds, treeCandidates);
|
|
2553
2606
|
for (const e of treeEdges) {
|
|
2554
2607
|
if (ast.nodes[e.from] && ast.nodes[e.to] && e.from !== e.to) {
|
|
2555
2608
|
children.get(e.from)?.push(e.to);
|
|
@@ -5257,6 +5310,97 @@ function buildSmoothSvgPath(start, waypoints, end, cornerRadius = 0) {
|
|
|
5257
5310
|
d += ` L ${end.x} ${end.y}`;
|
|
5258
5311
|
return d;
|
|
5259
5312
|
}
|
|
5313
|
+
function routeAroundObstacles(sourceBox, targetBox, points, options) {
|
|
5314
|
+
if (!options.obstacles?.length) return points;
|
|
5315
|
+
const margin = options.margin ?? 20;
|
|
5316
|
+
const obstacles = [sourceBox, targetBox, ...options.obstacles];
|
|
5317
|
+
const intersections = (route) => {
|
|
5318
|
+
let hits = 0;
|
|
5319
|
+
for (let index = 1; index < route.length; index++) {
|
|
5320
|
+
const start2 = route[index - 1];
|
|
5321
|
+
const end2 = route[index];
|
|
5322
|
+
if (start2.x === end2.x && start2.y === end2.y) continue;
|
|
5323
|
+
for (const box of obstacles) {
|
|
5324
|
+
if (start2.y === end2.y && start2.y > box.y && start2.y < box.y + box.height && Math.max(start2.x, end2.x) > box.x && Math.min(start2.x, end2.x) < box.x + box.width) hits++;
|
|
5325
|
+
else if (start2.x === end2.x && start2.x > box.x && start2.x < box.x + box.width && Math.max(start2.y, end2.y) > box.y && Math.min(start2.y, end2.y) < box.y + box.height) hits++;
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
return hits;
|
|
5329
|
+
};
|
|
5330
|
+
if (intersections(points) === 0) return points;
|
|
5331
|
+
const start = points[0];
|
|
5332
|
+
const end = points[points.length - 1];
|
|
5333
|
+
const stub = (point, port) => {
|
|
5334
|
+
let distance = margin;
|
|
5335
|
+
for (const box of obstacles) {
|
|
5336
|
+
let clearance = Number.POSITIVE_INFINITY;
|
|
5337
|
+
if (point.y > box.y && point.y < box.y + box.height) {
|
|
5338
|
+
if (port === "right") clearance = box.x - point.x;
|
|
5339
|
+
if (port === "left") clearance = point.x - box.x - box.width;
|
|
5340
|
+
}
|
|
5341
|
+
if (point.x > box.x && point.x < box.x + box.width) {
|
|
5342
|
+
if (port === "bottom") clearance = box.y - point.y;
|
|
5343
|
+
if (port === "top") clearance = point.y - box.y - box.height;
|
|
5344
|
+
}
|
|
5345
|
+
if (clearance >= 0) distance = Math.min(distance, clearance / 2);
|
|
5346
|
+
}
|
|
5347
|
+
return {
|
|
5348
|
+
x: point.x + (port === "right" ? distance : port === "left" ? -distance : 0),
|
|
5349
|
+
y: point.y + (port === "bottom" ? distance : port === "top" ? -distance : 0)
|
|
5350
|
+
};
|
|
5351
|
+
};
|
|
5352
|
+
const sourceStub = stub(start, options.sourcePort);
|
|
5353
|
+
const targetStub = stub(end, options.targetPort);
|
|
5354
|
+
const corridorsX = /* @__PURE__ */ new Set();
|
|
5355
|
+
const corridorsY = /* @__PURE__ */ new Set();
|
|
5356
|
+
for (const box of obstacles) {
|
|
5357
|
+
corridorsX.add(box.x - margin);
|
|
5358
|
+
corridorsX.add(box.x + box.width + margin);
|
|
5359
|
+
corridorsY.add(box.y - margin);
|
|
5360
|
+
corridorsY.add(box.y + box.height + margin);
|
|
5361
|
+
}
|
|
5362
|
+
const candidates = [
|
|
5363
|
+
points,
|
|
5364
|
+
[start, sourceStub, { x: sourceStub.x, y: targetStub.y }, targetStub, end],
|
|
5365
|
+
[start, sourceStub, { x: targetStub.x, y: sourceStub.y }, targetStub, end]
|
|
5366
|
+
];
|
|
5367
|
+
for (const corridor of corridorsX) {
|
|
5368
|
+
candidates.push([
|
|
5369
|
+
start,
|
|
5370
|
+
sourceStub,
|
|
5371
|
+
{ x: corridor, y: sourceStub.y },
|
|
5372
|
+
{ x: corridor, y: targetStub.y },
|
|
5373
|
+
targetStub,
|
|
5374
|
+
end
|
|
5375
|
+
]);
|
|
5376
|
+
}
|
|
5377
|
+
for (const corridor of corridorsY) {
|
|
5378
|
+
candidates.push([
|
|
5379
|
+
start,
|
|
5380
|
+
sourceStub,
|
|
5381
|
+
{ x: sourceStub.x, y: corridor },
|
|
5382
|
+
{ x: targetStub.x, y: corridor },
|
|
5383
|
+
targetStub,
|
|
5384
|
+
end
|
|
5385
|
+
]);
|
|
5386
|
+
}
|
|
5387
|
+
let best = points;
|
|
5388
|
+
let bestHits = Number.POSITIVE_INFINITY;
|
|
5389
|
+
let bestLength = Number.POSITIVE_INFINITY;
|
|
5390
|
+
for (const candidate of candidates) {
|
|
5391
|
+
const hits = intersections(candidate);
|
|
5392
|
+
let length = 0;
|
|
5393
|
+
for (let index = 1; index < candidate.length; index++) {
|
|
5394
|
+
length += Math.abs(candidate[index].x - candidate[index - 1].x) + Math.abs(candidate[index].y - candidate[index - 1].y);
|
|
5395
|
+
}
|
|
5396
|
+
if (hits < bestHits || hits === bestHits && length < bestLength) {
|
|
5397
|
+
best = candidate;
|
|
5398
|
+
bestHits = hits;
|
|
5399
|
+
bestLength = length;
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
return best.filter((point, index) => index === 0 || point.x !== best[index - 1].x || point.y !== best[index - 1].y);
|
|
5403
|
+
}
|
|
5260
5404
|
function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
5261
5405
|
const isSelfLoop = sourceBox === targetBox || sourceBox.x === targetBox.x && sourceBox.y === targetBox.y && sourceBox.width === targetBox.width && sourceBox.height === targetBox.height;
|
|
5262
5406
|
const MARGIN = options.margin ?? 20;
|
|
@@ -5299,6 +5443,11 @@ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
|
5299
5443
|
end2 = { x: rightX, y: endY };
|
|
5300
5444
|
waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
|
|
5301
5445
|
}
|
|
5446
|
+
waypoints2 = routeAroundObstacles(sourceBox, targetBox, [start2, ...waypoints2, end2], {
|
|
5447
|
+
...options,
|
|
5448
|
+
sourcePort: port,
|
|
5449
|
+
targetPort: port
|
|
5450
|
+
}).slice(1, -1);
|
|
5302
5451
|
const svgPathData2 = buildSmoothSvgPath(start2, waypoints2, end2, options.cornerRadius ?? 6);
|
|
5303
5452
|
return {
|
|
5304
5453
|
sourcePort: port,
|
|
@@ -5381,13 +5530,18 @@ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
|
|
|
5381
5530
|
}
|
|
5382
5531
|
waypoints.push(p2);
|
|
5383
5532
|
}
|
|
5384
|
-
const
|
|
5533
|
+
const routedWaypoints = routeAroundObstacles(sourceBox, targetBox, [start, ...waypoints, end], {
|
|
5534
|
+
...options,
|
|
5535
|
+
sourcePort,
|
|
5536
|
+
targetPort
|
|
5537
|
+
}).slice(1, -1);
|
|
5538
|
+
const svgPathData = buildSmoothSvgPath(start, routedWaypoints, end, options.cornerRadius ?? 6);
|
|
5385
5539
|
return {
|
|
5386
5540
|
sourcePort,
|
|
5387
5541
|
targetPort,
|
|
5388
5542
|
startPoint: start,
|
|
5389
5543
|
endPoint: end,
|
|
5390
|
-
waypoints,
|
|
5544
|
+
waypoints: routedWaypoints,
|
|
5391
5545
|
svgPathData
|
|
5392
5546
|
};
|
|
5393
5547
|
}
|
package/package.json
CHANGED