@diagrammo/dgmo 0.6.0 → 0.6.1

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/src/d3.ts CHANGED
@@ -184,6 +184,19 @@ import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
184
184
  import { collectIndentedValues, extractColor, parsePipeMetadata } from './utils/parsing';
185
185
  import { matchTagBlockHeading, validateTagValues, resolveTagColor } from './utils/tag-groups';
186
186
  import type { TagGroup } from './utils/tag-groups';
187
+ import {
188
+ LEGEND_HEIGHT as TL_LEGEND_HEIGHT,
189
+ LEGEND_PILL_PAD as TL_LEGEND_PILL_PAD,
190
+ LEGEND_PILL_FONT_SIZE as TL_LEGEND_PILL_FONT_SIZE,
191
+ LEGEND_PILL_FONT_W as TL_LEGEND_PILL_FONT_W,
192
+ LEGEND_CAPSULE_PAD as TL_LEGEND_CAPSULE_PAD,
193
+ LEGEND_DOT_R as TL_LEGEND_DOT_R,
194
+ LEGEND_ENTRY_FONT_SIZE as TL_LEGEND_ENTRY_FONT_SIZE,
195
+ LEGEND_ENTRY_FONT_W as TL_LEGEND_ENTRY_FONT_W,
196
+ LEGEND_ENTRY_DOT_GAP as TL_LEGEND_ENTRY_DOT_GAP,
197
+ LEGEND_ENTRY_TRAIL as TL_LEGEND_ENTRY_TRAIL,
198
+ LEGEND_GROUP_GAP as TL_LEGEND_GROUP_GAP,
199
+ } from './utils/legend-constants';
187
200
 
188
201
  // ============================================================
189
202
  // Shared Rendering Helpers
@@ -3047,7 +3060,7 @@ export function renderTimeline(
3047
3060
  }
3048
3061
  }
3049
3062
 
3050
- // Reserve space for tag legend between title and chart content
3063
+ // Reserve space for tag legend at the bottom of chart content
3051
3064
  const tagLegendReserve = parsed.timelineTagGroups.length > 0 ? 36 : 0;
3052
3065
 
3053
3066
  // ================================================================
@@ -3087,9 +3100,9 @@ export function renderTimeline(
3087
3100
  const scaleMargin = timelineScale ? 40 : 0;
3088
3101
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
3089
3102
  const margin = {
3090
- top: 104 + markerMargin + tagLegendReserve,
3103
+ top: 104 + markerMargin,
3091
3104
  right: 40 + scaleMargin,
3092
- bottom: 40,
3105
+ bottom: 40 + tagLegendReserve,
3093
3106
  left: 60 + scaleMargin,
3094
3107
  };
3095
3108
  const innerWidth = width - margin.left - margin.right;
@@ -3309,9 +3322,9 @@ export function renderTimeline(
3309
3322
  const scaleMargin = timelineScale ? 40 : 0;
3310
3323
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
3311
3324
  const margin = {
3312
- top: 104 + markerMargin + tagLegendReserve,
3325
+ top: 104 + markerMargin,
3313
3326
  right: 200,
3314
- bottom: 40,
3327
+ bottom: 40 + tagLegendReserve,
3315
3328
  left: 60 + scaleMargin,
3316
3329
  };
3317
3330
  const innerWidth = width - margin.left - margin.right;
@@ -3590,9 +3603,9 @@ export function renderTimeline(
3590
3603
  // Group-sorted doesn't need legend space (group names shown on left)
3591
3604
  const baseTopMargin = title ? 50 : 20;
3592
3605
  const margin = {
3593
- top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
3606
+ top: baseTopMargin + (timelineScale ? 40 : 0) + markerMargin,
3594
3607
  right: 40,
3595
- bottom: 40 + scaleMargin,
3608
+ bottom: 40 + scaleMargin + tagLegendReserve,
3596
3609
  left: dynamicLeftMargin,
3597
3610
  };
3598
3611
  const innerWidth = width - margin.left - margin.right;
@@ -3869,9 +3882,9 @@ export function renderTimeline(
3869
3882
  const scaleMargin = timelineScale ? 24 : 0;
3870
3883
  const markerMargin = timelineMarkers.length > 0 ? 30 : 0;
3871
3884
  const margin = {
3872
- top: 104 + (timelineScale ? 40 : 0) + markerMargin + tagLegendReserve,
3885
+ top: 104 + (timelineScale ? 40 : 0) + markerMargin,
3873
3886
  right: 40,
3874
- bottom: 40 + scaleMargin,
3887
+ bottom: 40 + scaleMargin + tagLegendReserve,
3875
3888
  left: 60,
3876
3889
  };
3877
3890
  const innerWidth = width - margin.left - margin.right;
@@ -4121,23 +4134,23 @@ export function renderTimeline(
4121
4134
 
4122
4135
  // ── Tag Legend (org-chart-style pills) ──
4123
4136
  if (parsed.timelineTagGroups.length > 0) {
4124
- const LG_HEIGHT = 28;
4125
- const LG_PILL_PAD = 16;
4126
- const LG_PILL_FONT_SIZE = 11;
4127
- const LG_PILL_FONT_W = LG_PILL_FONT_SIZE * 0.6;
4128
- const LG_CAPSULE_PAD = 4;
4129
- const LG_DOT_R = 4;
4130
- const LG_ENTRY_FONT_SIZE = 10;
4131
- const LG_ENTRY_FONT_W = LG_ENTRY_FONT_SIZE * 0.6;
4132
- const LG_ENTRY_DOT_GAP = 4;
4133
- const LG_ENTRY_TRAIL = 8;
4134
- const LG_GROUP_GAP = 12;
4135
- const LG_ICON_W = 20; // swimlane icon area (icon + surrounding space)
4137
+ const LG_HEIGHT = TL_LEGEND_HEIGHT;
4138
+ const LG_PILL_PAD = TL_LEGEND_PILL_PAD;
4139
+ const LG_PILL_FONT_SIZE = TL_LEGEND_PILL_FONT_SIZE;
4140
+ const LG_PILL_FONT_W = TL_LEGEND_PILL_FONT_W;
4141
+ const LG_CAPSULE_PAD = TL_LEGEND_CAPSULE_PAD;
4142
+ const LG_DOT_R = TL_LEGEND_DOT_R;
4143
+ const LG_ENTRY_FONT_SIZE = TL_LEGEND_ENTRY_FONT_SIZE;
4144
+ const LG_ENTRY_FONT_W = TL_LEGEND_ENTRY_FONT_W;
4145
+ const LG_ENTRY_DOT_GAP = TL_LEGEND_ENTRY_DOT_GAP;
4146
+ const LG_ENTRY_TRAIL = TL_LEGEND_ENTRY_TRAIL;
4147
+ const LG_GROUP_GAP = TL_LEGEND_GROUP_GAP;
4148
+ const LG_ICON_W = 20; // swimlane icon area (icon + surrounding space) — local
4136
4149
 
4137
4150
  const mainSvg = d3Selection.select(container).select<SVGSVGElement>('svg');
4138
4151
  const mainG = mainSvg.select<SVGGElement>('g');
4139
4152
  if (!mainSvg.empty() && !mainG.empty()) {
4140
- const legendY = title ? 50 : 10;
4153
+ const legendY = height - LG_HEIGHT - 4;
4141
4154
 
4142
4155
  const groupBg = isDark
4143
4156
  ? mix(palette.surface, palette.bg, 50)
@@ -4212,6 +4225,7 @@ export function renderTimeline(
4212
4225
  function drawLegend() {
4213
4226
  // Remove previous legend
4214
4227
  mainSvg.selectAll('.tl-tag-legend-group').remove();
4228
+ mainSvg.selectAll('.tl-tag-legend-container').remove();
4215
4229
 
4216
4230
  // Effective color source: explicit color group > swimlane group
4217
4231
  const effectiveColorKey = (currentActiveGroup ?? currentSwimlaneGroup)?.toLowerCase() ?? null;
@@ -4238,6 +4252,13 @@ export function renderTimeline(
4238
4252
 
4239
4253
  let cx = (width - totalW) / 2;
4240
4254
 
4255
+ // Legend container for data-legend-active attribute
4256
+ const legendContainer = mainSvg.append('g')
4257
+ .attr('class', 'tl-tag-legend-container');
4258
+ if (currentActiveGroup) {
4259
+ legendContainer.attr('data-legend-active', currentActiveGroup.toLowerCase());
4260
+ }
4261
+
4241
4262
  for (const lg of visibleGroups) {
4242
4263
  const groupKey = lg.group.name.toLowerCase();
4243
4264
  const isActive = viewMode ||
@@ -4249,7 +4270,7 @@ export function renderTimeline(
4249
4270
  const pillLabel = lg.group.name;
4250
4271
  const pillWidth = pillLabel.length * LG_PILL_FONT_W + LG_PILL_PAD;
4251
4272
 
4252
- const gEl = mainSvg
4273
+ const gEl = legendContainer
4253
4274
  .append('g')
4254
4275
  .attr('transform', `translate(${cx}, ${legendY})`)
4255
4276
  .attr('class', 'tl-tag-legend-group tl-tag-legend-entry')
@@ -5781,7 +5802,7 @@ export async function renderForExport(
5781
5802
  hiddenAttributes?: Set<string>;
5782
5803
  swimlaneTagGroup?: string | null;
5783
5804
  },
5784
- options?: { branding?: boolean; c4Level?: 'context' | 'containers' | 'components' | 'deployment'; c4System?: string; c4Container?: string; scenario?: string }
5805
+ options?: { branding?: boolean; c4Level?: 'context' | 'containers' | 'components' | 'deployment'; c4System?: string; c4Container?: string; tagGroup?: string }
5785
5806
  ): Promise<string> {
5786
5807
  // Flowchart and org chart use their own parser pipelines — intercept before parseVisualization()
5787
5808
  const { parseDgmoChartType } = await import('./dgmo-router');
@@ -5801,7 +5822,7 @@ export async function renderForExport(
5801
5822
 
5802
5823
  // Apply interactive collapse state when provided
5803
5824
  const collapsedNodes = orgExportState?.collapsedNodes;
5804
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
5825
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
5805
5826
  const hiddenAttributes = orgExportState?.hiddenAttributes;
5806
5827
 
5807
5828
  const { parsed: effectiveParsed, hiddenCounts } =
@@ -5841,7 +5862,7 @@ export async function renderForExport(
5841
5862
 
5842
5863
  // Apply interactive collapse state when provided
5843
5864
  const collapsedNodes = orgExportState?.collapsedNodes;
5844
- const activeTagGroup = orgExportState?.activeTagGroup ?? null;
5865
+ const activeTagGroup = orgExportState?.activeTagGroup ?? options?.tagGroup ?? null;
5845
5866
  const hiddenAttributes = orgExportState?.hiddenAttributes;
5846
5867
 
5847
5868
  const { parsed: effectiveParsed, hiddenCounts } =
@@ -5881,7 +5902,7 @@ export async function renderForExport(
5881
5902
  container.style.left = '-9999px';
5882
5903
  document.body.appendChild(container);
5883
5904
 
5884
- renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark');
5905
+ renderKanban(container, kanbanParsed, effectivePalette, theme === 'dark', undefined, undefined, options?.tagGroup);
5885
5906
  return finalizeSvgExport(container, theme, effectivePalette, options);
5886
5907
  }
5887
5908
 
@@ -5921,7 +5942,7 @@ export async function renderForExport(
5921
5942
  const exportHeight = erLayout.height + PADDING * 2 + titleOffset;
5922
5943
  const container = createExportContainer(exportWidth, exportHeight);
5923
5944
 
5924
- renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
5945
+ renderERDiagram(container, erParsed, erLayout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight }, options?.tagGroup);
5925
5946
  return finalizeSvgExport(container, theme, effectivePalette, options);
5926
5947
  }
5927
5948
 
@@ -5979,7 +6000,7 @@ export async function renderForExport(
5979
6000
  ? renderC4Containers
5980
6001
  : renderC4Context;
5981
6002
 
5982
- renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight });
6003
+ renderFn(container, c4Parsed, c4Layout, effectivePalette, theme === 'dark', undefined, { width: exportWidth, height: exportHeight }, options?.tagGroup);
5983
6004
  return finalizeSvgExport(container, theme, effectivePalette, options);
5984
6005
  }
5985
6006
 
@@ -6009,11 +6030,9 @@ export async function renderForExport(
6009
6030
  const infraParsed = parseInfra(content);
6010
6031
  if (infraParsed.error || infraParsed.nodes.length === 0) return '';
6011
6032
 
6012
- const selectedScenario = options?.scenario
6013
- ? infraParsed.scenarios.find((s) => s.name.toLowerCase() === options.scenario!.toLowerCase()) ?? null
6014
- : null;
6015
- const infraComputed = computeInfra(infraParsed, selectedScenario ? { scenario: selectedScenario } : {});
6033
+ const infraComputed = computeInfra(infraParsed);
6016
6034
  const infraLayout = layoutInfra(infraComputed);
6035
+ const activeTagGroup = options?.tagGroup ?? null;
6017
6036
 
6018
6037
  const titleOffset = infraParsed.title ? 40 : 0;
6019
6038
  const legendGroups = computeInfraLegendGroups(infraLayout.nodes, infraParsed.tagGroups, effectivePalette);
@@ -6022,7 +6041,7 @@ export async function renderForExport(
6022
6041
  const exportHeight = infraLayout.height + titleOffset + legendOffset;
6023
6042
  const container = createExportContainer(exportWidth, exportHeight);
6024
6043
 
6025
- renderInfra(container, infraLayout, effectivePalette, theme === 'dark', infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, null, false, null, null, true);
6044
+ renderInfra(container, infraLayout, effectivePalette, theme === 'dark', infraParsed.title, infraParsed.titleLineNumber, infraParsed.tagGroups, activeTagGroup, false, null, null, true);
6026
6045
  // Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
6027
6046
  const infraSvg = container.querySelector('svg');
6028
6047
  if (infraSvg) {
@@ -6079,6 +6098,7 @@ export async function renderForExport(
6079
6098
  if (seqParsed.error || seqParsed.participants.length === 0) return '';
6080
6099
  renderSequenceDiagram(container, seqParsed, effectivePalette, isDark, undefined, {
6081
6100
  exportWidth: EXPORT_WIDTH,
6101
+ activeTagGroup: options?.tagGroup,
6082
6102
  });
6083
6103
  } else if (parsed.type === 'wordcloud') {
6084
6104
  await renderWordCloudAsync(container, parsed, effectivePalette, isDark, dims);
@@ -6086,7 +6106,7 @@ export async function renderForExport(
6086
6106
  renderArcDiagram(container, parsed, effectivePalette, isDark, undefined, dims);
6087
6107
  } else if (parsed.type === 'timeline') {
6088
6108
  renderTimeline(container, parsed, effectivePalette, isDark, undefined, dims,
6089
- orgExportState?.activeTagGroup, orgExportState?.swimlaneTagGroup);
6109
+ orgExportState?.activeTagGroup ?? options?.tagGroup, orgExportState?.swimlaneTagGroup);
6090
6110
  } else if (parsed.type === 'venn') {
6091
6111
  renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
6092
6112
  } else if (parsed.type === 'quadrant') {
package/src/echarts.ts CHANGED
@@ -1323,7 +1323,7 @@ function makeGridAxis(
1323
1323
  data?: string[],
1324
1324
  nameGapOverride?: number,
1325
1325
  chartWidthHint?: number,
1326
- intervalOverride?: (index: number, value: string) => boolean
1326
+ intervalOverride?: number
1327
1327
  ): Record<string, unknown> {
1328
1328
  const defaultGap = type === 'value' ? 75 : 40;
1329
1329
 
@@ -1333,13 +1333,17 @@ function makeGridAxis(
1333
1333
  if (type === 'category' && data && data.length > 0) {
1334
1334
  const maxLabelLen = Math.max(...data.map((l) => l.length));
1335
1335
  const count = data.length;
1336
+ // When interval skips labels, base sizing on visible count (≈ count / step)
1337
+ const step = intervalOverride != null && intervalOverride > 0 ? intervalOverride + 1 : 1;
1338
+ const visibleCount = Math.ceil(count / step);
1336
1339
  // Reduce font size based on density and label length
1337
- if (count > 10 || maxLabelLen > 20) catFontSize = 10;
1338
- else if (count > 5 || maxLabelLen > 14) catFontSize = 11;
1340
+ if (visibleCount > 10 || maxLabelLen > 20) catFontSize = 10;
1341
+ else if (visibleCount > 5 || maxLabelLen > 14) catFontSize = 11;
1339
1342
  else if (maxLabelLen > 8) catFontSize = 12;
1340
1343
 
1341
- // Constrain labels to their allotted slot width so ECharts wraps instead of hiding
1342
- if (chartWidthHint && count > 0) {
1344
+ // Constrain labels to their allotted slot width so ECharts wraps instead of hiding.
1345
+ // Skip when interval > 0 — visible labels are spread out and need no constraint.
1346
+ if ((intervalOverride == null || intervalOverride === 0) && chartWidthHint && count > 0) {
1343
1347
  const availPerLabel = Math.floor((chartWidthHint * 0.85) / count);
1344
1348
  catLabelExtras = {
1345
1349
  width: availPerLabel,
@@ -1358,6 +1362,9 @@ function makeGridAxis(
1358
1362
  fontFamily: FONT_FAMILY,
1359
1363
  ...(type === 'category' && {
1360
1364
  interval: intervalOverride ?? 0,
1365
+ // Prevent ECharts auto-rotation: it measures raw slot width (chartWidth/N),
1366
+ // which is too narrow when an interval skips most labels, and rotates to 90°.
1367
+ rotate: 0,
1361
1368
  formatter: (value: string) =>
1362
1369
  value.replace(/([a-z])([A-Z])/g, '$1\n$2'),
1363
1370
  ...catLabelExtras,
@@ -1477,24 +1484,17 @@ function buildBarOption(
1477
1484
 
1478
1485
  // ── Era band helpers ──────────────────────────────────────────
1479
1486
 
1480
- function buildIntervalCallback(
1481
- labels: string[],
1482
- eras: ChartEra[]
1483
- ): (index: number, value: string) => boolean {
1487
+ // Returns an integer interval for ECharts axisLabel.interval.
1488
+ // interval: N means show label at index 0, N+1, 2*(N+1), ...
1489
+ // For a desired step S we return S-1.
1490
+ // Targets ~5 visible labels conservative enough to prevent ECharts stagger.
1491
+ function buildIntervalStep(labels: string[]): number {
1484
1492
  const count = labels.length;
1485
- if (count <= 8) return () => true; // show all; not `0` (ECharts auto ≠ show all)
1486
- const snapSteps = [1, 2, 4, 5, 10, 20, 25, 50];
1487
- const raw = Math.ceil(count / 8);
1493
+ if (count <= 6) return 0; // show all
1494
+ const snapSteps = [1, 2, 5, 10, 25, 50, 100];
1495
+ const raw = Math.ceil(count / 5); // target ~5 visible labels
1488
1496
  const N = [...snapSteps].reverse().find((s) => s <= raw) ?? 1; // snap down
1489
- const pinned = new Set<number>();
1490
- for (let i = 0; i < count; i += N) pinned.add(i);
1491
- for (const era of eras) {
1492
- const si = labels.indexOf(era.start);
1493
- const ei = labels.indexOf(era.end);
1494
- if (si >= 0) pinned.add(si);
1495
- if (ei >= 0) pinned.add(ei);
1496
- }
1497
- return (index: number) => pinned.has(index);
1497
+ return N - 1; // ECharts shows labels at indices 0, N, 2N, ...
1498
1498
  }
1499
1499
 
1500
1500
  function buildMarkArea(
@@ -1548,7 +1548,7 @@ function buildLineOption(
1548
1548
  const labels = parsed.data.map((d) => d.label);
1549
1549
  const values = parsed.data.map((d) => d.value);
1550
1550
  const eras = parsed.eras ?? [];
1551
- const interval = buildIntervalCallback(labels, eras);
1551
+ const interval = buildIntervalStep(labels);
1552
1552
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
1553
1553
 
1554
1554
  return {
@@ -1595,7 +1595,7 @@ function buildMultiLineOption(
1595
1595
  const seriesNames = parsed.seriesNames ?? [];
1596
1596
  const labels = parsed.data.map((d) => d.label);
1597
1597
  const eras = parsed.eras ?? [];
1598
- const interval = buildIntervalCallback(labels, eras);
1598
+ const interval = buildIntervalStep(labels);
1599
1599
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
1600
1600
 
1601
1601
  const series = seriesNames.map((name, idx) => {
@@ -1654,7 +1654,7 @@ function buildAreaOption(
1654
1654
  const labels = parsed.data.map((d) => d.label);
1655
1655
  const values = parsed.data.map((d) => d.value);
1656
1656
  const eras = parsed.eras ?? [];
1657
- const interval = buildIntervalCallback(labels, eras);
1657
+ const interval = buildIntervalStep(labels);
1658
1658
  const markArea = buildMarkArea(eras, labels, textColor, palette.colors.blue);
1659
1659
 
1660
1660
  return {
@@ -9,6 +9,12 @@ import type { PaletteColors } from '../palettes';
9
9
  import { mix } from '../palettes/color-utils';
10
10
  import { getSeriesColors } from '../palettes';
11
11
  import { resolveTagColor } from '../utils/tag-groups';
12
+ import {
13
+ LEGEND_HEIGHT,
14
+ LEGEND_PILL_PAD,
15
+ LEGEND_PILL_FONT_SIZE,
16
+ LEGEND_GROUP_GAP,
17
+ } from '../utils/legend-constants';
12
18
  import type { ParsedERDiagram, ERConstraint } from './types';
13
19
  import type { ERLayoutResult, ERLayoutNode, ERLayoutEdge } from './layout';
14
20
  import { parseERDiagram } from './parser';
@@ -445,17 +451,17 @@ export function renderERDiagram(
445
451
 
446
452
  // ── Tag Legend ──
447
453
  if (parsed.tagGroups.length > 0) {
448
- const LEGEND_Y_PAD = 16;
449
- const LEGEND_PILL_H = 22;
450
- const LEGEND_PILL_RX = 11;
451
- const LEGEND_PILL_PAD = 10;
454
+ const LEGEND_PILL_H = LEGEND_HEIGHT - 6;
455
+ const LEGEND_PILL_RX = Math.floor(LEGEND_PILL_H / 2);
452
456
  const LEGEND_GAP = 8;
453
- const LEGEND_FONT_SIZE = 11;
454
- const LEGEND_GROUP_GAP = 16;
455
457
 
456
458
  const legendG = svg.append('g')
457
459
  .attr('class', 'er-tag-legend');
458
460
 
461
+ if (activeTagGroup) {
462
+ legendG.attr('data-legend-active', activeTagGroup.toLowerCase());
463
+ }
464
+
459
465
  let legendX = DIAGRAM_PADDING;
460
466
  let legendY = height - DIAGRAM_PADDING;
461
467
 
@@ -469,7 +475,7 @@ export function renderERDiagram(
469
475
  .attr('y', legendY + LEGEND_PILL_H / 2)
470
476
  .attr('dominant-baseline', 'central')
471
477
  .attr('fill', palette.textMuted)
472
- .attr('font-size', LEGEND_FONT_SIZE)
478
+ .attr('font-size', LEGEND_PILL_FONT_SIZE)
473
479
  .attr('font-family', FONT_FAMILY)
474
480
  .text(`${group.name}:`);
475
481
 
@@ -484,7 +490,7 @@ export function renderERDiagram(
484
490
 
485
491
  // Estimate text width
486
492
  const tmpText = legendG.append('text')
487
- .attr('font-size', LEGEND_FONT_SIZE)
493
+ .attr('font-size', LEGEND_PILL_FONT_SIZE)
488
494
  .attr('font-family', FONT_FAMILY)
489
495
  .text(entry.value);
490
496
  const textW = tmpText.node()?.getComputedTextLength?.() ?? entry.value.length * 7;
@@ -509,7 +515,7 @@ export function renderERDiagram(
509
515
  .attr('text-anchor', 'middle')
510
516
  .attr('dominant-baseline', 'central')
511
517
  .attr('fill', palette.text)
512
- .attr('font-size', LEGEND_FONT_SIZE)
518
+ .attr('font-size', LEGEND_PILL_FONT_SIZE)
513
519
  .attr('font-family', FONT_FAMILY)
514
520
  .text(entry.value);
515
521
 
package/src/index.ts CHANGED
@@ -246,7 +246,7 @@ export { collapseSitemapTree } from './sitemap/collapse';
246
246
 
247
247
  // ── Infra Chart ────────────────────────────────────────────
248
248
  export { parseInfra } from './infra/parser';
249
- export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic, InfraScenario, InfraComputeParams, InfraBehaviorKey } from './infra/types';
249
+ export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic, InfraComputeParams, InfraBehaviorKey } from './infra/types';
250
250
  export { INFRA_BEHAVIOR_KEYS } from './infra/types';
251
251
  export { computeInfra } from './infra/compute';
252
252
  export type { ComputedInfraModel, ComputedInfraNode, ComputedInfraEdge, InfraLatencyPercentiles, InfraAvailabilityPercentiles, InfraCbState } from './infra/types';
@@ -256,7 +256,7 @@ export type { InfraRole } from './infra/roles';
256
256
  export { layoutInfra } from './infra/layout';
257
257
  export type { InfraLayoutResult, InfraLayoutNode, InfraLayoutEdge, InfraLayoutGroup } from './infra/layout';
258
258
  export { renderInfra, parseAndLayoutInfra, computeInfraLegendGroups } from './infra/renderer';
259
- export type { InfraLegendGroup, InfraPlaybackState } from './infra/renderer';
259
+ export type { InfraLegendGroup } from './infra/renderer';
260
260
  export type { CollapsedSitemapResult } from './sitemap/collapse';
261
261
 
262
262
  export { collapseOrgTree } from './org/collapse';
@@ -604,28 +604,8 @@ export function computeInfra(
604
604
  const defaultLatencyMs = parseFloat(parsed.options['default-latency-ms'] ?? '') || 0;
605
605
  const defaultUptime = parseFloat(parsed.options['default-uptime'] ?? '') || 100;
606
606
 
607
- // Apply scenario overrides (shallow clone nodes with modified properties)
608
- let effectiveNodes = parsed.nodes;
609
- if (params.scenario) {
610
- const overrides = params.scenario.overrides;
611
- effectiveNodes = parsed.nodes.map((node) => {
612
- const nodeOverrides = overrides[node.id];
613
- if (!nodeOverrides) return node;
614
- const props = node.properties.map((p) => {
615
- const ov = nodeOverrides[p.key];
616
- return ov != null ? { ...p, value: ov } : p;
617
- });
618
- // Add new properties from scenario that don't exist on the node
619
- for (const [key, val] of Object.entries(nodeOverrides)) {
620
- if (!props.some((p) => p.key === key)) {
621
- props.push({ key, value: val, lineNumber: node.lineNumber });
622
- }
623
- }
624
- return { ...node, properties: props };
625
- });
626
- }
627
607
  // Apply per-node property overrides (from interactive sliders)
628
- // These take precedence over scenario overrides
608
+ let effectiveNodes = parsed.nodes;
629
609
  if (params.propertyOverrides) {
630
610
  const propOv = params.propertyOverrides;
631
611
  effectiveNodes = effectiveNodes.map((node) => {
@@ -110,7 +110,6 @@ export function parseInfra(content: string): ParsedInfra {
110
110
  edges: [],
111
111
  groups: [],
112
112
  tagGroups: [],
113
- scenarios: [],
114
113
  options: {},
115
114
  diagnostics: [],
116
115
  error: null,
@@ -250,46 +249,20 @@ export function parseInfra(content: string): ParsedInfra {
250
249
  continue;
251
250
  }
252
251
 
253
- // scenario: Name
252
+ // scenario: Name — deprecated, emit warning and skip block
254
253
  if (/^scenario\s*:/i.test(trimmed)) {
255
- finishCurrentNode();
256
- finishCurrentTagGroup();
257
- currentGroup = null;
258
- const scenarioName = trimmed.replace(/^scenario\s*:\s*/i, '').trim();
259
- const scenario: import('./types').InfraScenario = {
260
- name: scenarioName,
261
- overrides: {},
262
- lineNumber,
263
- };
264
- // Parse indented block for scenario overrides
265
- let scenarioNodeId: string | null = null;
254
+ console.warn('[dgmo warn] scenario syntax is deprecated and will be ignored');
255
+ // Skip indented block
266
256
  let si = i + 1;
267
257
  while (si < lines.length) {
268
258
  const sLine = lines[si];
269
259
  const sTrimmed = sLine.trim();
270
260
  if (!sTrimmed || sTrimmed.startsWith('#')) { si++; continue; }
271
261
  const sIndent = sLine.length - sLine.trimStart().length;
272
- if (sIndent === 0) break; // back to top level
273
-
274
- if (sIndent <= 2) {
275
- // Node reference (e.g., " edge" or " API")
276
- scenarioNodeId = nodeId(sTrimmed.replace(/\|.*$/, '').trim());
277
- if (!scenario.overrides[scenarioNodeId]) {
278
- scenario.overrides[scenarioNodeId] = {};
279
- }
280
- } else if (scenarioNodeId) {
281
- // Property override (e.g., " rps: 10000")
282
- const pm = sTrimmed.match(PROPERTY_RE);
283
- if (pm) {
284
- const key = pm[1].toLowerCase();
285
- const val = parsePropertyValue(pm[2].trim());
286
- scenario.overrides[scenarioNodeId][key] = val;
287
- }
288
- }
262
+ if (sIndent === 0) break;
289
263
  si++;
290
264
  }
291
- i = si - 1; // advance past scenario block
292
- result.scenarios.push(scenario);
265
+ i = si - 1;
293
266
  continue;
294
267
  }
295
268