@markdy/core 1.3.3 → 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 +545 -241
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -804,7 +804,6 @@ var SAFE = 48;
|
|
|
804
804
|
var TITLE_BAND = 84;
|
|
805
805
|
var NODE_W = 180;
|
|
806
806
|
var NODE_H = 76;
|
|
807
|
-
var VENN_NODE_SIZE = 220;
|
|
808
807
|
var GROUP_PAD = 28;
|
|
809
808
|
function estimateTextLines(words, charsPerLine) {
|
|
810
809
|
if (words.length === 0) return 0;
|
|
@@ -944,7 +943,13 @@ function diagramType(ast) {
|
|
|
944
943
|
return ast.meta.type ?? "architecture";
|
|
945
944
|
}
|
|
946
945
|
function effectiveTitleBand(ast) {
|
|
947
|
-
|
|
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;
|
|
948
953
|
}
|
|
949
954
|
function nodeShape(kind, dtype, props) {
|
|
950
955
|
if (typeof props?.shape === "string") {
|
|
@@ -1252,66 +1257,108 @@ function layoutTree(ast, edges) {
|
|
|
1252
1257
|
}
|
|
1253
1258
|
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
1254
1259
|
const maxDepth = Math.max(...depth.values(), 0);
|
|
1255
|
-
const
|
|
1256
|
-
function leafSpan(id) {
|
|
1257
|
-
const w = nodeDims.get(id)?.width ?? NODE_W;
|
|
1258
|
-
return w + 36;
|
|
1259
|
-
}
|
|
1260
|
-
function measureSubtree(id) {
|
|
1261
|
-
const kids = children.get(id) ?? [];
|
|
1262
|
-
const minSpan = leafSpan(id);
|
|
1263
|
-
if (kids.length === 0) {
|
|
1264
|
-
subtreeSpan.set(id, minSpan);
|
|
1265
|
-
return minSpan;
|
|
1266
|
-
}
|
|
1267
|
-
let total = 0;
|
|
1268
|
-
for (const kid of kids) {
|
|
1269
|
-
total += measureSubtree(kid);
|
|
1270
|
-
}
|
|
1271
|
-
const span = Math.max(minSpan, total);
|
|
1272
|
-
subtreeSpan.set(id, span);
|
|
1273
|
-
return span;
|
|
1274
|
-
}
|
|
1275
|
-
let totalRootsWidth = 0;
|
|
1276
|
-
for (const r of roots) {
|
|
1277
|
-
totalRootsWidth += measureSubtree(r);
|
|
1278
|
-
}
|
|
1260
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
1279
1261
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1280
1262
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1281
|
-
const treeStartX = SAFE + Math.max(0, (contentW - totalRootsWidth) / 2);
|
|
1282
|
-
const levelHeight = Math.max(maxNodeH + 40, Math.min(220, contentH / Math.max(maxDepth + 1, 1)));
|
|
1283
|
-
const totalTreeHeight = maxDepth * levelHeight + maxNodeH;
|
|
1284
|
-
const treeStartY = TITLE_BAND + Math.max(16, (contentH - totalTreeHeight) / 2);
|
|
1285
1263
|
const nodePositions = /* @__PURE__ */ new Map();
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
+
}
|
|
1315
1362
|
}
|
|
1316
1363
|
const nodes = [];
|
|
1317
1364
|
for (const id of nodeIds) {
|
|
@@ -1490,45 +1537,85 @@ function layoutMedallion(ast, edges) {
|
|
|
1490
1537
|
}
|
|
1491
1538
|
const activeTiers = [...tiers.entries()].filter(([_, ids]) => ids.length > 0);
|
|
1492
1539
|
const tierCount = activeTiers.length || 1;
|
|
1540
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
1493
1541
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1494
1542
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1495
1543
|
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width));
|
|
1496
|
-
const
|
|
1497
|
-
const colGap = Math.max(maxNodeW + 36, Math.min(360, availableColGap));
|
|
1498
|
-
const totalW = (tierCount - 1) * colGap + maxNodeW;
|
|
1499
|
-
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1544
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height));
|
|
1500
1545
|
const nodes = [];
|
|
1501
|
-
|
|
1502
|
-
const
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
id
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
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;
|
|
1529
1580
|
});
|
|
1530
1581
|
});
|
|
1531
|
-
}
|
|
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
|
+
}
|
|
1532
1619
|
return nodes;
|
|
1533
1620
|
}
|
|
1534
1621
|
function layoutQuadrant(ast) {
|
|
@@ -1725,6 +1812,40 @@ function layoutTimeline(ast) {
|
|
|
1725
1812
|
for (const id of nodeIds) {
|
|
1726
1813
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1727
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
|
+
}
|
|
1728
1849
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1729
1850
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1730
1851
|
const baselineY = TITLE_BAND + contentH / 2;
|
|
@@ -1772,19 +1893,20 @@ function layoutGantt(ast) {
|
|
|
1772
1893
|
const totalH = N * rowH;
|
|
1773
1894
|
const startY = topBand + Math.max(16, (contentH - totalH) / 2);
|
|
1774
1895
|
const nodes = [];
|
|
1896
|
+
const hasExplicitPhases = nodeIds.some((nid) => typeof ast.nodes[nid].props.phase === "number" || typeof ast.nodes[nid].props.span === "number");
|
|
1775
1897
|
nodeIds.forEach((id, idx) => {
|
|
1776
1898
|
const decl = ast.nodes[id];
|
|
1777
1899
|
const dims = nodeDims.get(id);
|
|
1778
1900
|
const phase = typeof decl.props.phase === "number" ? decl.props.phase : 0;
|
|
1779
1901
|
const span = typeof decl.props.span === "number" ? decl.props.span : 1;
|
|
1780
|
-
const totalPhases = Math.max(...nodeIds.map((nid) => {
|
|
1902
|
+
const totalPhases = hasExplicitPhases ? Math.max(...nodeIds.map((nid) => {
|
|
1781
1903
|
const p = ast.nodes[nid].props.phase;
|
|
1782
1904
|
const s = ast.nodes[nid].props.span;
|
|
1783
1905
|
return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
|
|
1784
|
-
}), 1);
|
|
1906
|
+
}), 1) : 1;
|
|
1785
1907
|
const unitW = contentW / totalPhases;
|
|
1786
|
-
const x = SAFE + phase * unitW;
|
|
1787
|
-
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;
|
|
1788
1910
|
const barH = dims.height;
|
|
1789
1911
|
const y = startY + idx * rowH + (rowH - barH) / 2;
|
|
1790
1912
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
@@ -1813,45 +1935,67 @@ function layoutVenn(ast) {
|
|
|
1813
1935
|
for (const id of nodeIds) {
|
|
1814
1936
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1815
1937
|
}
|
|
1938
|
+
const titleBand = effectiveTitleBand(ast);
|
|
1939
|
+
const bottomBand = ast.beats.some((b) => b.label && b.label.trim().length > 0) ? 44 : 0;
|
|
1816
1940
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1817
|
-
const contentH = ast.meta.height -
|
|
1941
|
+
const contentH = ast.meta.height - titleBand - bottomBand - SAFE * 2;
|
|
1818
1942
|
const centerX = SAFE + contentW / 2;
|
|
1819
|
-
const centerY =
|
|
1943
|
+
const centerY = titleBand + SAFE + contentH / 2;
|
|
1820
1944
|
const N = nodeIds.length;
|
|
1821
|
-
const maxNodeDim = Math.max(...nodeIds.map((id) => Math.max(nodeDims.get(id).width, nodeDims.get(id).height)));
|
|
1822
|
-
const vennSize = Math.max(VENN_NODE_SIZE, maxNodeDim);
|
|
1823
|
-
const radius = Math.max(vennSize * 0.7, Math.min(contentW, contentH) * 0.24);
|
|
1824
1945
|
const nodes = [];
|
|
1825
|
-
|
|
1826
|
-
const
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
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];
|
|
1834
1978
|
const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
|
|
1835
|
-
x = centerX + radius *
|
|
1836
|
-
y =
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
+
});
|
|
1853
1997
|
});
|
|
1854
|
-
}
|
|
1998
|
+
}
|
|
1855
1999
|
return nodes;
|
|
1856
2000
|
}
|
|
1857
2001
|
function layoutLayers(ast) {
|
|
@@ -1903,49 +2047,97 @@ function layoutNested(ast) {
|
|
|
1903
2047
|
for (const id of nodeIds) {
|
|
1904
2048
|
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1905
2049
|
}
|
|
1906
|
-
const
|
|
1907
|
-
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;
|
|
1908
2054
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1909
2055
|
const contentH = ast.meta.height - startY - SAFE;
|
|
1910
|
-
const centerX = SAFE + contentW / 2;
|
|
1911
|
-
const centerY = startY + contentH / 2;
|
|
1912
2056
|
const N = nodeIds.length;
|
|
1913
|
-
const padX = Math.min(54, contentW * 0.42 / Math.max(N, 1));
|
|
1914
|
-
const padY = Math.min(48, contentH * 0.42 / Math.max(N, 1));
|
|
1915
2057
|
const nodes = [];
|
|
1916
|
-
|
|
1917
|
-
const
|
|
1918
|
-
const
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
x
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
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
|
+
});
|
|
1947
2099
|
});
|
|
1948
|
-
}
|
|
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
|
+
}
|
|
1949
2141
|
return nodes;
|
|
1950
2142
|
}
|
|
1951
2143
|
function layoutRadar(ast) {
|
|
@@ -2063,24 +2255,45 @@ function computeTreeBuses(ast, nodes, edges) {
|
|
|
2063
2255
|
children.get(edge.from).push(edge.to);
|
|
2064
2256
|
parent.add(edge.to);
|
|
2065
2257
|
}
|
|
2258
|
+
const isVertical = ast.meta.direction === "TB" || ast.meta.direction === "BT";
|
|
2066
2259
|
const buses = [];
|
|
2067
2260
|
for (const [parentId, childIds] of children) {
|
|
2068
2261
|
const parentNode = byId.get(parentId);
|
|
2069
2262
|
const childNodes = childIds.map((id) => byId.get(id)).filter(Boolean);
|
|
2070
2263
|
if (!parentNode || childNodes.length === 0) continue;
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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
|
+
}
|
|
2084
2297
|
}
|
|
2085
2298
|
return buses;
|
|
2086
2299
|
}
|
|
@@ -2284,7 +2497,7 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2284
2497
|
const maxNodeW = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.width)) : NODE_W;
|
|
2285
2498
|
const maxNodeH = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.height)) : NODE_H;
|
|
2286
2499
|
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
2287
|
-
const titleBand =
|
|
2500
|
+
const titleBand = effectiveTitleBand(ast);
|
|
2288
2501
|
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
2289
2502
|
const bottomBand = hasBeatCaptions ? 44 : 0;
|
|
2290
2503
|
const titleW = hasTitle ? Math.max(320, ast.meta.title.trim().length * 11 + SAFE * 2) : 0;
|
|
@@ -2304,34 +2517,35 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2304
2517
|
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2305
2518
|
} else if (dtype === "medallion") {
|
|
2306
2519
|
const tierSet = /* @__PURE__ */ new Set();
|
|
2520
|
+
const tierCounts = /* @__PURE__ */ new Map();
|
|
2307
2521
|
for (const id of nodeIds) {
|
|
2308
2522
|
const combined = `${id} ${ast.nodes[id].kind}`.toLowerCase();
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
else if (combined.includes("
|
|
2312
|
-
else if (combined.includes("
|
|
2313
|
-
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);
|
|
2314
2530
|
}
|
|
2315
2531
|
const tierCount = Math.max(tierSet.size, 4);
|
|
2316
|
-
const
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
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
|
+
}
|
|
2321
2548
|
} else if (dtype === "tree") {
|
|
2322
|
-
let measureSubtree2 = function(id) {
|
|
2323
|
-
const kids = children.get(id) ?? [];
|
|
2324
|
-
if (kids.length === 0) {
|
|
2325
|
-
subtreeSpan.set(id, minLeafWidth);
|
|
2326
|
-
return minLeafWidth;
|
|
2327
|
-
}
|
|
2328
|
-
let total = 0;
|
|
2329
|
-
for (const kid of kids) total += measureSubtree2(kid);
|
|
2330
|
-
const span = Math.max(minLeafWidth, total);
|
|
2331
|
-
subtreeSpan.set(id, span);
|
|
2332
|
-
return span;
|
|
2333
|
-
};
|
|
2334
|
-
var measureSubtree = measureSubtree2;
|
|
2335
2549
|
const children = /* @__PURE__ */ new Map();
|
|
2336
2550
|
const hasParent = /* @__PURE__ */ new Set();
|
|
2337
2551
|
for (const id of nodeIds) children.set(id, []);
|
|
@@ -2343,31 +2557,74 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2343
2557
|
}
|
|
2344
2558
|
}
|
|
2345
2559
|
const roots = nodeIds.filter((id) => !hasParent.has(id));
|
|
2346
|
-
const
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
const
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
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
|
+
}
|
|
2356
2576
|
}
|
|
2357
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));
|
|
2358
2627
|
}
|
|
2359
|
-
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
2360
|
-
const maxDepth = Math.max(...depth.values(), 0);
|
|
2361
|
-
const minLeafWidth = Math.max(NODE_W + 36, maxNodeW + 36);
|
|
2362
|
-
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
2363
|
-
let totalRootsWidth = 0;
|
|
2364
|
-
const activeRoots = roots.length > 0 ? roots : nodeIds;
|
|
2365
|
-
for (const r of activeRoots) totalRootsWidth += measureSubtree2(r);
|
|
2366
|
-
const levelH = Math.max(130, maxNodeH + 54);
|
|
2367
|
-
const requiredW = Math.max(SAFE * 2 + totalRootsWidth, titleW);
|
|
2368
|
-
const requiredH = titleBand + SAFE * 2 + (maxDepth + 1) * levelH + bottomBand;
|
|
2369
|
-
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2370
|
-
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2371
2628
|
} else if (dtype === "swimlane") {
|
|
2372
2629
|
const laneCount = Math.max(groupCount, 1);
|
|
2373
2630
|
const maxInLane = Math.max(
|
|
@@ -2393,24 +2650,45 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2393
2650
|
autoW = Math.max(480, Math.min(2e3, Math.max(SAFE * 2 + maxNodeW + 160, titleW)));
|
|
2394
2651
|
const requiredH = titleBand + SAFE * 2 + layerCount * layerH + bottomBand;
|
|
2395
2652
|
autoH = Math.max(320, Math.min(1440, requiredH));
|
|
2396
|
-
} else if (dtype === "timeline"
|
|
2653
|
+
} else if (dtype === "timeline") {
|
|
2397
2654
|
const milestones = Math.max(nodeCount, 1);
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
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) => {
|
|
2401
2682
|
const p = ast.nodes[nid].props.phase;
|
|
2402
2683
|
const s = ast.nodes[nid].props.span;
|
|
2403
2684
|
return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
|
|
2404
|
-
}), 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)));
|
|
2405
2691
|
}
|
|
2406
|
-
const milestoneW = Math.max(220, maxNodeW + 36);
|
|
2407
|
-
const requiredW = Math.max(
|
|
2408
|
-
SAFE * 2 + (dtype === "gantt" ? totalPhases * Math.max(160, maxNodeW + 16) : milestones * milestoneW),
|
|
2409
|
-
titleW
|
|
2410
|
-
);
|
|
2411
|
-
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;
|
|
2412
|
-
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2413
|
-
autoH = Math.max(320, Math.min(1600, requiredH));
|
|
2414
2692
|
} else if (dtype === "loop" || dtype === "flywheel" || dtype === "radar" || dtype === "venn" || dtype === "constellation") {
|
|
2415
2693
|
const N = Math.max(nodeCount, 3);
|
|
2416
2694
|
const minRadius = Math.max(160, maxNodeW * 0.75);
|
|
@@ -2425,6 +2703,31 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2425
2703
|
const requiredH = titleBand + SAFE * 2 + maxNodeH * 3 + bottomBand;
|
|
2426
2704
|
autoW = Math.min(2400, requiredW);
|
|
2427
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
|
+
}
|
|
2428
2731
|
} else {
|
|
2429
2732
|
const forceVertical = dtype === "flowchart" && isVertical || isVertical;
|
|
2430
2733
|
const ranks = assignRanks(nodeIds, effectiveEdges, forceVertical ? "TB" : "LR");
|
|
@@ -2473,7 +2776,8 @@ function compilePlan(ast, theme) {
|
|
|
2473
2776
|
}
|
|
2474
2777
|
const nodes = layoutNodes(ast, structuralEdges);
|
|
2475
2778
|
const groupBoundaries = computeGroupBoundaries(ast, nodes);
|
|
2476
|
-
const
|
|
2779
|
+
const treeEdges = structuralEdges.some((edge) => edge.structural) ? structuralEdges.filter((edge) => edge.structural) : structuralEdges;
|
|
2780
|
+
const treeBuses = computeTreeBuses(ast, nodes, treeEdges);
|
|
2477
2781
|
const { cues, beats } = scheduleBeats(ast, structuralEdges);
|
|
2478
2782
|
const sequence = buildSequencePlan(ast, cues);
|
|
2479
2783
|
const shown = /* @__PURE__ */ new Set();
|
|
@@ -3734,7 +4038,7 @@ function parse(source, opts = {}) {
|
|
|
3734
4038
|
diagnostics.push({ severity: "warning", message: `unknown diagram type '${v}'`, line: lineNo });
|
|
3735
4039
|
} else {
|
|
3736
4040
|
meta.type = t;
|
|
3737
|
-
if (t === "flowchart" && !props.direction && !props.layout && !props.rankdir && !inlineLayout) {
|
|
4041
|
+
if ((t === "flowchart" || t === "nested") && !props.direction && !props.layout && !props.rankdir && !inlineLayout) {
|
|
3738
4042
|
meta.direction = "TB";
|
|
3739
4043
|
meta.explicitDirection = true;
|
|
3740
4044
|
}
|
package/package.json
CHANGED