@markdy/core 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +551 -245
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -758,6 +758,7 @@ var SCENE_KEYS = /* @__PURE__ */ new Set([
|
|
|
758
758
|
"duration",
|
|
759
759
|
"direction",
|
|
760
760
|
"layout",
|
|
761
|
+
"rankdir",
|
|
761
762
|
"type",
|
|
762
763
|
...PLAYER_FLAT_KEYS
|
|
763
764
|
]);
|
|
@@ -803,7 +804,6 @@ var SAFE = 48;
|
|
|
803
804
|
var TITLE_BAND = 84;
|
|
804
805
|
var NODE_W = 180;
|
|
805
806
|
var NODE_H = 76;
|
|
806
|
-
var VENN_NODE_SIZE = 220;
|
|
807
807
|
var GROUP_PAD = 28;
|
|
808
808
|
function estimateTextLines(words, charsPerLine) {
|
|
809
809
|
if (words.length === 0) return 0;
|
|
@@ -943,7 +943,13 @@ function diagramType(ast) {
|
|
|
943
943
|
return ast.meta.type ?? "architecture";
|
|
944
944
|
}
|
|
945
945
|
function effectiveTitleBand(ast) {
|
|
946
|
-
|
|
946
|
+
if (!ast.meta.title || ast.meta.title.trim().length === 0) return 16;
|
|
947
|
+
const title = ast.meta.title.trim();
|
|
948
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
949
|
+
if (isVertical && title.length > 26) {
|
|
950
|
+
return TITLE_BAND + 52;
|
|
951
|
+
}
|
|
952
|
+
return TITLE_BAND;
|
|
947
953
|
}
|
|
948
954
|
function nodeShape(kind, dtype, props) {
|
|
949
955
|
if (typeof props?.shape === "string") {
|
|
@@ -1251,66 +1257,108 @@ function layoutTree(ast, edges) {
|
|
|
1251
1257
|
}
|
|
1252
1258
|
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
1253
1259
|
const maxDepth = Math.max(...depth.values(), 0);
|
|
1254
|
-
const
|
|
1255
|
-
function leafSpan(id) {
|
|
1256
|
-
const w = nodeDims.get(id)?.width ?? NODE_W;
|
|
1257
|
-
return w + 36;
|
|
1258
|
-
}
|
|
1259
|
-
function measureSubtree(id) {
|
|
1260
|
-
const kids = children.get(id) ?? [];
|
|
1261
|
-
const minSpan = leafSpan(id);
|
|
1262
|
-
if (kids.length === 0) {
|
|
1263
|
-
subtreeSpan.set(id, minSpan);
|
|
1264
|
-
return minSpan;
|
|
1265
|
-
}
|
|
1266
|
-
let total = 0;
|
|
1267
|
-
for (const kid of kids) {
|
|
1268
|
-
total += measureSubtree(kid);
|
|
1269
|
-
}
|
|
1270
|
-
const span = Math.max(minSpan, total);
|
|
1271
|
-
subtreeSpan.set(id, span);
|
|
1272
|
-
return span;
|
|
1273
|
-
}
|
|
1274
|
-
let totalRootsWidth = 0;
|
|
1275
|
-
for (const r of roots) {
|
|
1276
|
-
totalRootsWidth += measureSubtree(r);
|
|
1277
|
-
}
|
|
1260
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
1278
1261
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1279
1262
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1280
|
-
const treeStartX = SAFE + Math.max(0, (contentW - totalRootsWidth) / 2);
|
|
1281
|
-
const levelHeight = Math.max(maxNodeH + 40, Math.min(220, contentH / Math.max(maxDepth + 1, 1)));
|
|
1282
|
-
const totalTreeHeight = maxDepth * levelHeight + maxNodeH;
|
|
1283
|
-
const treeStartY = TITLE_BAND + Math.max(16, (contentH - totalTreeHeight) / 2);
|
|
1284
1263
|
const nodePositions = /* @__PURE__ */ new Map();
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
const
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1264
|
+
if (isVertical) {
|
|
1265
|
+
let layoutSubtree2 = function(nodeId, d) {
|
|
1266
|
+
if (visited.has(nodeId)) return;
|
|
1267
|
+
visited.add(nodeId);
|
|
1268
|
+
const dims = nodeDims.get(nodeId);
|
|
1269
|
+
const x = treeStartX + d * indentStep;
|
|
1270
|
+
nodePositions.set(nodeId, { x, y: curY });
|
|
1271
|
+
const kids = (children.get(nodeId) ?? []).filter((k) => !visited.has(k));
|
|
1272
|
+
curY += dims.height + siblingGap;
|
|
1273
|
+
kids.forEach((kId, idx) => {
|
|
1274
|
+
layoutSubtree2(kId, d + 1);
|
|
1275
|
+
if (idx === kids.length - 1 && d === 0) {
|
|
1276
|
+
curY += branchGap - siblingGap;
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
};
|
|
1280
|
+
var layoutSubtree = layoutSubtree2;
|
|
1281
|
+
const indentStep = 32;
|
|
1282
|
+
const siblingGap = 16;
|
|
1283
|
+
const branchGap = 28;
|
|
1284
|
+
const titleTop = effectiveTitleBand(ast) + 16;
|
|
1285
|
+
const maxTreeW = maxDepth * indentStep + Math.max(...nodeIds.map((id) => nodeDims.get(id).width));
|
|
1286
|
+
const treeStartX = SAFE + Math.max(12, (contentW - maxTreeW) / 2);
|
|
1287
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1288
|
+
let curY = titleTop;
|
|
1289
|
+
for (const r of roots) {
|
|
1290
|
+
layoutSubtree2(r, 0);
|
|
1291
|
+
curY += branchGap;
|
|
1292
|
+
}
|
|
1293
|
+
for (const id of nodeIds) {
|
|
1294
|
+
if (!visited.has(id)) {
|
|
1295
|
+
visited.add(id);
|
|
1296
|
+
const dims = nodeDims.get(id);
|
|
1297
|
+
nodePositions.set(id, {
|
|
1298
|
+
x: treeStartX,
|
|
1299
|
+
y: curY
|
|
1300
|
+
});
|
|
1301
|
+
curY += dims.height + siblingGap;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
} else {
|
|
1305
|
+
let leafSpan2 = function(id) {
|
|
1306
|
+
const w = nodeDims.get(id)?.width ?? NODE_W;
|
|
1307
|
+
return w + 36;
|
|
1308
|
+
}, measureSubtree2 = function(id) {
|
|
1309
|
+
const kids = children.get(id) ?? [];
|
|
1310
|
+
const minSpan = leafSpan2(id);
|
|
1311
|
+
if (kids.length === 0) {
|
|
1312
|
+
subtreeSpan.set(id, minSpan);
|
|
1313
|
+
return minSpan;
|
|
1314
|
+
}
|
|
1315
|
+
let total = 0;
|
|
1316
|
+
for (const kid of kids) {
|
|
1317
|
+
total += measureSubtree2(kid);
|
|
1318
|
+
}
|
|
1319
|
+
const span = Math.max(minSpan, total);
|
|
1320
|
+
subtreeSpan.set(id, span);
|
|
1321
|
+
return span;
|
|
1322
|
+
}, positionSubtree2 = function(id, leftX) {
|
|
1323
|
+
const d = depth.get(id) ?? 0;
|
|
1324
|
+
const y = treeStartY + d * levelHeight;
|
|
1325
|
+
const kids = children.get(id) ?? [];
|
|
1326
|
+
const span = subtreeSpan.get(id) ?? leafSpan2(id);
|
|
1327
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1328
|
+
if (kids.length === 0) {
|
|
1329
|
+
const centerX = leftX + span / 2;
|
|
1330
|
+
nodePositions.set(id, { x: centerX - dims.width / 2, y });
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
let curX = leftX;
|
|
1334
|
+
for (const kid of kids) {
|
|
1335
|
+
const kidSpan = subtreeSpan.get(kid) ?? leafSpan2(kid);
|
|
1336
|
+
positionSubtree2(kid, curX);
|
|
1337
|
+
curX += kidSpan;
|
|
1338
|
+
}
|
|
1339
|
+
const firstChild = nodePositions.get(kids[0]);
|
|
1340
|
+
const lastChild = nodePositions.get(kids[kids.length - 1]);
|
|
1341
|
+
const firstDims = nodeDims.get(kids[0]) ?? { width: NODE_W, height: NODE_H };
|
|
1342
|
+
const lastDims = nodeDims.get(kids[kids.length - 1]) ?? { width: NODE_W, height: NODE_H };
|
|
1343
|
+
const parentCenterX = (firstChild.x + firstDims.width / 2 + lastChild.x + lastDims.width / 2) / 2;
|
|
1344
|
+
nodePositions.set(id, { x: parentCenterX - dims.width / 2, y });
|
|
1345
|
+
};
|
|
1346
|
+
var leafSpan = leafSpan2, measureSubtree = measureSubtree2, positionSubtree = positionSubtree2;
|
|
1347
|
+
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
1348
|
+
let totalRootsWidth = 0;
|
|
1349
|
+
for (const r of roots) {
|
|
1350
|
+
totalRootsWidth += measureSubtree2(r);
|
|
1351
|
+
}
|
|
1352
|
+
const treeStartX = SAFE + Math.max(0, (contentW - totalRootsWidth) / 2);
|
|
1353
|
+
const levelHeight = Math.max(maxNodeH + 40, Math.min(220, contentH / Math.max(maxDepth + 1, 1)));
|
|
1354
|
+
const totalTreeHeight = maxDepth * levelHeight + maxNodeH;
|
|
1355
|
+
const treeStartY = TITLE_BAND + Math.max(16, (contentH - totalTreeHeight) / 2);
|
|
1356
|
+
let currentRootLeft = treeStartX;
|
|
1357
|
+
for (const r of roots) {
|
|
1358
|
+
const span = subtreeSpan.get(r) ?? leafSpan2(r);
|
|
1359
|
+
positionSubtree2(r, currentRootLeft);
|
|
1360
|
+
currentRootLeft += span;
|
|
1361
|
+
}
|
|
1314
1362
|
}
|
|
1315
1363
|
const nodes = [];
|
|
1316
1364
|
for (const id of nodeIds) {
|
|
@@ -1489,45 +1537,85 @@ function layoutMedallion(ast, edges) {
|
|
|
1489
1537
|
}
|
|
1490
1538
|
const activeTiers = [...tiers.entries()].filter(([_, ids]) => ids.length > 0);
|
|
1491
1539
|
const tierCount = activeTiers.length || 1;
|
|
1540
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
1492
1541
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1493
1542
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1494
1543
|
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width));
|
|
1495
|
-
const
|
|
1496
|
-
const colGap = Math.max(maxNodeW + 36, Math.min(360, availableColGap));
|
|
1497
|
-
const totalW = (tierCount - 1) * colGap + maxNodeW;
|
|
1498
|
-
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1544
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height));
|
|
1499
1545
|
const nodes = [];
|
|
1500
|
-
|
|
1501
|
-
const
|
|
1502
|
-
const
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
id
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1546
|
+
if (isVertical) {
|
|
1547
|
+
const availableRowGap = tierCount > 1 ? (contentH - maxNodeH * tierCount) / (tierCount - 1) : 32;
|
|
1548
|
+
const rowGap = Math.max(32, Math.min(80, availableRowGap));
|
|
1549
|
+
const totalH = tierCount * maxNodeH + (tierCount - 1) * rowGap;
|
|
1550
|
+
const startY = TITLE_BAND + Math.max(16, (contentH - totalH) / 2);
|
|
1551
|
+
activeTiers.forEach(([_, ids], rowIdx) => {
|
|
1552
|
+
const rowCount = ids.length;
|
|
1553
|
+
const tierMaxH = Math.max(...ids.map((id) => nodeDims.get(id).height));
|
|
1554
|
+
const rowY = startY + rowIdx * (maxNodeH + rowGap);
|
|
1555
|
+
const colSpacing = 28;
|
|
1556
|
+
const totalRowNodesW = ids.reduce((acc, id) => acc + nodeDims.get(id).width, 0);
|
|
1557
|
+
const totalRowW = totalRowNodesW + Math.max(0, rowCount - 1) * colSpacing;
|
|
1558
|
+
let curX = SAFE + Math.max(0, (contentW - totalRowW) / 2);
|
|
1559
|
+
ids.forEach((id) => {
|
|
1560
|
+
const decl = ast.nodes[id];
|
|
1561
|
+
const dims = nodeDims.get(id);
|
|
1562
|
+
const nodeY = rowY + (tierMaxH - dims.height) / 2;
|
|
1563
|
+
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1564
|
+
nodes.push({
|
|
1565
|
+
id,
|
|
1566
|
+
kind: decl.kind,
|
|
1567
|
+
role: nodeRole(decl.kind),
|
|
1568
|
+
label: decl.label,
|
|
1569
|
+
x: snapGrid(curX),
|
|
1570
|
+
y: snapGrid(nodeY),
|
|
1571
|
+
width: dims.width,
|
|
1572
|
+
height: dims.height,
|
|
1573
|
+
style: resolveNodeStyle(decl, ast),
|
|
1574
|
+
props: decl.props,
|
|
1575
|
+
opacity: 0,
|
|
1576
|
+
shape: "card",
|
|
1577
|
+
focal
|
|
1578
|
+
});
|
|
1579
|
+
curX += dims.width + colSpacing;
|
|
1528
1580
|
});
|
|
1529
1581
|
});
|
|
1530
|
-
}
|
|
1582
|
+
} else {
|
|
1583
|
+
const availableColGap = tierCount > 1 ? (contentW - maxNodeW) / (tierCount - 1) : 0;
|
|
1584
|
+
const colGap = Math.max(maxNodeW + 36, Math.min(360, availableColGap));
|
|
1585
|
+
const totalW = (tierCount - 1) * colGap + maxNodeW;
|
|
1586
|
+
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1587
|
+
activeTiers.forEach(([_, ids], colIdx) => {
|
|
1588
|
+
const colCount = ids.length;
|
|
1589
|
+
const colTierMaxW = Math.max(...ids.map((id) => nodeDims.get(id).width));
|
|
1590
|
+
const colTierMaxH = Math.max(...ids.map((id) => nodeDims.get(id).height));
|
|
1591
|
+
const colX = startX + colIdx * colGap;
|
|
1592
|
+
const rowSpacing = Math.max(colTierMaxH + 32, contentH / (colCount + 1));
|
|
1593
|
+
const colH = (colCount - 1) * rowSpacing + colTierMaxH;
|
|
1594
|
+
const startY = TITLE_BAND + Math.max(0, (contentH - colH) / 2);
|
|
1595
|
+
ids.forEach((id, rowIdx) => {
|
|
1596
|
+
const decl = ast.nodes[id];
|
|
1597
|
+
const dims = nodeDims.get(id);
|
|
1598
|
+
const rowY = startY + rowIdx * rowSpacing + (colTierMaxH - dims.height) / 2;
|
|
1599
|
+
const nodeX = colX + (colTierMaxW - dims.width) / 2;
|
|
1600
|
+
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1601
|
+
nodes.push({
|
|
1602
|
+
id,
|
|
1603
|
+
kind: decl.kind,
|
|
1604
|
+
role: nodeRole(decl.kind),
|
|
1605
|
+
label: decl.label,
|
|
1606
|
+
x: snapGrid(nodeX),
|
|
1607
|
+
y: snapGrid(rowY),
|
|
1608
|
+
width: dims.width,
|
|
1609
|
+
height: dims.height,
|
|
1610
|
+
style: resolveNodeStyle(decl, ast),
|
|
1611
|
+
props: decl.props,
|
|
1612
|
+
opacity: 0,
|
|
1613
|
+
shape: "card",
|
|
1614
|
+
focal
|
|
1615
|
+
});
|
|
1616
|
+
});
|
|
1617
|
+
});
|
|
1618
|
+
}
|
|
1531
1619
|
return nodes;
|
|
1532
1620
|
}
|
|
1533
1621
|
function layoutQuadrant(ast) {
|
|
@@ -1724,6 +1812,40 @@ function layoutTimeline(ast) {
|
|
|
1724
1812
|
for (const id of nodeIds) {
|
|
1725
1813
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1726
1814
|
}
|
|
1815
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
1816
|
+
if (isVertical) {
|
|
1817
|
+
const topBand = effectiveTitleBand(ast);
|
|
1818
|
+
const contentW2 = ast.meta.width - SAFE * 2;
|
|
1819
|
+
const contentH2 = ast.meta.height - SAFE - topBand - SAFE;
|
|
1820
|
+
const centerX = SAFE + contentW2 / 2;
|
|
1821
|
+
const N = nodeIds.length;
|
|
1822
|
+
const spacingY = contentH2 / Math.max(N, 1);
|
|
1823
|
+
const nodes2 = [];
|
|
1824
|
+
nodeIds.forEach((id, idx) => {
|
|
1825
|
+
const decl = ast.nodes[id];
|
|
1826
|
+
const dims = nodeDims.get(id);
|
|
1827
|
+
const isLeft = N === 1 ? false : idx % 2 === 0;
|
|
1828
|
+
const x = N === 1 ? centerX - dims.width / 2 : isLeft ? centerX - dims.width - 24 : centerX + 24;
|
|
1829
|
+
const y = topBand + spacingY * idx + (spacingY - dims.height) / 2;
|
|
1830
|
+
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1831
|
+
nodes2.push({
|
|
1832
|
+
id,
|
|
1833
|
+
kind: decl.kind,
|
|
1834
|
+
role: nodeRole(decl.kind),
|
|
1835
|
+
label: decl.label,
|
|
1836
|
+
x: snapGrid(x),
|
|
1837
|
+
y: snapGrid(y),
|
|
1838
|
+
width: dims.width,
|
|
1839
|
+
height: dims.height,
|
|
1840
|
+
style: resolveNodeStyle(decl, ast),
|
|
1841
|
+
props: decl.props,
|
|
1842
|
+
opacity: 0,
|
|
1843
|
+
shape: "pill",
|
|
1844
|
+
focal
|
|
1845
|
+
});
|
|
1846
|
+
});
|
|
1847
|
+
return nodes2;
|
|
1848
|
+
}
|
|
1727
1849
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1728
1850
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1729
1851
|
const baselineY = TITLE_BAND + contentH / 2;
|
|
@@ -1771,19 +1893,20 @@ function layoutGantt(ast) {
|
|
|
1771
1893
|
const totalH = N * rowH;
|
|
1772
1894
|
const startY = topBand + Math.max(16, (contentH - totalH) / 2);
|
|
1773
1895
|
const nodes = [];
|
|
1896
|
+
const hasExplicitPhases = nodeIds.some((nid) => typeof ast.nodes[nid].props.phase === "number" || typeof ast.nodes[nid].props.span === "number");
|
|
1774
1897
|
nodeIds.forEach((id, idx) => {
|
|
1775
1898
|
const decl = ast.nodes[id];
|
|
1776
1899
|
const dims = nodeDims.get(id);
|
|
1777
1900
|
const phase = typeof decl.props.phase === "number" ? decl.props.phase : 0;
|
|
1778
1901
|
const span = typeof decl.props.span === "number" ? decl.props.span : 1;
|
|
1779
|
-
const totalPhases = Math.max(...nodeIds.map((nid) => {
|
|
1902
|
+
const totalPhases = hasExplicitPhases ? Math.max(...nodeIds.map((nid) => {
|
|
1780
1903
|
const p = ast.nodes[nid].props.phase;
|
|
1781
1904
|
const s = ast.nodes[nid].props.span;
|
|
1782
1905
|
return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
|
|
1783
|
-
}), 1);
|
|
1906
|
+
}), 1) : 1;
|
|
1784
1907
|
const unitW = contentW / totalPhases;
|
|
1785
|
-
const x = SAFE + phase * unitW;
|
|
1786
|
-
const w = Math.max(span * unitW - 8, dims.width);
|
|
1908
|
+
const x = hasExplicitPhases ? SAFE + phase * unitW : SAFE;
|
|
1909
|
+
const w = hasExplicitPhases ? Math.max(span * unitW - 8, dims.width) : contentW;
|
|
1787
1910
|
const barH = dims.height;
|
|
1788
1911
|
const y = startY + idx * rowH + (rowH - barH) / 2;
|
|
1789
1912
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
@@ -1812,45 +1935,67 @@ function layoutVenn(ast) {
|
|
|
1812
1935
|
for (const id of nodeIds) {
|
|
1813
1936
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1814
1937
|
}
|
|
1938
|
+
const titleBand = effectiveTitleBand(ast);
|
|
1939
|
+
const bottomBand = ast.beats.some((b) => b.label && b.label.trim().length > 0) ? 44 : 0;
|
|
1815
1940
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1816
|
-
const contentH = ast.meta.height -
|
|
1941
|
+
const contentH = ast.meta.height - titleBand - bottomBand - SAFE * 2;
|
|
1817
1942
|
const centerX = SAFE + contentW / 2;
|
|
1818
|
-
const centerY =
|
|
1943
|
+
const centerY = titleBand + SAFE + contentH / 2;
|
|
1819
1944
|
const N = nodeIds.length;
|
|
1820
|
-
const maxNodeDim = Math.max(...nodeIds.map((id) => Math.max(nodeDims.get(id).width, nodeDims.get(id).height)));
|
|
1821
|
-
const vennSize = Math.max(VENN_NODE_SIZE, maxNodeDim);
|
|
1822
|
-
const radius = Math.max(vennSize * 0.7, Math.min(contentW, contentH) * 0.24);
|
|
1823
1945
|
const nodes = [];
|
|
1824
|
-
|
|
1825
|
-
const
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1946
|
+
if (N === 2) {
|
|
1947
|
+
const circleSize = snapGrid(Math.min(contentH * 0.85, contentW * 0.85 / 1.6), 16);
|
|
1948
|
+
const radius = circleSize * 0.35;
|
|
1949
|
+
nodeIds.forEach((id, idx) => {
|
|
1950
|
+
const decl = ast.nodes[id];
|
|
1951
|
+
const x = centerX + (idx === 0 ? -radius : radius) - circleSize / 2;
|
|
1952
|
+
const y = centerY - circleSize / 2;
|
|
1953
|
+
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1954
|
+
nodes.push({
|
|
1955
|
+
id,
|
|
1956
|
+
kind: decl.kind,
|
|
1957
|
+
role: nodeRole(decl.kind),
|
|
1958
|
+
label: decl.label,
|
|
1959
|
+
x: snapGrid(x),
|
|
1960
|
+
y: snapGrid(y),
|
|
1961
|
+
width: circleSize,
|
|
1962
|
+
height: circleSize,
|
|
1963
|
+
style: resolveNodeStyle(decl, ast),
|
|
1964
|
+
props: decl.props,
|
|
1965
|
+
opacity: 0,
|
|
1966
|
+
shape: "circle",
|
|
1967
|
+
focal
|
|
1968
|
+
});
|
|
1969
|
+
});
|
|
1970
|
+
} else {
|
|
1971
|
+
const maxCircleByH = contentH * 0.88 / 1.78;
|
|
1972
|
+
const maxCircleByW = contentW * 0.88 / 1.9;
|
|
1973
|
+
const circleSize = snapGrid(Math.min(320, Math.max(180, Math.min(maxCircleByH, maxCircleByW))), 16);
|
|
1974
|
+
const radius = circleSize * 0.52;
|
|
1975
|
+
const clusterCenterY = centerY + (N === 3 ? radius * 0.25 : 0);
|
|
1976
|
+
nodeIds.forEach((id, idx) => {
|
|
1977
|
+
const decl = ast.nodes[id];
|
|
1833
1978
|
const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
|
|
1834
|
-
x = centerX + radius *
|
|
1835
|
-
y =
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1979
|
+
const x = centerX + radius * Math.cos(angle) - circleSize / 2;
|
|
1980
|
+
const y = clusterCenterY + radius * Math.sin(angle) - circleSize / 2;
|
|
1981
|
+
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1982
|
+
nodes.push({
|
|
1983
|
+
id,
|
|
1984
|
+
kind: decl.kind,
|
|
1985
|
+
role: nodeRole(decl.kind),
|
|
1986
|
+
label: decl.label,
|
|
1987
|
+
x: snapGrid(x),
|
|
1988
|
+
y: snapGrid(y),
|
|
1989
|
+
width: circleSize,
|
|
1990
|
+
height: circleSize,
|
|
1991
|
+
style: resolveNodeStyle(decl, ast),
|
|
1992
|
+
props: decl.props,
|
|
1993
|
+
opacity: 0,
|
|
1994
|
+
shape: "circle",
|
|
1995
|
+
focal
|
|
1996
|
+
});
|
|
1852
1997
|
});
|
|
1853
|
-
}
|
|
1998
|
+
}
|
|
1854
1999
|
return nodes;
|
|
1855
2000
|
}
|
|
1856
2001
|
function layoutLayers(ast) {
|
|
@@ -1902,49 +2047,97 @@ function layoutNested(ast) {
|
|
|
1902
2047
|
for (const id of nodeIds) {
|
|
1903
2048
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1904
2049
|
}
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
2050
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
2051
|
+
const topBand = effectiveTitleBand(ast);
|
|
2052
|
+
const topHeadroom = 16;
|
|
2053
|
+
const startY = topBand + topHeadroom;
|
|
1907
2054
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1908
2055
|
const contentH = ast.meta.height - startY - SAFE;
|
|
1909
|
-
const centerX = SAFE + contentW / 2;
|
|
1910
|
-
const centerY = startY + contentH / 2;
|
|
1911
2056
|
const N = nodeIds.length;
|
|
1912
|
-
const padX = Math.min(54, contentW * 0.42 / Math.max(N, 1));
|
|
1913
|
-
const padY = Math.min(48, contentH * 0.42 / Math.max(N, 1));
|
|
1914
2057
|
const nodes = [];
|
|
1915
|
-
|
|
1916
|
-
const
|
|
1917
|
-
const
|
|
1918
|
-
const
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
x
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2058
|
+
if (isVertical) {
|
|
2059
|
+
const stepX = Math.min(40, Math.max(24, contentW * 0.32 / Math.max(N - 1, 1)));
|
|
2060
|
+
const stepY = Math.min(60, Math.max(46, contentH * 0.4 / Math.max(N - 1, 1)));
|
|
2061
|
+
const bottomStepY = Math.min(36, Math.max(22, contentH * 0.22 / Math.max(N - 1, 1)));
|
|
2062
|
+
nodeIds.forEach((id, idx) => {
|
|
2063
|
+
const decl = ast.nodes[id];
|
|
2064
|
+
const dims = nodeDims.get(id);
|
|
2065
|
+
const isCore = idx === N - 1;
|
|
2066
|
+
let x, y, w, h;
|
|
2067
|
+
if (isCore) {
|
|
2068
|
+
const availLeft = SAFE + idx * stepX;
|
|
2069
|
+
const availWidth = Math.max(dims.width, contentW - idx * stepX * 2);
|
|
2070
|
+
const availTop = startY + idx * stepY;
|
|
2071
|
+
const availBottom = startY + contentH - idx * bottomStepY;
|
|
2072
|
+
const availHeight = Math.max(80, availBottom - availTop);
|
|
2073
|
+
w = Math.min(availWidth - 24, Math.max(dims.width, 240));
|
|
2074
|
+
h = Math.max(dims.height, 80);
|
|
2075
|
+
x = availLeft + (availWidth - w) / 2;
|
|
2076
|
+
y = availTop + (availHeight - h) / 2;
|
|
2077
|
+
} else {
|
|
2078
|
+
x = SAFE + idx * stepX;
|
|
2079
|
+
y = startY + idx * stepY;
|
|
2080
|
+
w = contentW - idx * stepX * 2;
|
|
2081
|
+
h = contentH - idx * (stepY + bottomStepY);
|
|
2082
|
+
}
|
|
2083
|
+
const focal = decl.props.focal === true || decl.props.accent === true || isCore;
|
|
2084
|
+
nodes.push({
|
|
2085
|
+
id,
|
|
2086
|
+
kind: decl.kind,
|
|
2087
|
+
role: nodeRole(decl.kind),
|
|
2088
|
+
label: decl.label,
|
|
2089
|
+
x: snapGrid(x),
|
|
2090
|
+
y: snapGrid(y),
|
|
2091
|
+
width: snapGrid(w),
|
|
2092
|
+
height: snapGrid(h),
|
|
2093
|
+
style: resolveNodeStyle(decl, ast),
|
|
2094
|
+
props: decl.props,
|
|
2095
|
+
opacity: 0,
|
|
2096
|
+
shape: isCore ? "card" : "container",
|
|
2097
|
+
focal
|
|
2098
|
+
});
|
|
1946
2099
|
});
|
|
1947
|
-
}
|
|
2100
|
+
} else {
|
|
2101
|
+
const padX = Math.min(54, contentW * 0.38 / Math.max(N, 1));
|
|
2102
|
+
const padY = Math.min(48, contentH * 0.38 / Math.max(N, 1));
|
|
2103
|
+
nodeIds.forEach((id, idx) => {
|
|
2104
|
+
const decl = ast.nodes[id];
|
|
2105
|
+
const dims = nodeDims.get(id);
|
|
2106
|
+
const isCore = idx === N - 1;
|
|
2107
|
+
let x, y, w, h;
|
|
2108
|
+
if (isCore) {
|
|
2109
|
+
const availLeft = SAFE + idx * padX;
|
|
2110
|
+
const availWidth = Math.max(dims.width, contentW - idx * padX * 2);
|
|
2111
|
+
const availTop = startY + idx * padY;
|
|
2112
|
+
const availHeight = Math.max(80, contentH - idx * padY * 2);
|
|
2113
|
+
w = Math.min(availWidth - 24, Math.max(dims.width, 240));
|
|
2114
|
+
h = Math.max(dims.height, 80);
|
|
2115
|
+
x = availLeft + (availWidth - w) / 2;
|
|
2116
|
+
y = availTop + (availHeight - h) / 2;
|
|
2117
|
+
} else {
|
|
2118
|
+
x = SAFE + idx * padX;
|
|
2119
|
+
y = startY + idx * padY;
|
|
2120
|
+
w = contentW - idx * padX * 2;
|
|
2121
|
+
h = contentH - idx * padY * 2;
|
|
2122
|
+
}
|
|
2123
|
+
const focal = decl.props.focal === true || decl.props.accent === true || isCore;
|
|
2124
|
+
nodes.push({
|
|
2125
|
+
id,
|
|
2126
|
+
kind: decl.kind,
|
|
2127
|
+
role: nodeRole(decl.kind),
|
|
2128
|
+
label: decl.label,
|
|
2129
|
+
x: snapGrid(x),
|
|
2130
|
+
y: snapGrid(y),
|
|
2131
|
+
width: snapGrid(w),
|
|
2132
|
+
height: snapGrid(h),
|
|
2133
|
+
style: resolveNodeStyle(decl, ast),
|
|
2134
|
+
props: decl.props,
|
|
2135
|
+
opacity: 0,
|
|
2136
|
+
shape: isCore ? "card" : "container",
|
|
2137
|
+
focal
|
|
2138
|
+
});
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
1948
2141
|
return nodes;
|
|
1949
2142
|
}
|
|
1950
2143
|
function layoutRadar(ast) {
|
|
@@ -2062,24 +2255,45 @@ function computeTreeBuses(ast, nodes, edges) {
|
|
|
2062
2255
|
children.get(edge.from).push(edge.to);
|
|
2063
2256
|
parent.add(edge.to);
|
|
2064
2257
|
}
|
|
2258
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
2065
2259
|
const buses = [];
|
|
2066
2260
|
for (const [parentId, childIds] of children) {
|
|
2067
2261
|
const parentNode = byId.get(parentId);
|
|
2068
2262
|
const childNodes = childIds.map((id) => byId.get(id)).filter(Boolean);
|
|
2069
2263
|
if (!parentNode || childNodes.length === 0) continue;
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2264
|
+
if (isVertical) {
|
|
2265
|
+
const trunkX = snapGrid(parentNode.x + 18);
|
|
2266
|
+
const parentY = snapGrid(parentNode.y + parentNode.height);
|
|
2267
|
+
const childXs = childNodes.map((node) => snapGrid(node.x));
|
|
2268
|
+
const childYs = childNodes.map((node) => snapGrid(node.y + node.height / 2));
|
|
2269
|
+
buses.push({
|
|
2270
|
+
id: `tree_bus_${parentId}`,
|
|
2271
|
+
parentId,
|
|
2272
|
+
childIds,
|
|
2273
|
+
parentX: trunkX,
|
|
2274
|
+
parentY,
|
|
2275
|
+
branchY: parentY,
|
|
2276
|
+
childXs,
|
|
2277
|
+
childY: childYs[0],
|
|
2278
|
+
childYs,
|
|
2279
|
+
vertical: true
|
|
2280
|
+
});
|
|
2281
|
+
} else {
|
|
2282
|
+
const parentX = parentNode.x + parentNode.width / 2;
|
|
2283
|
+
const parentY = parentNode.y + parentNode.height;
|
|
2284
|
+
const childY = Math.min(...childNodes.map((node) => node.y));
|
|
2285
|
+
buses.push({
|
|
2286
|
+
id: `tree_bus_${parentId}`,
|
|
2287
|
+
parentId,
|
|
2288
|
+
childIds,
|
|
2289
|
+
parentX: snapGrid(parentX),
|
|
2290
|
+
parentY: snapGrid(parentY),
|
|
2291
|
+
branchY: snapGrid((parentY + childY) / 2),
|
|
2292
|
+
childXs: childNodes.map((node) => snapGrid(node.x + node.width / 2)),
|
|
2293
|
+
childY: snapGrid(childY),
|
|
2294
|
+
childYs: childNodes.map((node) => snapGrid(node.y))
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2083
2297
|
}
|
|
2084
2298
|
return buses;
|
|
2085
2299
|
}
|
|
@@ -2283,7 +2497,7 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2283
2497
|
const maxNodeW = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.width)) : NODE_W;
|
|
2284
2498
|
const maxNodeH = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.height)) : NODE_H;
|
|
2285
2499
|
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
2286
|
-
const titleBand =
|
|
2500
|
+
const titleBand = effectiveTitleBand(ast);
|
|
2287
2501
|
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
2288
2502
|
const bottomBand = hasBeatCaptions ? 44 : 0;
|
|
2289
2503
|
const titleW = hasTitle ? Math.max(320, ast.meta.title.trim().length * 11 + SAFE * 2) : 0;
|
|
@@ -2303,34 +2517,35 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2303
2517
|
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2304
2518
|
} else if (dtype === "medallion") {
|
|
2305
2519
|
const tierSet = /* @__PURE__ */ new Set();
|
|
2520
|
+
const tierCounts = /* @__PURE__ */ new Map();
|
|
2306
2521
|
for (const id of nodeIds) {
|
|
2307
2522
|
const combined = `${id} ${ast.nodes[id].kind}`.toLowerCase();
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
else if (combined.includes("
|
|
2311
|
-
else if (combined.includes("
|
|
2312
|
-
else
|
|
2523
|
+
let t = 0;
|
|
2524
|
+
if (combined.includes("bronze") || combined.includes("raw") || combined.includes("landing")) t = 1;
|
|
2525
|
+
else if (combined.includes("silver") || combined.includes("clean") || combined.includes("curated") || combined.includes("conformed")) t = 2;
|
|
2526
|
+
else if (combined.includes("gold") || combined.includes("agg") || combined.includes("mart") || combined.includes("analytics")) t = 3;
|
|
2527
|
+
else if (combined.includes("bi") || combined.includes("dash") || combined.includes("model") || combined.includes("app") || combined.includes("consumer") || combined.includes("user") || combined.includes("client")) t = 4;
|
|
2528
|
+
tierSet.add(t);
|
|
2529
|
+
tierCounts.set(t, (tierCounts.get(t) ?? 0) + 1);
|
|
2313
2530
|
}
|
|
2314
2531
|
const tierCount = Math.max(tierSet.size, 4);
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2532
|
+
const maxInTier = Math.max(...tierCounts.values(), 2);
|
|
2533
|
+
if (isVertical) {
|
|
2534
|
+
const nodeColSpacing = 28;
|
|
2535
|
+
const tierSpacing = 36;
|
|
2536
|
+
const tierH = Math.max(76, maxNodeH + 16);
|
|
2537
|
+
const requiredW = Math.max(SAFE * 2 + maxInTier * maxNodeW + (maxInTier - 1) * nodeColSpacing, titleW);
|
|
2538
|
+
const requiredH = titleBand + SAFE * 2 + tierCount * tierH + (tierCount - 1) * tierSpacing + bottomBand;
|
|
2539
|
+
autoW = Math.max(540, Math.min(1600, snapGrid(requiredW, 16)));
|
|
2540
|
+
autoH = Math.max(680, Math.min(2e3, snapGrid(requiredH, 16)));
|
|
2541
|
+
} else {
|
|
2542
|
+
const tierW = Math.max(260, maxNodeW + 48);
|
|
2543
|
+
const requiredW = Math.max(SAFE * 2 + tierCount * tierW, titleW);
|
|
2544
|
+
const requiredH = titleBand + SAFE * 2 + Math.max(440, maxNodeH * 3 + 120) + bottomBand;
|
|
2545
|
+
autoW = Math.max(640, Math.min(2800, requiredW));
|
|
2546
|
+
autoH = Math.max(400, Math.min(1600, requiredH));
|
|
2547
|
+
}
|
|
2320
2548
|
} else if (dtype === "tree") {
|
|
2321
|
-
let measureSubtree2 = function(id) {
|
|
2322
|
-
const kids = children.get(id) ?? [];
|
|
2323
|
-
if (kids.length === 0) {
|
|
2324
|
-
subtreeSpan.set(id, minLeafWidth);
|
|
2325
|
-
return minLeafWidth;
|
|
2326
|
-
}
|
|
2327
|
-
let total = 0;
|
|
2328
|
-
for (const kid of kids) total += measureSubtree2(kid);
|
|
2329
|
-
const span = Math.max(minLeafWidth, total);
|
|
2330
|
-
subtreeSpan.set(id, span);
|
|
2331
|
-
return span;
|
|
2332
|
-
};
|
|
2333
|
-
var measureSubtree = measureSubtree2;
|
|
2334
2549
|
const children = /* @__PURE__ */ new Map();
|
|
2335
2550
|
const hasParent = /* @__PURE__ */ new Set();
|
|
2336
2551
|
for (const id of nodeIds) children.set(id, []);
|
|
@@ -2342,31 +2557,74 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2342
2557
|
}
|
|
2343
2558
|
}
|
|
2344
2559
|
const roots = nodeIds.filter((id) => !hasParent.has(id));
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
const
|
|
2350
|
-
const
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2560
|
+
const activeRoots = roots.length > 0 ? roots : nodeIds;
|
|
2561
|
+
if (isVertical) {
|
|
2562
|
+
const indentStep = 32;
|
|
2563
|
+
const siblingGap = 16;
|
|
2564
|
+
const branchGap = 28;
|
|
2565
|
+
const depth = /* @__PURE__ */ new Map();
|
|
2566
|
+
const q = [...activeRoots];
|
|
2567
|
+
for (const r of activeRoots) depth.set(r, 0);
|
|
2568
|
+
while (q.length > 0) {
|
|
2569
|
+
const cur = q.shift();
|
|
2570
|
+
const d = depth.get(cur) ?? 0;
|
|
2571
|
+
for (const child of children.get(cur) ?? []) {
|
|
2572
|
+
if (!depth.has(child)) {
|
|
2573
|
+
depth.set(child, d + 1);
|
|
2574
|
+
q.push(child);
|
|
2575
|
+
}
|
|
2355
2576
|
}
|
|
2356
2577
|
}
|
|
2578
|
+
const treeDepth = Math.max(...depth.values(), 0);
|
|
2579
|
+
const maxTreeW = treeDepth * indentStep + maxNodeW;
|
|
2580
|
+
const requiredW = Math.max(SAFE * 2 + maxTreeW + 36, titleW);
|
|
2581
|
+
let totalNodesH = 0;
|
|
2582
|
+
for (const id of nodeIds) {
|
|
2583
|
+
totalNodesH += computeNodeDimensions(ast.nodes[id]).height + siblingGap;
|
|
2584
|
+
}
|
|
2585
|
+
totalNodesH += Math.max(0, activeRoots.length) * branchGap;
|
|
2586
|
+
const requiredH = titleBand + SAFE * 2 + totalNodesH + bottomBand;
|
|
2587
|
+
autoW = Math.max(480, Math.min(1600, snapGrid(requiredW, 16)));
|
|
2588
|
+
autoH = Math.max(640, Math.min(2600, snapGrid(requiredH, 16)));
|
|
2589
|
+
} else {
|
|
2590
|
+
let measureSubtree2 = function(id) {
|
|
2591
|
+
const kids = children.get(id) ?? [];
|
|
2592
|
+
if (kids.length === 0) {
|
|
2593
|
+
subtreeSpan.set(id, minLeafWidth);
|
|
2594
|
+
return minLeafWidth;
|
|
2595
|
+
}
|
|
2596
|
+
let total = 0;
|
|
2597
|
+
for (const kid of kids) total += measureSubtree2(kid);
|
|
2598
|
+
const span = Math.max(minLeafWidth, total);
|
|
2599
|
+
subtreeSpan.set(id, span);
|
|
2600
|
+
return span;
|
|
2601
|
+
};
|
|
2602
|
+
var measureSubtree = measureSubtree2;
|
|
2603
|
+
const depth = /* @__PURE__ */ new Map();
|
|
2604
|
+
const queue = [...roots];
|
|
2605
|
+
for (const r of roots) depth.set(r, 0);
|
|
2606
|
+
while (queue.length > 0) {
|
|
2607
|
+
const cur = queue.shift();
|
|
2608
|
+
const d = depth.get(cur) ?? 0;
|
|
2609
|
+
for (const child of children.get(cur) ?? []) {
|
|
2610
|
+
if (!depth.has(child)) {
|
|
2611
|
+
depth.set(child, d + 1);
|
|
2612
|
+
queue.push(child);
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
2617
|
+
const maxDepth = Math.max(...depth.values(), 0);
|
|
2618
|
+
const minLeafWidth = Math.max(NODE_W + 36, maxNodeW + 36);
|
|
2619
|
+
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
2620
|
+
let totalRootsWidth = 0;
|
|
2621
|
+
for (const r of activeRoots) totalRootsWidth += measureSubtree2(r);
|
|
2622
|
+
const levelH = Math.max(130, maxNodeH + 54);
|
|
2623
|
+
const requiredW = Math.max(SAFE * 2 + totalRootsWidth, titleW);
|
|
2624
|
+
const requiredH = titleBand + SAFE * 2 + (maxDepth + 1) * levelH + bottomBand;
|
|
2625
|
+
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2626
|
+
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2357
2627
|
}
|
|
2358
|
-
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
2359
|
-
const maxDepth = Math.max(...depth.values(), 0);
|
|
2360
|
-
const minLeafWidth = Math.max(NODE_W + 36, maxNodeW + 36);
|
|
2361
|
-
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
2362
|
-
let totalRootsWidth = 0;
|
|
2363
|
-
const activeRoots = roots.length > 0 ? roots : nodeIds;
|
|
2364
|
-
for (const r of activeRoots) totalRootsWidth += measureSubtree2(r);
|
|
2365
|
-
const levelH = Math.max(130, maxNodeH + 54);
|
|
2366
|
-
const requiredW = Math.max(SAFE * 2 + totalRootsWidth, titleW);
|
|
2367
|
-
const requiredH = titleBand + SAFE * 2 + (maxDepth + 1) * levelH + bottomBand;
|
|
2368
|
-
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2369
|
-
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2370
2628
|
} else if (dtype === "swimlane") {
|
|
2371
2629
|
const laneCount = Math.max(groupCount, 1);
|
|
2372
2630
|
const maxInLane = Math.max(
|
|
@@ -2392,24 +2650,45 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2392
2650
|
autoW = Math.max(480, Math.min(2e3, Math.max(SAFE * 2 + maxNodeW + 160, titleW)));
|
|
2393
2651
|
const requiredH = titleBand + SAFE * 2 + layerCount * layerH + bottomBand;
|
|
2394
2652
|
autoH = Math.max(320, Math.min(1440, requiredH));
|
|
2395
|
-
} else if (dtype === "timeline"
|
|
2653
|
+
} else if (dtype === "timeline") {
|
|
2396
2654
|
const milestones = Math.max(nodeCount, 1);
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2655
|
+
if (isVertical) {
|
|
2656
|
+
const milestoneH = Math.max(76, maxNodeH);
|
|
2657
|
+
const stepY = Math.max(92, milestoneH + 20);
|
|
2658
|
+
const requiredW = Math.max(SAFE * 2 + maxNodeW * 2 + 64, titleW);
|
|
2659
|
+
const contentH = milestones * stepY;
|
|
2660
|
+
const requiredH = titleBand + SAFE * 2 + contentH + bottomBand;
|
|
2661
|
+
autoW = Math.max(540, Math.min(1600, snapGrid(requiredW, 16)));
|
|
2662
|
+
autoH = Math.max(640, Math.min(2200, snapGrid(requiredH, 16)));
|
|
2663
|
+
} else {
|
|
2664
|
+
const milestoneW = Math.max(220, maxNodeW + 36);
|
|
2665
|
+
const requiredW = Math.max(SAFE * 2 + milestones * milestoneW, titleW);
|
|
2666
|
+
const requiredH = titleBand + SAFE * 2 + Math.max(260, maxNodeH * 2 + 80) + bottomBand;
|
|
2667
|
+
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2668
|
+
autoH = Math.max(320, Math.min(1600, requiredH));
|
|
2669
|
+
}
|
|
2670
|
+
} else if (dtype === "gantt") {
|
|
2671
|
+
const milestones = Math.max(nodeCount, 1);
|
|
2672
|
+
const hasExplicitPhases = nodeIds.some((nid) => typeof ast.nodes[nid].props.phase === "number" || typeof ast.nodes[nid].props.span === "number");
|
|
2673
|
+
if (isVertical) {
|
|
2674
|
+
const rowH = Math.max(68, maxNodeH + 12);
|
|
2675
|
+
const contentH = milestones * rowH + 24;
|
|
2676
|
+
const requiredW = Math.max(SAFE * 2 + maxNodeW + 48, titleW, 540);
|
|
2677
|
+
const requiredH = titleBand + SAFE * 2 + contentH + bottomBand;
|
|
2678
|
+
autoW = Math.max(540, Math.min(1600, snapGrid(requiredW, 16)));
|
|
2679
|
+
autoH = Math.max(640, Math.min(2200, snapGrid(requiredH, 16)));
|
|
2680
|
+
} else {
|
|
2681
|
+
const totalPhases = hasExplicitPhases ? Math.max(...nodeIds.map((nid) => {
|
|
2400
2682
|
const p = ast.nodes[nid].props.phase;
|
|
2401
2683
|
const s = ast.nodes[nid].props.span;
|
|
2402
2684
|
return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
|
|
2403
|
-
}), milestones);
|
|
2685
|
+
}), 1) : Math.min(milestones, 6);
|
|
2686
|
+
const phaseColW = Math.max(120, Math.min(200, maxNodeW * 0.4));
|
|
2687
|
+
const requiredW = Math.max(SAFE * 2 + (hasExplicitPhases ? totalPhases * phaseColW : maxNodeW + 200), titleW);
|
|
2688
|
+
const requiredH = titleBand + SAFE * 2 + milestones * Math.max(76, maxNodeH + 16) + 40 + bottomBand;
|
|
2689
|
+
autoW = Math.max(680, Math.min(2560, snapGrid(requiredW, 16)));
|
|
2690
|
+
autoH = Math.max(480, Math.min(1600, snapGrid(requiredH, 16)));
|
|
2404
2691
|
}
|
|
2405
|
-
const milestoneW = Math.max(220, maxNodeW + 36);
|
|
2406
|
-
const requiredW = Math.max(
|
|
2407
|
-
SAFE * 2 + (dtype === "gantt" ? totalPhases * Math.max(160, maxNodeW + 16) : milestones * milestoneW),
|
|
2408
|
-
titleW
|
|
2409
|
-
);
|
|
2410
|
-
const requiredH = dtype === "gantt" ? titleBand + SAFE * 2 + milestones * Math.max(76, maxNodeH + 16) + 40 + bottomBand : titleBand + SAFE * 2 + Math.max(260, maxNodeH * 2 + 80) + bottomBand;
|
|
2411
|
-
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2412
|
-
autoH = Math.max(320, Math.min(1600, requiredH));
|
|
2413
2692
|
} else if (dtype === "loop" || dtype === "flywheel" || dtype === "radar" || dtype === "venn" || dtype === "constellation") {
|
|
2414
2693
|
const N = Math.max(nodeCount, 3);
|
|
2415
2694
|
const minRadius = Math.max(160, maxNodeW * 0.75);
|
|
@@ -2424,6 +2703,31 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2424
2703
|
const requiredH = titleBand + SAFE * 2 + maxNodeH * 3 + bottomBand;
|
|
2425
2704
|
autoW = Math.min(2400, requiredW);
|
|
2426
2705
|
autoH = Math.min(1600, requiredH);
|
|
2706
|
+
} else if (dtype === "nested") {
|
|
2707
|
+
const N = Math.max(nodeCount, 1);
|
|
2708
|
+
const isVertical2 = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
2709
|
+
if (isVertical2) {
|
|
2710
|
+
const coreW = Math.max(280, maxNodeW + 48);
|
|
2711
|
+
const coreH = Math.max(76, maxNodeH + 16);
|
|
2712
|
+
const stepX = 36;
|
|
2713
|
+
const stepY = 56;
|
|
2714
|
+
const bottomStepY = 28;
|
|
2715
|
+
const requiredW = Math.max(SAFE * 2 + coreW + (N - 1) * stepX * 2, titleW);
|
|
2716
|
+
const contentH = (N - 1) * stepY + coreH + (N - 1) * bottomStepY + 48;
|
|
2717
|
+
const requiredH = titleBand + SAFE * 2 + contentH + bottomBand;
|
|
2718
|
+
autoW = Math.max(540, Math.min(1600, snapGrid(requiredW, 16)));
|
|
2719
|
+
autoH = Math.max(680, Math.min(2e3, snapGrid(requiredH, 16)));
|
|
2720
|
+
} else {
|
|
2721
|
+
const coreW = Math.max(300, maxNodeW + 48);
|
|
2722
|
+
const coreH = Math.max(84, maxNodeH + 24);
|
|
2723
|
+
const stepX = 48;
|
|
2724
|
+
const stepY = 40;
|
|
2725
|
+
const requiredW = Math.max(SAFE * 2 + coreW + (N - 1) * stepX * 2 + 100, titleW);
|
|
2726
|
+
const contentH = coreH + (N - 1) * stepY * 2 + 40;
|
|
2727
|
+
const requiredH = titleBand + SAFE * 2 + contentH + bottomBand;
|
|
2728
|
+
autoW = Math.max(720, Math.min(2400, snapGrid(requiredW, 16)));
|
|
2729
|
+
autoH = Math.max(460, Math.min(1400, snapGrid(requiredH, 16)));
|
|
2730
|
+
}
|
|
2427
2731
|
} else {
|
|
2428
2732
|
const forceVertical = dtype === "flowchart" && isVertical || isVertical;
|
|
2429
2733
|
const ranks = assignRanks(nodeIds, effectiveEdges, forceVertical ? "TB" : "LR");
|
|
@@ -2472,7 +2776,8 @@ function compilePlan(ast, theme) {
|
|
|
2472
2776
|
}
|
|
2473
2777
|
const nodes = layoutNodes(ast, structuralEdges);
|
|
2474
2778
|
const groupBoundaries = computeGroupBoundaries(ast, nodes);
|
|
2475
|
-
const
|
|
2779
|
+
const treeEdges = structuralEdges.some((edge) => edge.structural) ? structuralEdges.filter((edge) => edge.structural) : structuralEdges;
|
|
2780
|
+
const treeBuses = computeTreeBuses(ast, nodes, treeEdges);
|
|
2476
2781
|
const { cues, beats } = scheduleBeats(ast, structuralEdges);
|
|
2477
2782
|
const sequence = buildSequencePlan(ast, cues);
|
|
2478
2783
|
const shown = /* @__PURE__ */ new Set();
|
|
@@ -3696,11 +4001,11 @@ function parse(source, opts = {}) {
|
|
|
3696
4001
|
meta.title = title;
|
|
3697
4002
|
remainder = str.rest;
|
|
3698
4003
|
}
|
|
3699
|
-
const inlineLayout = remainder.match(/\
|
|
4004
|
+
const inlineLayout = remainder.match(/\b(?:layout|direction|rankdir)\s+(LR|RL|TB|BT)\b/i);
|
|
3700
4005
|
if (inlineLayout) {
|
|
3701
4006
|
meta.direction = inlineLayout[1].toUpperCase();
|
|
3702
4007
|
meta.explicitDirection = true;
|
|
3703
|
-
remainder = remainder.replace(/\
|
|
4008
|
+
remainder = remainder.replace(/\b(?:layout|direction|rankdir)\s+(LR|RL|TB|BT)\b/i, " ");
|
|
3704
4009
|
}
|
|
3705
4010
|
const props = parseProps(remainder);
|
|
3706
4011
|
for (const [k, v] of Object.entries(props)) {
|
|
@@ -3721,7 +4026,7 @@ function parse(source, opts = {}) {
|
|
|
3721
4026
|
const val = String(v).toLowerCase();
|
|
3722
4027
|
meta.theme = val;
|
|
3723
4028
|
meta.explicitTheme = val !== "auto";
|
|
3724
|
-
} else if (k === "direction" || k === "layout") {
|
|
4029
|
+
} else if (k === "direction" || k === "layout" || k === "rankdir") {
|
|
3725
4030
|
meta.direction = String(v).toUpperCase();
|
|
3726
4031
|
meta.explicitDirection = true;
|
|
3727
4032
|
} else if (PLAYER_FLAT_KEY_SET.has(k)) {
|
|
@@ -3733,8 +4038,9 @@ function parse(source, opts = {}) {
|
|
|
3733
4038
|
diagnostics.push({ severity: "warning", message: `unknown diagram type '${v}'`, line: lineNo });
|
|
3734
4039
|
} else {
|
|
3735
4040
|
meta.type = t;
|
|
3736
|
-
if (t === "flowchart" && !props.direction && !props.layout && !inlineLayout) {
|
|
4041
|
+
if ((t === "flowchart" || t === "nested") && !props.direction && !props.layout && !props.rankdir && !inlineLayout) {
|
|
3737
4042
|
meta.direction = "TB";
|
|
4043
|
+
meta.explicitDirection = true;
|
|
3738
4044
|
}
|
|
3739
4045
|
}
|
|
3740
4046
|
}
|
|
@@ -5893,7 +6199,7 @@ function diagnoseMarkdyCode(code, options = {}) {
|
|
|
5893
6199
|
});
|
|
5894
6200
|
}
|
|
5895
6201
|
}
|
|
5896
|
-
const layoutMatch = trimmed.match(/\
|
|
6202
|
+
const layoutMatch = trimmed.match(/\b(?:layout|direction|rankdir)\s*=?\s*(\w+)/i);
|
|
5897
6203
|
if (layoutMatch) {
|
|
5898
6204
|
const layoutVal = layoutMatch[1].toUpperCase();
|
|
5899
6205
|
if (!LAYOUT_DIRECTIONS.includes(layoutVal)) {
|
package/package.json
CHANGED