@markdy/core 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +8 -1
- package/dist/index.js +458 -197
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ type SceneMeta = {
|
|
|
85
85
|
explicitHeight?: boolean;
|
|
86
86
|
/** Whether theme was explicitly specified by the author in the script. */
|
|
87
87
|
explicitTheme?: boolean;
|
|
88
|
+
/** Whether layout direction was explicitly specified by the author in the script. */
|
|
89
|
+
explicitDirection?: boolean;
|
|
88
90
|
/** Opt-in diagram mode; defaults to architecture. */
|
|
89
91
|
type?: DiagramType;
|
|
90
92
|
/** Playback, controls, interaction, and chrome behavior. Source of truth. */
|
|
@@ -376,6 +378,10 @@ type RenderPlan = {
|
|
|
376
378
|
duration: number;
|
|
377
379
|
};
|
|
378
380
|
|
|
381
|
+
declare function computeNodeDimensions(decl: NodeDecl, baseW?: number, baseH?: number): {
|
|
382
|
+
width: number;
|
|
383
|
+
height: number;
|
|
384
|
+
};
|
|
379
385
|
/**
|
|
380
386
|
* Computes optimal, content-adaptive canvas width and height for a diagram AST
|
|
381
387
|
* when dimensions are omitted or partially specified in the script.
|
|
@@ -434,6 +440,7 @@ type PlayerOverrides = {
|
|
|
434
440
|
playback?: boolean;
|
|
435
441
|
});
|
|
436
442
|
interactiveViewport?: boolean;
|
|
443
|
+
clickToPlay?: boolean;
|
|
437
444
|
progress?: PlayerProgress;
|
|
438
445
|
progressColor?: string;
|
|
439
446
|
};
|
|
@@ -1131,4 +1138,4 @@ interface AutoHealResult {
|
|
|
1131
1138
|
*/
|
|
1132
1139
|
declare function autoHealArchitectureDrift(ast: DiagramAST, report: ArchitectureDriftReport, existingFiles?: string[]): AutoHealResult;
|
|
1133
1140
|
|
|
1134
|
-
export { ARCHITECTURE_RECIPES, ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitectureDriftReport, type ArchitecturePreset, type ArchitectureRecipe, type ArchitectureRecommendation, type ArchitectureRule, type ArchitectureViolation, type AutoHealResult, type AutoRepairResult, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, type BrokenAnchorDrift, type C4Level, type C4LevelViewExport, type C4ModelReport, type C4NodeMeta, CUE_ALIASES, type CardinalPort, type CodeProvenanceAnchor, type CodeProvenanceDiagnostic, type CodeProvenanceVerificationReport, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagnosticIssue, type DiagramAST, type DiagramContext, type DiagramDiffResult, type DiagramQualityMetrics, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type ExtractedBeat, type ExtractedGroup, type ExtractedNode, type FlowSegment, type GhostTextSuggestion, type GroupBoundary, type GroupDecl, type GroupDiff, type IntelliCodeItem, type IntelliCodeItemKind, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OrphanCodeServiceDrift, type OutputPreset, PLAYER_FLAT_KEYS, PLAYER_GROUPS, POPULAR_TECHS, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PatternRecommendationResult, type PlayerChromeConfig, type PlayerConfig, type PlayerControlsConfig, type PlayerInteractionConfig, type PlayerOverrides, type PlayerPlaybackConfig, type PlayerProgress, type PlayerScope, type Point, type PortLane, type PositionedNode, type QualityCheckItem, type QualityGateOptions, type QualityGateReport, type QualityProfile, type RenderPlan, type RepairPromptBundle, type ResolvedPlayer, type RouteOptions, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, type SyntaxDiagnosticReport, type SynthesizedRecipeResult, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type TechPreset, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VECTOR_SYMBOLS, VISUAL_PRIMITIVE_TYPES, type VectorSymbol, allocatePortLanes, analyzeAndBuildRepairPrompt, analyzeC4Model, applyPlayerSetting, autoHealArchitectureDrift, buildSmoothSvgPath, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, computeAdaptiveDimensions, damerauLevenshteinDistance, decompressMarkdyFromUrlHash, detectArchitectureDrift, diagnoseMarkdyCode, diffDiagramASTs, exportC4LevelViews, extractDiagramCodeAnchors, extractDiagramContext, filterC4Hierarchy, findClosestMatch, formatScene, generateC4Storyboard, generateThemeFromBrand, getArchitectureRecipe, getArchitectureSuggestions, getBoxPortPosition, getIntelliCodeCompletions, humanizeId, inferNodeC4Level, listArchitectureRecipes, listAvailableSymbols, listOutputPresets, nodeRole, parse, parseAndCompile, parseCodeAnchor, predictNextLineSuggestion, recommendArchitecturePattern, renderSymbolSvg, repairMarkdyCode, resolveArchitectureConfig, resolveOutputPreset, resolvePlayer, resolveTheme, resolveVectorSymbol, routeOrthogonalEdge, selectOptimalPorts, synthesizeCustomRecipe, validateArchitecture, validateC4Containment, verifyCodeAnchorsWithReader, verifyDiagramQuality };
|
|
1141
|
+
export { ARCHITECTURE_RECIPES, ARCH_RULE_PRESETS, type AnnotationDecl, type ArchRuleType, type ArchitectureDriftReport, type ArchitecturePreset, type ArchitectureRecipe, type ArchitectureRecommendation, type ArchitectureRule, type ArchitectureViolation, type AutoHealResult, type AutoRepairResult, BEAT_CUE_KEYWORDS, type BeatDecl, type BeatRange, type Box, type BrokenAnchorDrift, type C4Level, type C4LevelViewExport, type C4ModelReport, type C4NodeMeta, CUE_ALIASES, type CardinalPort, type CodeProvenanceAnchor, type CodeProvenanceDiagnostic, type CodeProvenanceVerificationReport, type Cue, DIAGRAM_TYPES, type Diagnostic, type DiagnosticIssue, type DiagramAST, type DiagramContext, type DiagramDiffResult, type DiagramQualityMetrics, type DiagramType, type DiffChangeType, EDGE_OPERATORS, type EdgeDecl, type EdgeDiff, type EdgeKind, type EdgeSelector, type ExtractedBeat, type ExtractedGroup, type ExtractedNode, type FlowSegment, type GhostTextSuggestion, type GroupBoundary, type GroupDecl, type GroupDiff, type IntelliCodeItem, type IntelliCodeItemKind, type LayoutDirection, type MarkdyConfig, NODE_ALIASES, NODE_KINDS, type NodeDecl, type NodeDiff, type NodeSelector, type NodeShape, OUTPUT_PRESETS, type OrphanCodeServiceDrift, type OutputPreset, PLAYER_FLAT_KEYS, PLAYER_GROUPS, POPULAR_TECHS, ParseError, type ParseOptions, type ParseResult, type PatternDecl, type PatternRecommendationResult, type PlayerChromeConfig, type PlayerConfig, type PlayerControlsConfig, type PlayerInteractionConfig, type PlayerOverrides, type PlayerPlaybackConfig, type PlayerProgress, type PlayerScope, type Point, type PortLane, type PositionedNode, type QualityCheckItem, type QualityGateOptions, type QualityGateReport, type QualityProfile, type RenderPlan, type RepairPromptBundle, type ResolvedPlayer, type RouteOptions, type RoutedEdge, type RoutedPath, type RuleSeverity, SCENE_KEYS, type SceneMeta, type SemanticProfile, type SequenceActivation, type SequenceMessage, type StyleDecl, type SyntaxDiagnosticReport, type SynthesizedRecipeResult, TECHNICAL_NODE_KINDS, TECHNICAL_NODE_TYPES, THEMES, type TechPreset, type ThemeGeneratorOptions, type ThemeTokens, type TimedCue, type TreeBus, VECTOR_SYMBOLS, VISUAL_PRIMITIVE_TYPES, type VectorSymbol, allocatePortLanes, analyzeAndBuildRepairPrompt, analyzeC4Model, applyPlayerSetting, autoHealArchitectureDrift, buildSmoothSvgPath, canonicalNodeKind, classifyTechnology, compile, compilePlan, compressMarkdyToUrlHash, computeAdaptiveDimensions, computeNodeDimensions, damerauLevenshteinDistance, decompressMarkdyFromUrlHash, detectArchitectureDrift, diagnoseMarkdyCode, diffDiagramASTs, exportC4LevelViews, extractDiagramCodeAnchors, extractDiagramContext, filterC4Hierarchy, findClosestMatch, formatScene, generateC4Storyboard, generateThemeFromBrand, getArchitectureRecipe, getArchitectureSuggestions, getBoxPortPosition, getIntelliCodeCompletions, humanizeId, inferNodeC4Level, listArchitectureRecipes, listAvailableSymbols, listOutputPresets, nodeRole, parse, parseAndCompile, parseCodeAnchor, predictNextLineSuggestion, recommendArchitecturePattern, renderSymbolSvg, repairMarkdyCode, resolveArchitectureConfig, resolveOutputPreset, resolvePlayer, resolveTheme, resolveVectorSymbol, routeOrthogonalEdge, selectOptimalPorts, synthesizeCustomRecipe, validateArchitecture, validateC4Containment, verifyCodeAnchorsWithReader, verifyDiagramQuality };
|
package/dist/index.js
CHANGED
|
@@ -673,7 +673,8 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
673
673
|
const controls = {
|
|
674
674
|
...requestedControls,
|
|
675
675
|
speed: requestedControls.speed && speeds.length > 1,
|
|
676
|
-
resetView: requestedControls.resetView && interactionEnabled
|
|
676
|
+
resetView: requestedControls.resetView && interactionEnabled,
|
|
677
|
+
fit: requestedControls.fit
|
|
677
678
|
};
|
|
678
679
|
const controlsEnabled = Object.values(controls).some(Boolean);
|
|
679
680
|
const rate = overrides.playbackRate ?? playback.rate ?? 1;
|
|
@@ -691,8 +692,8 @@ function resolvePlayer(config = {}, overrides = {}) {
|
|
|
691
692
|
interaction: {
|
|
692
693
|
...gestures,
|
|
693
694
|
enabled: interactionEnabled,
|
|
694
|
-
clickToPlay: interaction.clickToPlay ?? true,
|
|
695
|
-
keyboard: interaction.keyboard ?? false
|
|
695
|
+
clickToPlay: overrides.clickToPlay ?? (interactionOn && (interaction.clickToPlay ?? true)),
|
|
696
|
+
keyboard: interactionOn && (interaction.keyboard ?? false)
|
|
696
697
|
},
|
|
697
698
|
chrome: {
|
|
698
699
|
badge: overrides.copyright ?? chrome.badge ?? true,
|
|
@@ -804,35 +805,123 @@ var NODE_W = 180;
|
|
|
804
805
|
var NODE_H = 76;
|
|
805
806
|
var VENN_NODE_SIZE = 220;
|
|
806
807
|
var GROUP_PAD = 28;
|
|
807
|
-
function
|
|
808
|
-
if (
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
808
|
+
function estimateTextLines(words, charsPerLine) {
|
|
809
|
+
if (words.length === 0) return 0;
|
|
810
|
+
let lines = 1;
|
|
811
|
+
let curLen = 0;
|
|
812
|
+
for (const w of words) {
|
|
813
|
+
if (curLen === 0) {
|
|
814
|
+
curLen = w.length;
|
|
815
|
+
} else if (curLen + 1 + w.length <= charsPerLine) {
|
|
816
|
+
curLen += 1 + w.length;
|
|
817
|
+
} else {
|
|
818
|
+
lines++;
|
|
819
|
+
curLen = w.length;
|
|
820
|
+
}
|
|
813
821
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
822
|
+
return lines;
|
|
823
|
+
}
|
|
824
|
+
function measureLabelLines(rawLabel, words, charsPerLine) {
|
|
825
|
+
if (words.length === 0) return 0;
|
|
826
|
+
if (!rawLabel.includes("\n")) {
|
|
827
|
+
return Math.max(1, estimateTextLines(words, charsPerLine));
|
|
828
|
+
}
|
|
829
|
+
const explicitLines = rawLabel.split("\n");
|
|
830
|
+
let total = 0;
|
|
831
|
+
for (const line of explicitLines) {
|
|
832
|
+
const lineWords = line.trim().split(/\s+/).filter(Boolean);
|
|
833
|
+
total += Math.max(1, estimateTextLines(lineWords, charsPerLine));
|
|
834
|
+
}
|
|
835
|
+
return Math.max(explicitLines.length, total);
|
|
836
|
+
}
|
|
837
|
+
function computeNodeDimensions(decl, baseW = 180, baseH = 76) {
|
|
819
838
|
if (decl.kind === "dot") return { width: 64, height: 64 };
|
|
820
839
|
if (decl.kind === "matrix") return { width: 220, height: 96 };
|
|
821
|
-
const
|
|
822
|
-
const
|
|
840
|
+
const rawLabel = (decl.label || "").trim();
|
|
841
|
+
const labelLen = rawLabel.length;
|
|
842
|
+
const words = rawLabel.split(/\s+/).filter(Boolean);
|
|
823
843
|
const longestWord = Math.max(...words.map((w) => w.length), 0);
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
844
|
+
const tech = decl.props?.tech ?? decl.props?.sub;
|
|
845
|
+
const techStr = tech ? String(tech).trim() : "";
|
|
846
|
+
const techLen = techStr.length;
|
|
847
|
+
const val = decl.props?.value ?? decl.props?.metric;
|
|
848
|
+
const valStr = val ? String(val).trim() : "";
|
|
849
|
+
const valLen = valStr.length;
|
|
850
|
+
const leftSpace = 56;
|
|
851
|
+
const valueW = valLen > 0 ? Math.max(50, valLen * 9.5 + 16) : 0;
|
|
852
|
+
const rightSpace = valueW > 0 ? valueW + 14 : 18;
|
|
853
|
+
const isDiamond = decl.props?.shape === "diamond" || decl.kind === "decision" || decl.kind === "condition";
|
|
854
|
+
const isCircle = decl.props?.shape === "circle";
|
|
855
|
+
const isPill = decl.props?.shape === "pill";
|
|
856
|
+
const effectiveBaseW = isDiamond ? Math.max(baseW, 208) : isCircle ? Math.max(baseW, 140) : baseW;
|
|
857
|
+
const effectiveBaseH = isDiamond ? Math.max(baseH, 88) : isCircle ? Math.max(baseH, 140) : baseH;
|
|
858
|
+
const neededTechW = techLen > 0 ? Math.ceil(techLen * 6.6 + 16) : 0;
|
|
859
|
+
const longestWordW = Math.ceil(longestWord * 8.4 + 4);
|
|
860
|
+
const minColW = Math.max(96, longestWordW, neededTechW);
|
|
861
|
+
if (isCircle) {
|
|
862
|
+
const targetTextW2 = Math.max(minColW, Math.min(220, Math.ceil(labelLen * 7.5)));
|
|
863
|
+
const charsPerLine2 = Math.max(longestWord, Math.floor(targetTextW2 / 8));
|
|
864
|
+
const numLines2 = Math.max(words.length > 0 ? 1 : 0, measureLabelLines(rawLabel, words, charsPerLine2));
|
|
865
|
+
const labelH2 = numLines2 * 16;
|
|
866
|
+
const techH2 = techLen > 0 ? 20 : 0;
|
|
867
|
+
const circleContentH = 28 + labelH2 + techH2;
|
|
868
|
+
const circleContentW = Math.max(targetTextW2, 60);
|
|
869
|
+
const radiusNeeded = Math.sqrt((circleContentW / 2) ** 2 + (circleContentH / 2) ** 2) + 20;
|
|
870
|
+
const diameter = Math.max(effectiveBaseW, effectiveBaseH, Math.ceil(radiusNeeded * 2 / 8) * 8);
|
|
871
|
+
return {
|
|
872
|
+
width: typeof decl.props?.width === "number" ? Math.max(decl.props.width, diameter) : diameter,
|
|
873
|
+
height: typeof decl.props?.height === "number" ? Math.max(decl.props.height, diameter) : diameter
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
if (isDiamond) {
|
|
877
|
+
let targetTextW2;
|
|
878
|
+
if (labelLen <= 12) {
|
|
879
|
+
targetTextW2 = Math.max(minColW, Math.ceil(labelLen * 8));
|
|
880
|
+
} else if (labelLen <= 28) {
|
|
881
|
+
targetTextW2 = Math.max(minColW, Math.min(180, Math.ceil(labelLen / 2 * 8.2 + 8)));
|
|
882
|
+
} else {
|
|
883
|
+
targetTextW2 = Math.max(minColW, Math.min(260, Math.ceil(labelLen / 3 * 8.2 + 12)));
|
|
884
|
+
}
|
|
885
|
+
const charsPerLine2 = Math.max(longestWord, Math.floor(targetTextW2 / 8));
|
|
886
|
+
const numLines2 = Math.max(words.length > 0 ? 1 : 0, measureLabelLines(rawLabel, words, charsPerLine2));
|
|
887
|
+
const labelH2 = numLines2 * 16;
|
|
888
|
+
const techH2 = techLen > 0 ? 20 : 0;
|
|
889
|
+
const diamondContentH = 24 + labelH2 + techH2;
|
|
890
|
+
const neededH2 = Math.max(effectiveBaseH, Math.ceil(diamondContentH / 0.58 / 8) * 8);
|
|
891
|
+
const neededW = Math.max(effectiveBaseW, Math.ceil((targetTextW2 + 32) / 0.52 / 8) * 8);
|
|
892
|
+
return {
|
|
893
|
+
width: typeof decl.props?.width === "number" ? Math.max(decl.props.width, neededW) : neededW,
|
|
894
|
+
height: typeof decl.props?.height === "number" ? Math.max(decl.props.height, neededH2) : neededH2
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
let targetTextW;
|
|
898
|
+
if (labelLen <= 14) {
|
|
899
|
+
targetTextW = Math.max(minColW, Math.ceil(labelLen * 8.4));
|
|
900
|
+
} else if (labelLen <= 30) {
|
|
901
|
+
targetTextW = Math.max(minColW, Math.min(220, Math.ceil(labelLen * 8.4)));
|
|
902
|
+
} else if (labelLen <= 54) {
|
|
903
|
+
targetTextW = Math.max(minColW, Math.min(260, Math.max(180, Math.ceil(labelLen / 2 * 8.4 + 8))));
|
|
904
|
+
} else if (labelLen <= 84) {
|
|
905
|
+
targetTextW = Math.max(minColW, Math.min(300, Math.max(220, Math.ceil(labelLen / 3 * 8.4 + 12))));
|
|
906
|
+
} else {
|
|
907
|
+
targetTextW = Math.max(minColW, Math.min(360, Math.max(250, Math.ceil(labelLen / 4 * 8.4 + 16))));
|
|
908
|
+
}
|
|
909
|
+
const charsPerLine = Math.max(longestWord, Math.floor((targetTextW - 4) / 8.2));
|
|
910
|
+
const numLines = Math.max(words.length > 0 ? 1 : 0, measureLabelLines(rawLabel, words, charsPerLine));
|
|
911
|
+
const labelLineH = 18;
|
|
912
|
+
const labelH = numLines * labelLineH;
|
|
913
|
+
const techH = techLen > 0 ? 22 : 0;
|
|
914
|
+
const padV = 28;
|
|
915
|
+
const neededContentH = Math.max(28, labelH + techH);
|
|
916
|
+
const neededH = neededContentH + padV;
|
|
917
|
+
const height = Math.max(effectiveBaseH, Math.ceil(neededH / 8) * 8);
|
|
918
|
+
const pillPad = isPill ? 24 : 0;
|
|
919
|
+
const calculatedW = leftSpace + targetTextW + rightSpace + pillPad;
|
|
920
|
+
const width = Math.max(effectiveBaseW, Math.ceil(calculatedW / 8) * 8);
|
|
921
|
+
return {
|
|
922
|
+
width: typeof decl.props?.width === "number" ? Math.max(decl.props.width, width) : width,
|
|
923
|
+
height: typeof decl.props?.height === "number" ? Math.max(decl.props.height, height) : height
|
|
924
|
+
};
|
|
836
925
|
}
|
|
837
926
|
function resolveNodeStyle(decl, ast) {
|
|
838
927
|
if (!decl.style) return void 0;
|
|
@@ -853,6 +942,9 @@ var DEFAULTS = {
|
|
|
853
942
|
function diagramType(ast) {
|
|
854
943
|
return ast.meta.type ?? "architecture";
|
|
855
944
|
}
|
|
945
|
+
function effectiveTitleBand(ast) {
|
|
946
|
+
return ast.meta.title && ast.meta.title.trim().length > 0 ? TITLE_BAND : 16;
|
|
947
|
+
}
|
|
856
948
|
function nodeShape(kind, dtype, props) {
|
|
857
949
|
if (typeof props?.shape === "string") {
|
|
858
950
|
const s = props.shape.toLowerCase();
|
|
@@ -1127,6 +1219,11 @@ function layoutRanked(ast, edges, opts) {
|
|
|
1127
1219
|
function layoutTree(ast, edges) {
|
|
1128
1220
|
const nodeIds = Object.keys(ast.nodes);
|
|
1129
1221
|
if (nodeIds.length === 0) return [];
|
|
1222
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1223
|
+
for (const id of nodeIds) {
|
|
1224
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1225
|
+
}
|
|
1226
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height), NODE_H);
|
|
1130
1227
|
const children = /* @__PURE__ */ new Map();
|
|
1131
1228
|
const parent = /* @__PURE__ */ new Map();
|
|
1132
1229
|
for (const id of nodeIds) children.set(id, []);
|
|
@@ -1155,18 +1252,22 @@ function layoutTree(ast, edges) {
|
|
|
1155
1252
|
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
1156
1253
|
const maxDepth = Math.max(...depth.values(), 0);
|
|
1157
1254
|
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
1158
|
-
|
|
1255
|
+
function leafSpan(id) {
|
|
1256
|
+
const w = nodeDims.get(id)?.width ?? NODE_W;
|
|
1257
|
+
return w + 36;
|
|
1258
|
+
}
|
|
1159
1259
|
function measureSubtree(id) {
|
|
1160
1260
|
const kids = children.get(id) ?? [];
|
|
1261
|
+
const minSpan = leafSpan(id);
|
|
1161
1262
|
if (kids.length === 0) {
|
|
1162
|
-
subtreeSpan.set(id,
|
|
1163
|
-
return
|
|
1263
|
+
subtreeSpan.set(id, minSpan);
|
|
1264
|
+
return minSpan;
|
|
1164
1265
|
}
|
|
1165
1266
|
let total = 0;
|
|
1166
1267
|
for (const kid of kids) {
|
|
1167
1268
|
total += measureSubtree(kid);
|
|
1168
1269
|
}
|
|
1169
|
-
const span = Math.max(
|
|
1270
|
+
const span = Math.max(minSpan, total);
|
|
1170
1271
|
subtreeSpan.set(id, span);
|
|
1171
1272
|
return span;
|
|
1172
1273
|
}
|
|
@@ -1177,34 +1278,37 @@ function layoutTree(ast, edges) {
|
|
|
1177
1278
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1178
1279
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1179
1280
|
const treeStartX = SAFE + Math.max(0, (contentW - totalRootsWidth) / 2);
|
|
1180
|
-
const levelHeight = Math.max(
|
|
1181
|
-
const totalTreeHeight = maxDepth * levelHeight +
|
|
1281
|
+
const levelHeight = Math.max(maxNodeH + 40, Math.min(220, contentH / Math.max(maxDepth + 1, 1)));
|
|
1282
|
+
const totalTreeHeight = maxDepth * levelHeight + maxNodeH;
|
|
1182
1283
|
const treeStartY = TITLE_BAND + Math.max(16, (contentH - totalTreeHeight) / 2);
|
|
1183
1284
|
const nodePositions = /* @__PURE__ */ new Map();
|
|
1184
1285
|
function positionSubtree(id, leftX) {
|
|
1185
1286
|
const d = depth.get(id) ?? 0;
|
|
1186
1287
|
const y = treeStartY + d * levelHeight;
|
|
1187
1288
|
const kids = children.get(id) ?? [];
|
|
1188
|
-
const span = subtreeSpan.get(id) ??
|
|
1289
|
+
const span = subtreeSpan.get(id) ?? leafSpan(id);
|
|
1290
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1189
1291
|
if (kids.length === 0) {
|
|
1190
1292
|
const centerX = leftX + span / 2;
|
|
1191
|
-
nodePositions.set(id, { x: centerX -
|
|
1293
|
+
nodePositions.set(id, { x: centerX - dims.width / 2, y });
|
|
1192
1294
|
return;
|
|
1193
1295
|
}
|
|
1194
1296
|
let curX = leftX;
|
|
1195
1297
|
for (const kid of kids) {
|
|
1196
|
-
const kidSpan = subtreeSpan.get(kid) ??
|
|
1298
|
+
const kidSpan = subtreeSpan.get(kid) ?? leafSpan(kid);
|
|
1197
1299
|
positionSubtree(kid, curX);
|
|
1198
1300
|
curX += kidSpan;
|
|
1199
1301
|
}
|
|
1200
1302
|
const firstChild = nodePositions.get(kids[0]);
|
|
1201
1303
|
const lastChild = nodePositions.get(kids[kids.length - 1]);
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1304
|
+
const firstDims = nodeDims.get(kids[0]) ?? { width: NODE_W, height: NODE_H };
|
|
1305
|
+
const lastDims = nodeDims.get(kids[kids.length - 1]) ?? { width: NODE_W, height: NODE_H };
|
|
1306
|
+
const parentCenterX = (firstChild.x + firstDims.width / 2 + lastChild.x + lastDims.width / 2) / 2;
|
|
1307
|
+
nodePositions.set(id, { x: parentCenterX - dims.width / 2, y });
|
|
1204
1308
|
}
|
|
1205
1309
|
let currentRootLeft = treeStartX;
|
|
1206
1310
|
for (const r of roots) {
|
|
1207
|
-
const span = subtreeSpan.get(r) ??
|
|
1311
|
+
const span = subtreeSpan.get(r) ?? leafSpan(r);
|
|
1208
1312
|
positionSubtree(r, currentRootLeft);
|
|
1209
1313
|
currentRootLeft += span;
|
|
1210
1314
|
}
|
|
@@ -1212,6 +1316,7 @@ function layoutTree(ast, edges) {
|
|
|
1212
1316
|
for (const id of nodeIds) {
|
|
1213
1317
|
const decl = ast.nodes[id];
|
|
1214
1318
|
const pos = nodePositions.get(id) ?? { x: SAFE, y: TITLE_BAND };
|
|
1319
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1215
1320
|
nodes.push({
|
|
1216
1321
|
id,
|
|
1217
1322
|
kind: decl.kind,
|
|
@@ -1219,12 +1324,12 @@ function layoutTree(ast, edges) {
|
|
|
1219
1324
|
label: decl.label,
|
|
1220
1325
|
x: snapGrid(pos.x),
|
|
1221
1326
|
y: snapGrid(pos.y),
|
|
1222
|
-
width:
|
|
1223
|
-
height:
|
|
1327
|
+
width: dims.width,
|
|
1328
|
+
height: dims.height,
|
|
1224
1329
|
style: resolveNodeStyle(decl, ast),
|
|
1225
1330
|
props: decl.props,
|
|
1226
1331
|
opacity: 0,
|
|
1227
|
-
shape: "
|
|
1332
|
+
shape: nodeShape(decl.kind, "tree", decl.props),
|
|
1228
1333
|
focal: decl.props.focal === true || decl.props.accent === true
|
|
1229
1334
|
});
|
|
1230
1335
|
}
|
|
@@ -1260,6 +1365,12 @@ function cycleSafeEdges(nodeIds, edges) {
|
|
|
1260
1365
|
function layoutConstellation(ast) {
|
|
1261
1366
|
const nodeIds = Object.keys(ast.nodes);
|
|
1262
1367
|
if (nodeIds.length === 0) return [];
|
|
1368
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1369
|
+
for (const id of nodeIds) {
|
|
1370
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1371
|
+
}
|
|
1372
|
+
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width), NODE_W);
|
|
1373
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height), NODE_H);
|
|
1263
1374
|
const incoming = /* @__PURE__ */ new Set();
|
|
1264
1375
|
for (const edge of ast.edges) {
|
|
1265
1376
|
if (edge.kind !== "response" && edge.from !== edge.to && ast.nodes[edge.to]) incoming.add(edge.to);
|
|
@@ -1267,19 +1378,18 @@ function layoutConstellation(ast) {
|
|
|
1267
1378
|
const focalId = nodeIds.find((id) => ast.nodes[id].props.focal === true || ast.nodes[id].props.accent === true) ?? nodeIds.find((id) => !incoming.has(id)) ?? nodeIds[0];
|
|
1268
1379
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1269
1380
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1270
|
-
const centerX = SAFE + contentW / 2 - NODE_W / 2;
|
|
1271
|
-
const centerY = TITLE_BAND + contentH / 2 - NODE_H / 2;
|
|
1272
1381
|
const orbitIds = nodeIds.filter((id) => id !== focalId);
|
|
1273
|
-
const radiusX = Math.max(140, contentW / 2 -
|
|
1274
|
-
const radiusY = Math.max(120, contentH / 2 -
|
|
1382
|
+
const radiusX = Math.max(140, contentW / 2 - maxNodeW / 2 - SAFE);
|
|
1383
|
+
const radiusY = Math.max(120, contentH / 2 - maxNodeH / 2 - SAFE);
|
|
1275
1384
|
const nodes = [];
|
|
1276
1385
|
for (const id of nodeIds) {
|
|
1277
1386
|
const decl = ast.nodes[id];
|
|
1387
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1278
1388
|
const isFocal = id === focalId;
|
|
1279
1389
|
const index = orbitIds.indexOf(id);
|
|
1280
1390
|
const angle = orbitIds.length > 0 ? -Math.PI / 2 + index * Math.PI * 2 / orbitIds.length : 0;
|
|
1281
|
-
const x = isFocal ?
|
|
1282
|
-
const y = isFocal ?
|
|
1391
|
+
const x = isFocal ? SAFE + contentW / 2 - dims.width / 2 : SAFE + contentW / 2 + Math.cos(angle) * radiusX - dims.width / 2;
|
|
1392
|
+
const y = isFocal ? TITLE_BAND + contentH / 2 - dims.height / 2 : TITLE_BAND + contentH / 2 + Math.sin(angle) * radiusY - dims.height / 2;
|
|
1283
1393
|
nodes.push({
|
|
1284
1394
|
id,
|
|
1285
1395
|
kind: decl.kind,
|
|
@@ -1287,8 +1397,8 @@ function layoutConstellation(ast) {
|
|
|
1287
1397
|
label: decl.label,
|
|
1288
1398
|
x: snapGrid(x),
|
|
1289
1399
|
y: snapGrid(y),
|
|
1290
|
-
width:
|
|
1291
|
-
height:
|
|
1400
|
+
width: dims.width,
|
|
1401
|
+
height: dims.height,
|
|
1292
1402
|
style: resolveNodeStyle(decl, ast),
|
|
1293
1403
|
props: decl.props,
|
|
1294
1404
|
opacity: 0,
|
|
@@ -1301,6 +1411,12 @@ function layoutConstellation(ast) {
|
|
|
1301
1411
|
function layoutLoop(ast) {
|
|
1302
1412
|
const nodeIds = Object.keys(ast.nodes);
|
|
1303
1413
|
if (nodeIds.length === 0) return [];
|
|
1414
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1415
|
+
for (const id of nodeIds) {
|
|
1416
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1417
|
+
}
|
|
1418
|
+
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width), NODE_W);
|
|
1419
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height), NODE_H);
|
|
1304
1420
|
const hubId = nodeIds.find((id) => {
|
|
1305
1421
|
const decl = ast.nodes[id];
|
|
1306
1422
|
return decl.kind === "hub" || decl.props.hub === true || /^(hub|state|memory|shared_memory|core)$/i.test(id);
|
|
@@ -1310,22 +1426,25 @@ function layoutLoop(ast) {
|
|
|
1310
1426
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1311
1427
|
const centerX = SAFE + contentW / 2;
|
|
1312
1428
|
const centerY = TITLE_BAND + contentH / 2;
|
|
1313
|
-
const radiusX = Math.min(contentW * 0.4, Math.max(220, (contentW -
|
|
1314
|
-
const radiusY = Math.min(contentH * 0.38, Math.max(150, (contentH -
|
|
1429
|
+
const radiusX = Math.min(contentW * 0.4, Math.max(220, (contentW - maxNodeW) / 2 - 32));
|
|
1430
|
+
const radiusY = Math.min(contentH * 0.38, Math.max(150, (contentH - maxNodeH) / 2 - 32));
|
|
1315
1431
|
const nodes = [];
|
|
1316
1432
|
for (const id of nodeIds) {
|
|
1317
1433
|
const decl = ast.nodes[id];
|
|
1318
1434
|
const isHub = id === hubId;
|
|
1435
|
+
const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
|
|
1436
|
+
const w = isHub ? Math.max(dims.width, snapGrid(NODE_W * 1.25)) : dims.width;
|
|
1437
|
+
const h = isHub ? Math.max(dims.height, snapGrid(NODE_H * 1.15)) : dims.height;
|
|
1319
1438
|
let x;
|
|
1320
1439
|
let y;
|
|
1321
1440
|
if (isHub) {
|
|
1322
|
-
x = centerX -
|
|
1323
|
-
y = centerY -
|
|
1441
|
+
x = centerX - w / 2;
|
|
1442
|
+
y = centerY - h / 2;
|
|
1324
1443
|
} else {
|
|
1325
1444
|
const idx = stationIds.indexOf(id);
|
|
1326
1445
|
const angle = -Math.PI / 2 + idx * 2 * Math.PI / Math.max(stationIds.length, 1);
|
|
1327
|
-
x = centerX + Math.cos(angle) * radiusX -
|
|
1328
|
-
y = centerY + Math.sin(angle) * radiusY -
|
|
1446
|
+
x = centerX + Math.cos(angle) * radiusX - w / 2;
|
|
1447
|
+
y = centerY + Math.sin(angle) * radiusY - h / 2;
|
|
1329
1448
|
}
|
|
1330
1449
|
const focal = isHub || decl.props.focal === true || decl.props.accent === true;
|
|
1331
1450
|
nodes.push({
|
|
@@ -1335,8 +1454,8 @@ function layoutLoop(ast) {
|
|
|
1335
1454
|
label: decl.label,
|
|
1336
1455
|
x: snapGrid(x),
|
|
1337
1456
|
y: snapGrid(y),
|
|
1338
|
-
width:
|
|
1339
|
-
height:
|
|
1457
|
+
width: w,
|
|
1458
|
+
height: h,
|
|
1340
1459
|
style: resolveNodeStyle(decl, ast),
|
|
1341
1460
|
props: decl.props,
|
|
1342
1461
|
opacity: 0,
|
|
@@ -1357,6 +1476,10 @@ function layoutMedallion(ast, edges) {
|
|
|
1357
1476
|
if (combined.includes("bi") || combined.includes("dash") || combined.includes("model") || combined.includes("app") || combined.includes("consumer") || combined.includes("user") || combined.includes("client")) return 4;
|
|
1358
1477
|
return 0;
|
|
1359
1478
|
}
|
|
1479
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1480
|
+
for (const id of nodeIds) {
|
|
1481
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1482
|
+
}
|
|
1360
1483
|
const tiers = /* @__PURE__ */ new Map();
|
|
1361
1484
|
for (let i = 0; i <= 4; i++) tiers.set(i, []);
|
|
1362
1485
|
for (const id of nodeIds) {
|
|
@@ -1368,29 +1491,35 @@ function layoutMedallion(ast, edges) {
|
|
|
1368
1491
|
const tierCount = activeTiers.length || 1;
|
|
1369
1492
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1370
1493
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1494
|
+
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width));
|
|
1495
|
+
const availableColGap = tierCount > 1 ? (contentW - maxNodeW) / (tierCount - 1) : 0;
|
|
1496
|
+
const colGap = Math.max(maxNodeW + 36, Math.min(360, availableColGap));
|
|
1497
|
+
const totalW = (tierCount - 1) * colGap + maxNodeW;
|
|
1373
1498
|
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1374
1499
|
const nodes = [];
|
|
1375
1500
|
activeTiers.forEach(([_, ids], colIdx) => {
|
|
1376
1501
|
const colCount = ids.length;
|
|
1502
|
+
const colTierMaxW = Math.max(...ids.map((id) => nodeDims.get(id).width));
|
|
1503
|
+
const colTierMaxH = Math.max(...ids.map((id) => nodeDims.get(id).height));
|
|
1377
1504
|
const colX = startX + colIdx * colGap;
|
|
1378
|
-
const rowSpacing = Math.max(
|
|
1379
|
-
const colH = (colCount - 1) * rowSpacing +
|
|
1505
|
+
const rowSpacing = Math.max(colTierMaxH + 32, contentH / (colCount + 1));
|
|
1506
|
+
const colH = (colCount - 1) * rowSpacing + colTierMaxH;
|
|
1380
1507
|
const startY = TITLE_BAND + Math.max(0, (contentH - colH) / 2);
|
|
1381
1508
|
ids.forEach((id, rowIdx) => {
|
|
1382
1509
|
const decl = ast.nodes[id];
|
|
1383
|
-
const
|
|
1510
|
+
const dims = nodeDims.get(id);
|
|
1511
|
+
const rowY = startY + rowIdx * rowSpacing + (colTierMaxH - dims.height) / 2;
|
|
1512
|
+
const nodeX = colX + (colTierMaxW - dims.width) / 2;
|
|
1384
1513
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1385
1514
|
nodes.push({
|
|
1386
1515
|
id,
|
|
1387
1516
|
kind: decl.kind,
|
|
1388
1517
|
role: nodeRole(decl.kind),
|
|
1389
1518
|
label: decl.label,
|
|
1390
|
-
x: snapGrid(
|
|
1519
|
+
x: snapGrid(nodeX),
|
|
1391
1520
|
y: snapGrid(rowY),
|
|
1392
|
-
width:
|
|
1393
|
-
height:
|
|
1521
|
+
width: dims.width,
|
|
1522
|
+
height: dims.height,
|
|
1394
1523
|
style: resolveNodeStyle(decl, ast),
|
|
1395
1524
|
props: decl.props,
|
|
1396
1525
|
opacity: 0,
|
|
@@ -1404,6 +1533,10 @@ function layoutMedallion(ast, edges) {
|
|
|
1404
1533
|
function layoutQuadrant(ast) {
|
|
1405
1534
|
const nodeIds = Object.keys(ast.nodes);
|
|
1406
1535
|
if (nodeIds.length === 0) return [];
|
|
1536
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1537
|
+
for (const id of nodeIds) {
|
|
1538
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1539
|
+
}
|
|
1407
1540
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1408
1541
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1409
1542
|
const centerX = SAFE + contentW / 2;
|
|
@@ -1437,12 +1570,15 @@ function layoutQuadrant(ast) {
|
|
|
1437
1570
|
};
|
|
1438
1571
|
for (const [qNum, ids] of quadNodes) {
|
|
1439
1572
|
const center = quadCenters[qNum];
|
|
1573
|
+
const offsetCount = ids.length;
|
|
1574
|
+
const maxQHeight = ids.length > 0 ? Math.max(...ids.map((id) => nodeDims.get(id).height)) : NODE_H;
|
|
1575
|
+
const rowStep = maxQHeight + 20;
|
|
1440
1576
|
ids.forEach((id, idx) => {
|
|
1441
1577
|
const decl = ast.nodes[id];
|
|
1442
|
-
const
|
|
1443
|
-
const rowOffset = (idx - (offsetCount - 1) / 2) *
|
|
1444
|
-
const x = center.x -
|
|
1445
|
-
const y = center.y + rowOffset -
|
|
1578
|
+
const dims = nodeDims.get(id);
|
|
1579
|
+
const rowOffset = (idx - (offsetCount - 1) / 2) * rowStep;
|
|
1580
|
+
const x = center.x - dims.width / 2;
|
|
1581
|
+
const y = center.y + rowOffset - dims.height / 2;
|
|
1446
1582
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1447
1583
|
nodes.push({
|
|
1448
1584
|
id,
|
|
@@ -1451,8 +1587,8 @@ function layoutQuadrant(ast) {
|
|
|
1451
1587
|
label: decl.label,
|
|
1452
1588
|
x: snapGrid(x),
|
|
1453
1589
|
y: snapGrid(y),
|
|
1454
|
-
width:
|
|
1455
|
-
height:
|
|
1590
|
+
width: dims.width,
|
|
1591
|
+
height: dims.height,
|
|
1456
1592
|
style: resolveNodeStyle(decl, ast),
|
|
1457
1593
|
props: decl.props,
|
|
1458
1594
|
opacity: 0,
|
|
@@ -1466,6 +1602,10 @@ function layoutQuadrant(ast) {
|
|
|
1466
1602
|
function layoutSwimlane(ast, edges) {
|
|
1467
1603
|
const nodeIds = Object.keys(ast.nodes);
|
|
1468
1604
|
if (nodeIds.length === 0) return [];
|
|
1605
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1606
|
+
for (const id of nodeIds) {
|
|
1607
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1608
|
+
}
|
|
1469
1609
|
const lanes = /* @__PURE__ */ new Map();
|
|
1470
1610
|
const groupKeys = Object.keys(ast.groups);
|
|
1471
1611
|
if (groupKeys.length > 0) {
|
|
@@ -1488,14 +1628,16 @@ function layoutSwimlane(ast, edges) {
|
|
|
1488
1628
|
const laneHeight = contentH / laneCount;
|
|
1489
1629
|
const nodes = [];
|
|
1490
1630
|
activeLanes.forEach(([_, ids], laneIdx) => {
|
|
1491
|
-
const laneY = TITLE_BAND + laneIdx * laneHeight + (laneHeight - NODE_H) / 2;
|
|
1492
1631
|
const count = ids.length;
|
|
1493
|
-
const
|
|
1494
|
-
const
|
|
1632
|
+
const maxLaneW = count > 0 ? Math.max(...ids.map((id) => nodeDims.get(id).width)) : NODE_W;
|
|
1633
|
+
const colSpacing = Math.max(maxLaneW + 40, Math.min(320, contentW / Math.max(count + 1, 2)));
|
|
1634
|
+
const totalW = (count - 1) * colSpacing + maxLaneW;
|
|
1495
1635
|
const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
|
|
1496
1636
|
ids.forEach((id, colIdx) => {
|
|
1497
1637
|
const decl = ast.nodes[id];
|
|
1498
|
-
const
|
|
1638
|
+
const dims = nodeDims.get(id);
|
|
1639
|
+
const colX = startX + colIdx * colSpacing + (maxLaneW - dims.width) / 2;
|
|
1640
|
+
const laneY = TITLE_BAND + laneIdx * laneHeight + (laneHeight - dims.height) / 2;
|
|
1499
1641
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1500
1642
|
nodes.push({
|
|
1501
1643
|
id,
|
|
@@ -1504,8 +1646,8 @@ function layoutSwimlane(ast, edges) {
|
|
|
1504
1646
|
label: decl.label,
|
|
1505
1647
|
x: snapGrid(colX),
|
|
1506
1648
|
y: snapGrid(laneY),
|
|
1507
|
-
width:
|
|
1508
|
-
height:
|
|
1649
|
+
width: dims.width,
|
|
1650
|
+
height: dims.height,
|
|
1509
1651
|
style: resolveNodeStyle(decl, ast),
|
|
1510
1652
|
props: decl.props,
|
|
1511
1653
|
opacity: 0,
|
|
@@ -1519,6 +1661,10 @@ function layoutSwimlane(ast, edges) {
|
|
|
1519
1661
|
function layoutPyramid(ast) {
|
|
1520
1662
|
const nodeIds = Object.keys(ast.nodes);
|
|
1521
1663
|
if (nodeIds.length === 0) return [];
|
|
1664
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1665
|
+
for (const id of nodeIds) {
|
|
1666
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1667
|
+
}
|
|
1522
1668
|
const ranks = /* @__PURE__ */ new Map();
|
|
1523
1669
|
nodeIds.forEach((id, idx) => {
|
|
1524
1670
|
const decl = ast.nodes[id];
|
|
@@ -1531,20 +1677,26 @@ function layoutPyramid(ast) {
|
|
|
1531
1677
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1532
1678
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1533
1679
|
const centerX = SAFE + contentW / 2;
|
|
1534
|
-
const
|
|
1680
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height));
|
|
1681
|
+
const tierH = Math.max(NODE_H, maxNodeH);
|
|
1682
|
+
const totalH = tierCount * tierH + (tierCount - 1) * 24;
|
|
1535
1683
|
const startY = TITLE_BAND + Math.max(16, (contentH - totalH) / 2);
|
|
1536
1684
|
const nodes = [];
|
|
1537
1685
|
sortedTiers.forEach(([_, ids], tierIdx) => {
|
|
1538
|
-
const tierY = startY + tierIdx * (
|
|
1686
|
+
const tierY = startY + tierIdx * (tierH + 24);
|
|
1539
1687
|
const baseFraction = 0.32 + 0.44 * tierIdx / Math.max(tierCount - 1, 1);
|
|
1540
1688
|
const tierWidth = contentW * baseFraction;
|
|
1541
1689
|
const count = ids.length;
|
|
1542
|
-
const
|
|
1690
|
+
const maxTierW = Math.max(...ids.map((id) => nodeDims.get(id).width));
|
|
1691
|
+
const nodeW = Math.max(maxTierW, Math.min(tierWidth / count - 12, 480));
|
|
1543
1692
|
const totalTierW = count * nodeW + (count - 1) * 16;
|
|
1544
1693
|
const tierStartX = centerX - totalTierW / 2;
|
|
1545
1694
|
ids.forEach((id, idx) => {
|
|
1546
1695
|
const decl = ast.nodes[id];
|
|
1547
|
-
const
|
|
1696
|
+
const dims = nodeDims.get(id);
|
|
1697
|
+
const finalW = Math.max(nodeW, dims.width);
|
|
1698
|
+
const x = tierStartX + idx * (finalW + 16);
|
|
1699
|
+
const y = tierY + (tierH - dims.height) / 2;
|
|
1548
1700
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1549
1701
|
nodes.push({
|
|
1550
1702
|
id,
|
|
@@ -1553,8 +1705,8 @@ function layoutPyramid(ast) {
|
|
|
1553
1705
|
label: decl.label,
|
|
1554
1706
|
x: snapGrid(x),
|
|
1555
1707
|
y: snapGrid(tierY),
|
|
1556
|
-
width: snapGrid(
|
|
1557
|
-
height:
|
|
1708
|
+
width: snapGrid(finalW),
|
|
1709
|
+
height: dims.height,
|
|
1558
1710
|
style: resolveNodeStyle(decl, ast),
|
|
1559
1711
|
props: decl.props,
|
|
1560
1712
|
opacity: 0,
|
|
@@ -1568,6 +1720,10 @@ function layoutPyramid(ast) {
|
|
|
1568
1720
|
function layoutTimeline(ast) {
|
|
1569
1721
|
const nodeIds = Object.keys(ast.nodes);
|
|
1570
1722
|
if (nodeIds.length === 0) return [];
|
|
1723
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1724
|
+
for (const id of nodeIds) {
|
|
1725
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1726
|
+
}
|
|
1571
1727
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1572
1728
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1573
1729
|
const baselineY = TITLE_BAND + contentH / 2;
|
|
@@ -1575,9 +1731,10 @@ function layoutTimeline(ast) {
|
|
|
1575
1731
|
const nodes = [];
|
|
1576
1732
|
nodeIds.forEach((id, idx) => {
|
|
1577
1733
|
const decl = ast.nodes[id];
|
|
1578
|
-
const
|
|
1734
|
+
const dims = nodeDims.get(id);
|
|
1735
|
+
const x = SAFE + spacing * idx + (spacing - dims.width) / 2;
|
|
1579
1736
|
const above = idx % 2 === 0;
|
|
1580
|
-
const y = above ? baselineY -
|
|
1737
|
+
const y = above ? baselineY - dims.height - 24 : baselineY + 24;
|
|
1581
1738
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1582
1739
|
nodes.push({
|
|
1583
1740
|
id,
|
|
@@ -1586,8 +1743,8 @@ function layoutTimeline(ast) {
|
|
|
1586
1743
|
label: decl.label,
|
|
1587
1744
|
x: snapGrid(x),
|
|
1588
1745
|
y: snapGrid(y),
|
|
1589
|
-
width:
|
|
1590
|
-
height:
|
|
1746
|
+
width: dims.width,
|
|
1747
|
+
height: dims.height,
|
|
1591
1748
|
style: resolveNodeStyle(decl, ast),
|
|
1592
1749
|
props: decl.props,
|
|
1593
1750
|
opacity: 0,
|
|
@@ -1600,16 +1757,23 @@ function layoutTimeline(ast) {
|
|
|
1600
1757
|
function layoutGantt(ast) {
|
|
1601
1758
|
const nodeIds = Object.keys(ast.nodes);
|
|
1602
1759
|
if (nodeIds.length === 0) return [];
|
|
1760
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1761
|
+
for (const id of nodeIds) {
|
|
1762
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1763
|
+
}
|
|
1764
|
+
const topBand = effectiveTitleBand(ast);
|
|
1603
1765
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1604
|
-
const contentH = ast.meta.height - SAFE -
|
|
1766
|
+
const contentH = ast.meta.height - SAFE - topBand - SAFE;
|
|
1605
1767
|
const N = nodeIds.length;
|
|
1606
|
-
const
|
|
1607
|
-
const
|
|
1768
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height));
|
|
1769
|
+
const baseBarH = Math.max(44, maxNodeH);
|
|
1770
|
+
const rowH = Math.max(baseBarH + 16, Math.min(100, (contentH - 24) / Math.max(N, 1)));
|
|
1608
1771
|
const totalH = N * rowH;
|
|
1609
|
-
const startY =
|
|
1772
|
+
const startY = topBand + Math.max(16, (contentH - totalH) / 2);
|
|
1610
1773
|
const nodes = [];
|
|
1611
1774
|
nodeIds.forEach((id, idx) => {
|
|
1612
1775
|
const decl = ast.nodes[id];
|
|
1776
|
+
const dims = nodeDims.get(id);
|
|
1613
1777
|
const phase = typeof decl.props.phase === "number" ? decl.props.phase : 0;
|
|
1614
1778
|
const span = typeof decl.props.span === "number" ? decl.props.span : 1;
|
|
1615
1779
|
const totalPhases = Math.max(...nodeIds.map((nid) => {
|
|
@@ -1619,7 +1783,8 @@ function layoutGantt(ast) {
|
|
|
1619
1783
|
}), 1);
|
|
1620
1784
|
const unitW = contentW / totalPhases;
|
|
1621
1785
|
const x = SAFE + phase * unitW;
|
|
1622
|
-
const w = Math.max(span * unitW - 8,
|
|
1786
|
+
const w = Math.max(span * unitW - 8, dims.width);
|
|
1787
|
+
const barH = dims.height;
|
|
1623
1788
|
const y = startY + idx * rowH + (rowH - barH) / 2;
|
|
1624
1789
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1625
1790
|
nodes.push({
|
|
@@ -1630,7 +1795,7 @@ function layoutGantt(ast) {
|
|
|
1630
1795
|
x: snapGrid(x),
|
|
1631
1796
|
y: snapGrid(y),
|
|
1632
1797
|
width: snapGrid(w),
|
|
1633
|
-
height: barH,
|
|
1798
|
+
height: snapGrid(barH),
|
|
1634
1799
|
style: resolveNodeStyle(decl, ast),
|
|
1635
1800
|
props: decl.props,
|
|
1636
1801
|
opacity: 0,
|
|
@@ -1643,23 +1808,31 @@ function layoutGantt(ast) {
|
|
|
1643
1808
|
function layoutVenn(ast) {
|
|
1644
1809
|
const nodeIds = Object.keys(ast.nodes);
|
|
1645
1810
|
if (nodeIds.length === 0) return [];
|
|
1811
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1812
|
+
for (const id of nodeIds) {
|
|
1813
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1814
|
+
}
|
|
1646
1815
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1647
1816
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1648
1817
|
const centerX = SAFE + contentW / 2;
|
|
1649
1818
|
const centerY = TITLE_BAND + contentH / 2;
|
|
1650
1819
|
const N = nodeIds.length;
|
|
1651
|
-
const
|
|
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);
|
|
1652
1823
|
const nodes = [];
|
|
1653
1824
|
nodeIds.forEach((id, idx) => {
|
|
1654
1825
|
const decl = ast.nodes[id];
|
|
1826
|
+
const dims = nodeDims.get(id);
|
|
1827
|
+
const circleSize = Math.max(vennSize, dims.width, dims.height);
|
|
1655
1828
|
let x, y;
|
|
1656
1829
|
if (N === 2) {
|
|
1657
|
-
x = centerX + (idx === 0 ? -radius * 0.7 : radius * 0.7) -
|
|
1658
|
-
y = centerY -
|
|
1830
|
+
x = centerX + (idx === 0 ? -radius * 0.7 : radius * 0.7) - circleSize / 2;
|
|
1831
|
+
y = centerY - circleSize / 2;
|
|
1659
1832
|
} else {
|
|
1660
1833
|
const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
|
|
1661
|
-
x = centerX + radius * 0.85 * Math.cos(angle) -
|
|
1662
|
-
y = centerY + radius * 0.85 * Math.sin(angle) -
|
|
1834
|
+
x = centerX + radius * 0.85 * Math.cos(angle) - circleSize / 2;
|
|
1835
|
+
y = centerY + radius * 0.85 * Math.sin(angle) - circleSize / 2;
|
|
1663
1836
|
}
|
|
1664
1837
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1665
1838
|
nodes.push({
|
|
@@ -1669,8 +1842,8 @@ function layoutVenn(ast) {
|
|
|
1669
1842
|
label: decl.label,
|
|
1670
1843
|
x: snapGrid(x),
|
|
1671
1844
|
y: snapGrid(y),
|
|
1672
|
-
width:
|
|
1673
|
-
height:
|
|
1845
|
+
width: circleSize,
|
|
1846
|
+
height: circleSize,
|
|
1674
1847
|
style: resolveNodeStyle(decl, ast),
|
|
1675
1848
|
props: decl.props,
|
|
1676
1849
|
opacity: 0,
|
|
@@ -1683,18 +1856,26 @@ function layoutVenn(ast) {
|
|
|
1683
1856
|
function layoutLayers(ast) {
|
|
1684
1857
|
const nodeIds = Object.keys(ast.nodes);
|
|
1685
1858
|
if (nodeIds.length === 0) return [];
|
|
1859
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1860
|
+
for (const id of nodeIds) {
|
|
1861
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1862
|
+
}
|
|
1686
1863
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1687
1864
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1688
1865
|
const N = nodeIds.length;
|
|
1689
|
-
const
|
|
1866
|
+
const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id).height));
|
|
1867
|
+
const layerH = Math.max(maxNodeH, Math.min(Math.max((contentH - (N - 1) * 14) / N, 52), 96));
|
|
1690
1868
|
const totalH = N * layerH + (N - 1) * 14;
|
|
1691
|
-
const startY = TITLE_BAND + (contentH - totalH) / 2;
|
|
1692
|
-
const
|
|
1869
|
+
const startY = TITLE_BAND + Math.max(16, (contentH - totalH) / 2);
|
|
1870
|
+
const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id).width));
|
|
1871
|
+
const layerW = Math.max(maxNodeW, Math.min(contentW, 880));
|
|
1693
1872
|
const startX = SAFE + (contentW - layerW) / 2;
|
|
1694
1873
|
const nodes = [];
|
|
1695
1874
|
nodeIds.forEach((id, idx) => {
|
|
1696
1875
|
const decl = ast.nodes[id];
|
|
1876
|
+
const dims = nodeDims.get(id);
|
|
1697
1877
|
const y = startY + idx * (layerH + 14);
|
|
1878
|
+
const nodeH = Math.max(layerH, dims.height);
|
|
1698
1879
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1699
1880
|
nodes.push({
|
|
1700
1881
|
id,
|
|
@@ -1704,7 +1885,7 @@ function layoutLayers(ast) {
|
|
|
1704
1885
|
x: snapGrid(startX),
|
|
1705
1886
|
y: snapGrid(y),
|
|
1706
1887
|
width: snapGrid(layerW),
|
|
1707
|
-
height: snapGrid(
|
|
1888
|
+
height: snapGrid(nodeH),
|
|
1708
1889
|
style: resolveNodeStyle(decl, ast),
|
|
1709
1890
|
props: decl.props,
|
|
1710
1891
|
opacity: 0,
|
|
@@ -1717,6 +1898,10 @@ function layoutLayers(ast) {
|
|
|
1717
1898
|
function layoutNested(ast) {
|
|
1718
1899
|
const nodeIds = Object.keys(ast.nodes);
|
|
1719
1900
|
if (nodeIds.length === 0) return [];
|
|
1901
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1902
|
+
for (const id of nodeIds) {
|
|
1903
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1904
|
+
}
|
|
1720
1905
|
const topHeadroom = 20;
|
|
1721
1906
|
const startY = TITLE_BAND + topHeadroom;
|
|
1722
1907
|
const contentW = ast.meta.width - SAFE * 2;
|
|
@@ -1729,18 +1914,19 @@ function layoutNested(ast) {
|
|
|
1729
1914
|
const nodes = [];
|
|
1730
1915
|
nodeIds.forEach((id, idx) => {
|
|
1731
1916
|
const decl = ast.nodes[id];
|
|
1917
|
+
const dims = nodeDims.get(id);
|
|
1732
1918
|
const isCore = idx === N - 1;
|
|
1733
1919
|
let x, y, w, h;
|
|
1734
1920
|
if (isCore) {
|
|
1735
|
-
w = Math.min(320, contentW - idx * padX * 2);
|
|
1736
|
-
h = 84;
|
|
1921
|
+
w = Math.max(dims.width, Math.min(320, contentW - idx * padX * 2));
|
|
1922
|
+
h = Math.max(dims.height, 84);
|
|
1737
1923
|
x = centerX - w / 2;
|
|
1738
1924
|
y = centerY - h / 2 + (N > 1 ? 16 : 0);
|
|
1739
1925
|
} else {
|
|
1740
1926
|
x = SAFE + idx * padX;
|
|
1741
1927
|
y = startY + idx * padY;
|
|
1742
|
-
w = contentW - idx * padX * 2;
|
|
1743
|
-
h = contentH - idx * padY * 2;
|
|
1928
|
+
w = Math.max(dims.width, contentW - idx * padX * 2);
|
|
1929
|
+
h = Math.max(dims.height, contentH - idx * padY * 2);
|
|
1744
1930
|
}
|
|
1745
1931
|
const focal = decl.props.focal === true || decl.props.accent === true || isCore;
|
|
1746
1932
|
nodes.push({
|
|
@@ -1764,6 +1950,10 @@ function layoutNested(ast) {
|
|
|
1764
1950
|
function layoutRadar(ast) {
|
|
1765
1951
|
const nodeIds = Object.keys(ast.nodes);
|
|
1766
1952
|
if (nodeIds.length === 0) return [];
|
|
1953
|
+
const nodeDims = /* @__PURE__ */ new Map();
|
|
1954
|
+
for (const id of nodeIds) {
|
|
1955
|
+
nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
|
|
1956
|
+
}
|
|
1767
1957
|
const contentW = ast.meta.width - SAFE * 2;
|
|
1768
1958
|
const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
|
|
1769
1959
|
const centerX = SAFE + contentW / 2;
|
|
@@ -1773,9 +1963,10 @@ function layoutRadar(ast) {
|
|
|
1773
1963
|
const nodes = [];
|
|
1774
1964
|
nodeIds.forEach((id, idx) => {
|
|
1775
1965
|
const decl = ast.nodes[id];
|
|
1966
|
+
const dims = nodeDims.get(id);
|
|
1776
1967
|
const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
|
|
1777
|
-
const x = centerX + radius * Math.cos(angle) -
|
|
1778
|
-
const y = centerY + radius * Math.sin(angle) -
|
|
1968
|
+
const x = centerX + radius * Math.cos(angle) - dims.width / 2;
|
|
1969
|
+
const y = centerY + radius * Math.sin(angle) - dims.height / 2;
|
|
1779
1970
|
const focal = decl.props.focal === true || decl.props.accent === true;
|
|
1780
1971
|
nodes.push({
|
|
1781
1972
|
id,
|
|
@@ -1784,8 +1975,8 @@ function layoutRadar(ast) {
|
|
|
1784
1975
|
label: decl.label,
|
|
1785
1976
|
x: snapGrid(x),
|
|
1786
1977
|
y: snapGrid(y),
|
|
1787
|
-
width:
|
|
1788
|
-
height:
|
|
1978
|
+
width: dims.width,
|
|
1979
|
+
height: dims.height,
|
|
1789
1980
|
style: resolveNodeStyle(decl, ast),
|
|
1790
1981
|
props: decl.props,
|
|
1791
1982
|
opacity: 0,
|
|
@@ -1830,9 +2021,9 @@ function layoutNodes(ast, edges) {
|
|
|
1830
2021
|
case "nested":
|
|
1831
2022
|
return layoutNested(ast);
|
|
1832
2023
|
case "state":
|
|
1833
|
-
return layoutRanked(ast, cycleSafeEdges(Object.keys(ast.nodes), edges), { forceVertical:
|
|
2024
|
+
return layoutRanked(ast, cycleSafeEdges(Object.keys(ast.nodes), edges), { forceVertical: ast.meta.direction === "TB" || ast.meta.direction === "BT" });
|
|
1834
2025
|
default:
|
|
1835
|
-
return layoutRanked(ast, edges, { forceVertical:
|
|
2026
|
+
return layoutRanked(ast, edges, { forceVertical: ast.meta.direction === "TB" || ast.meta.direction === "BT" });
|
|
1836
2027
|
}
|
|
1837
2028
|
}
|
|
1838
2029
|
function computeGroupBoundaries(ast, nodes) {
|
|
@@ -1917,13 +2108,51 @@ function scheduleBeats(ast, edges) {
|
|
|
1917
2108
|
let edgeCounter = edges.length;
|
|
1918
2109
|
const edgeIds = edges.map((e) => e.id);
|
|
1919
2110
|
const groupMap = Object.fromEntries(Object.entries(ast.groups).map(([k, g]) => [k, g.members]));
|
|
1920
|
-
const
|
|
1921
|
-
|
|
2111
|
+
const hasShowCue = ast.beats.some(
|
|
2112
|
+
(b) => b.cues.some((c) => c.kind === "show" || c.kind === "parallel" && c.cues.some((pc) => pc.kind === "show"))
|
|
2113
|
+
);
|
|
2114
|
+
const flowNodeIds = /* @__PURE__ */ new Set();
|
|
2115
|
+
for (const b of ast.beats) {
|
|
2116
|
+
for (const c of b.cues) {
|
|
2117
|
+
if (c.kind === "flow") {
|
|
2118
|
+
for (const seg of c.segments) {
|
|
2119
|
+
flowNodeIds.add(seg.from);
|
|
2120
|
+
flowNodeIds.add(seg.to);
|
|
2121
|
+
}
|
|
2122
|
+
} else if (c.kind === "parallel") {
|
|
2123
|
+
for (const pc of c.cues) {
|
|
2124
|
+
if (pc.kind === "flow") {
|
|
2125
|
+
for (const seg of pc.segments) {
|
|
2126
|
+
flowNodeIds.add(seg.from);
|
|
2127
|
+
flowNodeIds.add(seg.to);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
const explicitlyShownNodes = /* @__PURE__ */ new Set();
|
|
2135
|
+
for (const b of ast.beats) {
|
|
2136
|
+
for (const c of b.cues) {
|
|
2137
|
+
if (c.kind === "show") {
|
|
2138
|
+
resolveTargets(c.targets, ast, groupMap, edgeIds).forEach((id) => explicitlyShownNodes.add(id));
|
|
2139
|
+
} else if (c.kind === "parallel") {
|
|
2140
|
+
for (const pc of c.cues) {
|
|
2141
|
+
if (pc.kind === "show") {
|
|
2142
|
+
resolveTargets(pc.targets, ast, groupMap, edgeIds).forEach((id) => explicitlyShownNodes.add(id));
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
const isSequence = ast.meta.type === "sequence";
|
|
2149
|
+
const nodesToReveal = !isSequence && flowNodeIds.size > 0 ? Object.keys(ast.nodes).filter((id) => !flowNodeIds.has(id) && !explicitlyShownNodes.has(id)) : !hasShowCue ? Object.keys(ast.nodes) : [];
|
|
2150
|
+
if (nodesToReveal.length > 0) {
|
|
1922
2151
|
cues.push({
|
|
1923
2152
|
start: 0,
|
|
1924
2153
|
duration: DEFAULTS.show,
|
|
1925
2154
|
kind: "show",
|
|
1926
|
-
targets:
|
|
2155
|
+
targets: nodesToReveal,
|
|
1927
2156
|
params: { stagger: DEFAULTS.stagger },
|
|
1928
2157
|
beat: "__intro"
|
|
1929
2158
|
});
|
|
@@ -1998,8 +2227,10 @@ function scheduleBeats(ast, edges) {
|
|
|
1998
2227
|
}
|
|
1999
2228
|
function buildSequencePlan(ast, cues) {
|
|
2000
2229
|
if (diagramType(ast) !== "sequence") return { messages: [], activations: [] };
|
|
2230
|
+
const nodeDims = Object.values(ast.nodes).map((n) => computeNodeDimensions(n));
|
|
2231
|
+
const maxNodeH = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.height)) : NODE_H;
|
|
2001
2232
|
const flowCues = cues.filter((cue) => cue.kind === "flow" && cue.segments?.[0]);
|
|
2002
|
-
const firstY = TITLE_BAND +
|
|
2233
|
+
const firstY = TITLE_BAND + maxNodeH + 56;
|
|
2003
2234
|
const lastY = ast.meta.height - SAFE - 48;
|
|
2004
2235
|
const availH = Math.max(120, lastY - firstY);
|
|
2005
2236
|
const step = flowCues.length > 1 ? Math.max(52, Math.min(90, availH / flowCues.length)) : 0;
|
|
@@ -2048,19 +2279,44 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2048
2279
|
const direction = ast.meta.direction ?? "LR";
|
|
2049
2280
|
const isVertical = direction === "TB" || direction === "BT";
|
|
2050
2281
|
const groupCount = Object.keys(ast.groups).length;
|
|
2051
|
-
|
|
2052
|
-
|
|
2282
|
+
const nodeDims = nodeIds.map((id) => computeNodeDimensions(ast.nodes[id]));
|
|
2283
|
+
const maxNodeW = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.width)) : NODE_W;
|
|
2284
|
+
const maxNodeH = nodeDims.length > 0 ? Math.max(...nodeDims.map((d) => d.height)) : NODE_H;
|
|
2285
|
+
const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
|
|
2286
|
+
const titleBand = hasTitle ? TITLE_BAND : 0;
|
|
2287
|
+
const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
|
|
2288
|
+
const bottomBand = hasBeatCaptions ? 44 : 0;
|
|
2289
|
+
const titleW = hasTitle ? Math.max(320, ast.meta.title.trim().length * 11 + SAFE * 2) : 0;
|
|
2290
|
+
let autoW = 1024;
|
|
2291
|
+
let autoH = 640;
|
|
2053
2292
|
if (nodeCount === 0) {
|
|
2054
|
-
autoW =
|
|
2055
|
-
autoH =
|
|
2293
|
+
autoW = 960;
|
|
2294
|
+
autoH = 540;
|
|
2056
2295
|
} else if (dtype === "sequence") {
|
|
2057
2296
|
const flowCues = ast.beats.flatMap((b) => b.cues).filter((c) => c.kind === "flow");
|
|
2058
2297
|
const count = Math.max(nodeCount, 1);
|
|
2059
2298
|
const flowCount = flowCues.length;
|
|
2060
|
-
const
|
|
2061
|
-
const
|
|
2062
|
-
|
|
2063
|
-
|
|
2299
|
+
const colW = Math.max(200, maxNodeW + 36);
|
|
2300
|
+
const requiredW = SAFE * 2 + Math.max(count * colW, titleW);
|
|
2301
|
+
const requiredH = titleBand + maxNodeH + 48 + Math.max(flowCount * 68, 160) + SAFE + bottomBand;
|
|
2302
|
+
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2303
|
+
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2304
|
+
} else if (dtype === "medallion") {
|
|
2305
|
+
const tierSet = /* @__PURE__ */ new Set();
|
|
2306
|
+
for (const id of nodeIds) {
|
|
2307
|
+
const combined = `${id} ${ast.nodes[id].kind}`.toLowerCase();
|
|
2308
|
+
if (combined.includes("bronze") || combined.includes("raw") || combined.includes("landing")) tierSet.add(1);
|
|
2309
|
+
else if (combined.includes("silver") || combined.includes("clean") || combined.includes("curated") || combined.includes("conformed")) tierSet.add(2);
|
|
2310
|
+
else if (combined.includes("gold") || combined.includes("agg") || combined.includes("mart") || combined.includes("analytics")) tierSet.add(3);
|
|
2311
|
+
else if (combined.includes("bi") || combined.includes("dash") || combined.includes("model") || combined.includes("app") || combined.includes("consumer") || combined.includes("user") || combined.includes("client")) tierSet.add(4);
|
|
2312
|
+
else tierSet.add(0);
|
|
2313
|
+
}
|
|
2314
|
+
const tierCount = Math.max(tierSet.size, 4);
|
|
2315
|
+
const tierW = Math.max(260, maxNodeW + 48);
|
|
2316
|
+
const requiredW = Math.max(SAFE * 2 + tierCount * tierW, titleW);
|
|
2317
|
+
const requiredH = titleBand + SAFE * 2 + Math.max(440, maxNodeH * 3 + 120) + bottomBand;
|
|
2318
|
+
autoW = Math.max(640, Math.min(2800, requiredW));
|
|
2319
|
+
autoH = Math.max(400, Math.min(1600, requiredH));
|
|
2064
2320
|
} else if (dtype === "tree") {
|
|
2065
2321
|
let measureSubtree2 = function(id) {
|
|
2066
2322
|
const kids = children.get(id) ?? [];
|
|
@@ -2101,15 +2357,16 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2101
2357
|
}
|
|
2102
2358
|
for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
|
|
2103
2359
|
const maxDepth = Math.max(...depth.values(), 0);
|
|
2104
|
-
const minLeafWidth = NODE_W +
|
|
2360
|
+
const minLeafWidth = Math.max(NODE_W + 36, maxNodeW + 36);
|
|
2105
2361
|
const subtreeSpan = /* @__PURE__ */ new Map();
|
|
2106
2362
|
let totalRootsWidth = 0;
|
|
2107
2363
|
const activeRoots = roots.length > 0 ? roots : nodeIds;
|
|
2108
2364
|
for (const r of activeRoots) totalRootsWidth += measureSubtree2(r);
|
|
2109
|
-
const
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
|
|
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));
|
|
2113
2370
|
} else if (dtype === "swimlane") {
|
|
2114
2371
|
const laneCount = Math.max(groupCount, 1);
|
|
2115
2372
|
const maxInLane = Math.max(
|
|
@@ -2117,37 +2374,56 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2117
2374
|
Math.ceil(nodeCount / laneCount),
|
|
2118
2375
|
1
|
|
2119
2376
|
);
|
|
2120
|
-
const
|
|
2121
|
-
const
|
|
2122
|
-
|
|
2123
|
-
|
|
2377
|
+
const stepX = Math.max(200, maxNodeW + 36);
|
|
2378
|
+
const laneH = Math.max(130, maxNodeH + 54);
|
|
2379
|
+
const requiredW = Math.max(SAFE * 2 + 140 + maxInLane * stepX, titleW);
|
|
2380
|
+
const requiredH = titleBand + SAFE * 2 + laneCount * laneH + bottomBand;
|
|
2381
|
+
autoW = Math.max(480, Math.min(2560, requiredW));
|
|
2382
|
+
autoH = Math.max(320, Math.min(1800, requiredH));
|
|
2124
2383
|
} else if (dtype === "pyramid") {
|
|
2125
2384
|
const tierCount = Math.max(nodeCount, 1);
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2385
|
+
const tierH = Math.max(100, maxNodeH + 28);
|
|
2386
|
+
autoW = Math.max(480, Math.min(2e3, Math.max(SAFE * 2 + maxNodeW * 2 + 100, titleW)));
|
|
2387
|
+
const requiredH = titleBand + SAFE * 2 + tierCount * tierH + bottomBand;
|
|
2388
|
+
autoH = Math.max(320, Math.min(1440, requiredH));
|
|
2129
2389
|
} else if (dtype === "layers") {
|
|
2130
2390
|
const layerCount = Math.max(nodeCount, 1);
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2391
|
+
const layerH = Math.max(90, maxNodeH + 18);
|
|
2392
|
+
autoW = Math.max(480, Math.min(2e3, Math.max(SAFE * 2 + maxNodeW + 160, titleW)));
|
|
2393
|
+
const requiredH = titleBand + SAFE * 2 + layerCount * layerH + bottomBand;
|
|
2394
|
+
autoH = Math.max(320, Math.min(1440, requiredH));
|
|
2134
2395
|
} else if (dtype === "timeline" || dtype === "gantt") {
|
|
2135
2396
|
const milestones = Math.max(nodeCount, 1);
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2397
|
+
let totalPhases = milestones;
|
|
2398
|
+
if (dtype === "gantt") {
|
|
2399
|
+
totalPhases = Math.max(...nodeIds.map((nid) => {
|
|
2400
|
+
const p = ast.nodes[nid].props.phase;
|
|
2401
|
+
const s = ast.nodes[nid].props.span;
|
|
2402
|
+
return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
|
|
2403
|
+
}), milestones);
|
|
2404
|
+
}
|
|
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));
|
|
2140
2413
|
} else if (dtype === "loop" || dtype === "flywheel" || dtype === "radar" || dtype === "venn" || dtype === "constellation") {
|
|
2141
2414
|
const N = Math.max(nodeCount, 3);
|
|
2142
|
-
const minRadius =
|
|
2143
|
-
const
|
|
2144
|
-
const
|
|
2145
|
-
const
|
|
2146
|
-
|
|
2147
|
-
|
|
2415
|
+
const minRadius = Math.max(160, maxNodeW * 0.75);
|
|
2416
|
+
const nodeFootprint = Math.max(160, maxNodeW + 16);
|
|
2417
|
+
const radiusNeeded = Math.max(minRadius, N * nodeFootprint / (2 * Math.PI));
|
|
2418
|
+
const requiredW = Math.max(SAFE * 2 + radiusNeeded * 2 + maxNodeW + 40, titleW);
|
|
2419
|
+
const requiredH = titleBand + SAFE * 2 + radiusNeeded * 2 + maxNodeH + 40 + bottomBand;
|
|
2420
|
+
autoW = Math.max(480, Math.min(2400, requiredW));
|
|
2421
|
+
autoH = Math.max(360, Math.min(1800, requiredH));
|
|
2148
2422
|
} else if (dtype === "quadrant") {
|
|
2149
|
-
|
|
2150
|
-
|
|
2423
|
+
const requiredW = Math.max(480, SAFE * 2 + maxNodeW * 2.5, titleW);
|
|
2424
|
+
const requiredH = titleBand + SAFE * 2 + maxNodeH * 3 + bottomBand;
|
|
2425
|
+
autoW = Math.min(2400, requiredW);
|
|
2426
|
+
autoH = Math.min(1600, requiredH);
|
|
2151
2427
|
} else {
|
|
2152
2428
|
const forceVertical = dtype === "flowchart" && isVertical || isVertical;
|
|
2153
2429
|
const ranks = assignRanks(nodeIds, effectiveEdges, forceVertical ? "TB" : "LR");
|
|
@@ -2160,47 +2436,26 @@ function computeAdaptiveDimensions(ast, edges) {
|
|
|
2160
2436
|
const rankCount = Math.max(...byRank.keys(), 0) + 1;
|
|
2161
2437
|
const maxInRank = Math.max(...[...byRank.values()].map((v) => v.length), 1);
|
|
2162
2438
|
const groupPaddingBonus = groupCount > 0 ? GROUP_PAD * 2 : 0;
|
|
2439
|
+
const maxRankW = maxNodeW;
|
|
2440
|
+
const maxRankH = maxNodeH;
|
|
2163
2441
|
if (forceVertical) {
|
|
2164
|
-
const
|
|
2165
|
-
const
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
} else {
|
|
2173
|
-
autoW = Math.max(960, Math.min(2400, requiredW));
|
|
2174
|
-
autoH = Math.max(720, Math.min(2e3, requiredH));
|
|
2175
|
-
}
|
|
2442
|
+
const colSpacing = 44;
|
|
2443
|
+
const rowGap = 56;
|
|
2444
|
+
const contentNeededW = maxInRank * maxRankW + Math.max(0, maxInRank - 1) * colSpacing + groupPaddingBonus;
|
|
2445
|
+
const requiredW = SAFE * 2 + contentNeededW;
|
|
2446
|
+
autoW = Math.max(480, Math.min(2400, Math.max(requiredW, titleW)));
|
|
2447
|
+
const contentNeededH = rankCount * maxRankH + Math.max(0, rankCount - 1) * rowGap + groupPaddingBonus;
|
|
2448
|
+
const requiredH = SAFE * 2 + titleBand + bottomBand + contentNeededH;
|
|
2449
|
+
autoH = Math.max(360, Math.min(3200, requiredH));
|
|
2176
2450
|
} else {
|
|
2177
|
-
const
|
|
2178
|
-
const
|
|
2179
|
-
const
|
|
2180
|
-
const
|
|
2181
|
-
const
|
|
2182
|
-
const
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
if (maxInRank === 1 && !hasTitle) {
|
|
2186
|
-
autoW = Math.max(1024, Math.min(2560, requiredW));
|
|
2187
|
-
autoH = Math.max(hasBeatCaptions ? 240 : 208, Math.min(360, requiredH + 24));
|
|
2188
|
-
} else if (maxInRank === 2 && !hasTitle) {
|
|
2189
|
-
autoW = Math.max(1024, Math.min(2560, requiredW));
|
|
2190
|
-
autoH = Math.max(hasBeatCaptions ? 368 : 336, Math.min(480, requiredH + 24));
|
|
2191
|
-
} else if (maxInRank === 3 && !hasTitle) {
|
|
2192
|
-
autoW = Math.max(1152, Math.min(2560, requiredW));
|
|
2193
|
-
autoH = Math.max(hasBeatCaptions ? 480 : 448, Math.min(640, requiredH + 24));
|
|
2194
|
-
} else if (nodeCount <= 2 && rankCount <= 2) {
|
|
2195
|
-
autoW = 1024;
|
|
2196
|
-
autoH = hasTitle ? 576 : 384;
|
|
2197
|
-
} else if (nodeCount <= 4 && rankCount <= 3 && maxInRank <= 2) {
|
|
2198
|
-
autoW = 1152;
|
|
2199
|
-
autoH = hasTitle ? 648 : 448;
|
|
2200
|
-
} else {
|
|
2201
|
-
autoW = Math.max(1152, Math.min(2560, requiredW));
|
|
2202
|
-
autoH = Math.max(hasTitle ? 648 : maxInRank <= 2 ? 448 : 576, Math.min(1600, requiredH));
|
|
2203
|
-
}
|
|
2451
|
+
const colGap = 56;
|
|
2452
|
+
const rowGap = 44;
|
|
2453
|
+
const contentNeededW = rankCount * maxRankW + Math.max(0, rankCount - 1) * colGap + groupPaddingBonus;
|
|
2454
|
+
const requiredW = SAFE * 2 + contentNeededW;
|
|
2455
|
+
const contentNeededH = maxInRank * maxRankH + Math.max(0, maxInRank - 1) * rowGap + groupPaddingBonus;
|
|
2456
|
+
const requiredH = SAFE * 2 + titleBand + bottomBand + contentNeededH;
|
|
2457
|
+
autoW = Math.max(480, Math.min(2560, Math.max(requiredW, titleW)));
|
|
2458
|
+
autoH = Math.max(240, Math.min(1800, requiredH));
|
|
2204
2459
|
}
|
|
2205
2460
|
}
|
|
2206
2461
|
return {
|
|
@@ -3339,7 +3594,8 @@ function parse(source, opts = {}) {
|
|
|
3339
3594
|
fps: 60,
|
|
3340
3595
|
theme: "paper",
|
|
3341
3596
|
explicitTheme: false,
|
|
3342
|
-
direction: "LR"
|
|
3597
|
+
direction: "LR",
|
|
3598
|
+
explicitDirection: false
|
|
3343
3599
|
};
|
|
3344
3600
|
const styles = {};
|
|
3345
3601
|
const nodes = {};
|
|
@@ -3443,6 +3699,7 @@ function parse(source, opts = {}) {
|
|
|
3443
3699
|
const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
|
|
3444
3700
|
if (inlineLayout) {
|
|
3445
3701
|
meta.direction = inlineLayout[1].toUpperCase();
|
|
3702
|
+
meta.explicitDirection = true;
|
|
3446
3703
|
remainder = remainder.replace(/\blayout\s+(LR|RL|TB|BT)\b/i, " ");
|
|
3447
3704
|
}
|
|
3448
3705
|
const props = parseProps(remainder);
|
|
@@ -3464,8 +3721,10 @@ function parse(source, opts = {}) {
|
|
|
3464
3721
|
const val = String(v).toLowerCase();
|
|
3465
3722
|
meta.theme = val;
|
|
3466
3723
|
meta.explicitTheme = val !== "auto";
|
|
3467
|
-
} else if (k === "direction" || k === "layout")
|
|
3468
|
-
|
|
3724
|
+
} else if (k === "direction" || k === "layout") {
|
|
3725
|
+
meta.direction = String(v).toUpperCase();
|
|
3726
|
+
meta.explicitDirection = true;
|
|
3727
|
+
} else if (PLAYER_FLAT_KEY_SET.has(k)) {
|
|
3469
3728
|
const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
|
|
3470
3729
|
if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
|
|
3471
3730
|
} else if (k === "type") {
|
|
@@ -3502,6 +3761,7 @@ function parse(source, opts = {}) {
|
|
|
3502
3761
|
}
|
|
3503
3762
|
if (/^layout\s+(LR|RL|TB|BT)\b/i.test(line)) {
|
|
3504
3763
|
meta.direction = line.split(/\s+/)[1].toUpperCase();
|
|
3764
|
+
meta.explicitDirection = true;
|
|
3505
3765
|
i++;
|
|
3506
3766
|
continue;
|
|
3507
3767
|
}
|
|
@@ -8699,6 +8959,7 @@ export {
|
|
|
8699
8959
|
compilePlan,
|
|
8700
8960
|
compressMarkdyToUrlHash,
|
|
8701
8961
|
computeAdaptiveDimensions,
|
|
8962
|
+
computeNodeDimensions,
|
|
8702
8963
|
damerauLevenshteinDistance,
|
|
8703
8964
|
decompressMarkdyFromUrlHash,
|
|
8704
8965
|
detectArchitectureDrift,
|
package/package.json
CHANGED