@diagrammo/dgmo 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude/commands/dgmo.md +231 -13
  2. package/AGENTS.md +148 -0
  3. package/dist/cli.cjs +327 -153
  4. package/dist/index.cjs +305 -177
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +24 -3
  7. package/dist/index.d.ts +24 -3
  8. package/dist/index.js +303 -177
  9. package/dist/index.js.map +1 -1
  10. package/package.json +5 -3
  11. package/src/c4/layout.ts +0 -5
  12. package/src/c4/parser.ts +0 -16
  13. package/src/c4/renderer.ts +1 -5
  14. package/src/class/layout.ts +0 -1
  15. package/src/class/parser.ts +28 -0
  16. package/src/class/renderer.ts +5 -26
  17. package/src/cli.ts +563 -14
  18. package/src/completion.ts +58 -0
  19. package/src/d3.ts +58 -106
  20. package/src/dgmo-router.ts +0 -57
  21. package/src/echarts.ts +96 -55
  22. package/src/er/parser.ts +30 -1
  23. package/src/er/renderer.ts +1 -2
  24. package/src/graph/flowchart-parser.ts +27 -4
  25. package/src/graph/flowchart-renderer.ts +1 -2
  26. package/src/graph/state-parser.ts +0 -1
  27. package/src/graph/state-renderer.ts +1 -3
  28. package/src/index.ts +10 -0
  29. package/src/infra/compute.ts +0 -7
  30. package/src/infra/layout.ts +0 -2
  31. package/src/infra/parser.ts +46 -4
  32. package/src/infra/renderer.ts +1 -15
  33. package/src/initiative-status/renderer.ts +5 -25
  34. package/src/kanban/parser.ts +0 -2
  35. package/src/org/layout.ts +0 -4
  36. package/src/org/renderer.ts +7 -28
  37. package/src/sequence/parser.ts +14 -11
  38. package/src/sequence/renderer.ts +0 -2
  39. package/src/sequence/tag-resolution.ts +0 -1
  40. package/src/sitemap/layout.ts +1 -14
  41. package/src/sitemap/parser.ts +1 -2
  42. package/src/sitemap/renderer.ts +0 -3
  43. package/src/utils/arrows.ts +7 -7
  44. package/src/utils/export-container.ts +40 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Diagram symbol extraction API.
3
+ *
4
+ * Provides DiagramSymbols interface + extractDiagramSymbols() dispatch.
5
+ * Each diagram type registers its own extractor via registerExtractor().
6
+ * All built-in extractors are registered at module init below.
7
+ */
8
+
9
+ import { extractSymbols as extractErSymbols } from './er/parser';
10
+ import { extractSymbols as extractFlowchartSymbols } from './graph/flowchart-parser';
11
+ import { extractSymbols as extractInfraSymbols } from './infra/parser';
12
+ import { extractSymbols as extractClassSymbols } from './class/parser';
13
+
14
+ // ChartType is just a string — alias here for documentation clarity.
15
+ export type ChartType = string;
16
+
17
+ export interface DiagramSymbols {
18
+ kind: ChartType;
19
+ entities: string[]; // table names, node IDs, class names, etc.
20
+ keywords: string[]; // diagram-specific reserved words
21
+ }
22
+
23
+ export type ExtractFn = (docText: string) => DiagramSymbols;
24
+
25
+ const registry = new Map<ChartType, ExtractFn>();
26
+
27
+ export function registerExtractor(kind: ChartType, fn: ExtractFn): void {
28
+ registry.set(kind, fn);
29
+ }
30
+
31
+ /**
32
+ * Extract diagram symbols from document text.
33
+ * Returns null if the chart type is unknown or has no registered extractor.
34
+ */
35
+ export function extractDiagramSymbols(docText: string): DiagramSymbols | null {
36
+ // Parse chartType from first `chart:` line — lightweight, no full parser.
37
+ let chartType: string | null = null;
38
+ for (const line of docText.split('\n')) {
39
+ const m = line.match(/^\s*chart\s*:\s*(.+)/i);
40
+ if (m) {
41
+ chartType = m[1]!.trim().toLowerCase();
42
+ break;
43
+ }
44
+ }
45
+ if (!chartType) return null;
46
+ const fn = registry.get(chartType);
47
+ if (!fn) return null;
48
+ return fn(docText);
49
+ }
50
+
51
+ // ============================================================
52
+ // Register built-in extractors
53
+ // ============================================================
54
+
55
+ registerExtractor('er', extractErSymbols);
56
+ registerExtractor('flowchart', extractFlowchartSymbols);
57
+ registerExtractor('infra', extractInfraSymbols);
58
+ registerExtractor('class', extractClassSymbols);
package/src/d3.ts CHANGED
@@ -1861,7 +1861,6 @@ export function renderArcDiagram(
1861
1861
  const positions = groupNodes.map((n) => yScale(n)!);
1862
1862
  const minY = Math.min(...positions) - bandPad;
1863
1863
  const maxY = Math.max(...positions) + bandPad;
1864
- const bandColor = group.color ?? mutedColor;
1865
1864
 
1866
1865
  g.append('rect')
1867
1866
  .attr('class', 'arc-group-band')
@@ -1996,7 +1995,6 @@ export function renderArcDiagram(
1996
1995
  const positions = groupNodes.map((n) => xScale(n)!);
1997
1996
  const minX = Math.min(...positions) - bandPad;
1998
1997
  const maxX = Math.max(...positions) + bandPad;
1999
- const bandColor = group.color ?? mutedColor;
2000
1998
 
2001
1999
  g.append('rect')
2002
2000
  .attr('class', 'arc-group-band')
@@ -2864,6 +2862,7 @@ export function renderTimeline(
2864
2862
  const textColor = palette.text;
2865
2863
  const mutedColor = palette.border;
2866
2864
  const bgColor = palette.bg;
2865
+ const bg = isDark ? palette.surface : palette.bg;
2867
2866
  const colors = getSeriesColors(palette);
2868
2867
 
2869
2868
  // Assign colors to groups
@@ -3254,7 +3253,7 @@ export function renderTimeline(
3254
3253
  const y2 = yScale(parseTimelineDate(ev.endDate));
3255
3254
  const rectH = Math.max(y2 - y, 4);
3256
3255
 
3257
- let fill: string = evColor;
3256
+ let fill: string = mix(evColor, bg, 30);
3258
3257
  if (ev.uncertain) {
3259
3258
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
3260
3259
  const defs =
@@ -3276,7 +3275,7 @@ export function renderTimeline(
3276
3275
  .enter()
3277
3276
  .append('stop')
3278
3277
  .attr('offset', (d) => d.offset)
3279
- .attr('stop-color', laneColor)
3278
+ .attr('stop-color', mix(laneColor, bg, 30))
3280
3279
  .attr('stop-opacity', (d) => d.opacity);
3281
3280
  fill = `url(#${gradientId})`;
3282
3281
  }
@@ -3288,7 +3287,9 @@ export function renderTimeline(
3288
3287
  .attr('width', 12)
3289
3288
  .attr('height', rectH)
3290
3289
  .attr('rx', 4)
3291
- .attr('fill', fill);
3290
+ .attr('fill', fill)
3291
+ .attr('stroke', evColor)
3292
+ .attr('stroke-width', 2);
3292
3293
  evG
3293
3294
  .append('text')
3294
3295
  .attr('x', laneCenter + 14)
@@ -3303,9 +3304,9 @@ export function renderTimeline(
3303
3304
  .attr('cx', laneCenter)
3304
3305
  .attr('cy', y)
3305
3306
  .attr('r', 4)
3306
- .attr('fill', evColor)
3307
- .attr('stroke', bgColor)
3308
- .attr('stroke-width', 1.5);
3307
+ .attr('fill', mix(evColor, bg, 30))
3308
+ .attr('stroke', evColor)
3309
+ .attr('stroke-width', 2);
3309
3310
  evG
3310
3311
  .append('text')
3311
3312
  .attr('x', laneCenter + 10)
@@ -3472,7 +3473,7 @@ export function renderTimeline(
3472
3473
  const y2 = yScale(parseTimelineDate(ev.endDate));
3473
3474
  const rectH = Math.max(y2 - y, 4);
3474
3475
 
3475
- let fill: string = color;
3476
+ let fill: string = mix(color, bg, 30);
3476
3477
  if (ev.uncertain) {
3477
3478
  const gradientId = `uncertain-v-${ev.lineNumber}`;
3478
3479
  const defs =
@@ -3494,7 +3495,7 @@ export function renderTimeline(
3494
3495
  .enter()
3495
3496
  .append('stop')
3496
3497
  .attr('offset', (d) => d.offset)
3497
- .attr('stop-color', color)
3498
+ .attr('stop-color', mix(color, bg, 30))
3498
3499
  .attr('stop-opacity', (d) => d.opacity);
3499
3500
  fill = `url(#${gradientId})`;
3500
3501
  }
@@ -3506,7 +3507,9 @@ export function renderTimeline(
3506
3507
  .attr('width', 12)
3507
3508
  .attr('height', rectH)
3508
3509
  .attr('rx', 4)
3509
- .attr('fill', fill);
3510
+ .attr('fill', fill)
3511
+ .attr('stroke', color)
3512
+ .attr('stroke-width', 2);
3510
3513
  evG
3511
3514
  .append('text')
3512
3515
  .attr('x', axisX + 16)
@@ -3521,9 +3524,9 @@ export function renderTimeline(
3521
3524
  .attr('cx', axisX)
3522
3525
  .attr('cy', y)
3523
3526
  .attr('r', 4)
3524
- .attr('fill', color)
3525
- .attr('stroke', bgColor)
3526
- .attr('stroke-width', 1.5);
3527
+ .attr('fill', mix(color, bg, 30))
3528
+ .attr('stroke', color)
3529
+ .attr('stroke-width', 2);
3527
3530
  evG
3528
3531
  .append('text')
3529
3532
  .attr('x', axisX + 16)
@@ -3781,7 +3784,7 @@ export function renderTimeline(
3781
3784
  const estLabelWidth = ev.label.length * 7 + 16;
3782
3785
  const labelFitsInside = rectW >= estLabelWidth;
3783
3786
 
3784
- let fill: string = evColor;
3787
+ let fill: string = mix(evColor, bg, 30);
3785
3788
  if (ev.uncertain) {
3786
3789
  // Create gradient for uncertain end - fades last 20%
3787
3790
  const gradientId = `uncertain-${ev.lineNumber}`;
@@ -3803,7 +3806,7 @@ export function renderTimeline(
3803
3806
  .enter()
3804
3807
  .append('stop')
3805
3808
  .attr('offset', (d) => d.offset)
3806
- .attr('stop-color', evColor)
3809
+ .attr('stop-color', mix(evColor, bg, 30))
3807
3810
  .attr('stop-opacity', (d) => d.opacity);
3808
3811
  fill = `url(#${gradientId})`;
3809
3812
  }
@@ -3815,17 +3818,19 @@ export function renderTimeline(
3815
3818
  .attr('width', rectW)
3816
3819
  .attr('height', BAR_H)
3817
3820
  .attr('rx', 4)
3818
- .attr('fill', fill);
3821
+ .attr('fill', fill)
3822
+ .attr('stroke', evColor)
3823
+ .attr('stroke-width', 2);
3819
3824
 
3820
3825
  if (labelFitsInside) {
3821
- // Text inside bar - always white for readability
3826
+ // Text inside bar - use textColor for readability on muted fill
3822
3827
  evG
3823
3828
  .append('text')
3824
3829
  .attr('x', x + 8)
3825
3830
  .attr('y', y)
3826
3831
  .attr('dy', '0.35em')
3827
3832
  .attr('text-anchor', 'start')
3828
- .attr('fill', '#ffffff')
3833
+ .attr('fill', textColor)
3829
3834
  .attr('font-size', '14px')
3830
3835
  .attr('font-weight', '700')
3831
3836
  .text(ev.label);
@@ -3856,9 +3861,9 @@ export function renderTimeline(
3856
3861
  .attr('cx', x)
3857
3862
  .attr('cy', y)
3858
3863
  .attr('r', 5)
3859
- .attr('fill', evColor)
3860
- .attr('stroke', bgColor)
3861
- .attr('stroke-width', 1.5);
3864
+ .attr('fill', mix(evColor, bg, 30))
3865
+ .attr('stroke', evColor)
3866
+ .attr('stroke-width', 2);
3862
3867
  evG
3863
3868
  .append('text')
3864
3869
  .attr('x', flipLeft ? x - 10 : x + 10)
@@ -4041,7 +4046,7 @@ export function renderTimeline(
4041
4046
  const estLabelWidth = ev.label.length * 7 + 16;
4042
4047
  const labelFitsInside = rectW >= estLabelWidth;
4043
4048
 
4044
- let fill: string = color;
4049
+ let fill: string = mix(color, bg, 30);
4045
4050
  if (ev.uncertain) {
4046
4051
  // Create gradient for uncertain end - fades last 20%
4047
4052
  const gradientId = `uncertain-ts-${ev.lineNumber}`;
@@ -4063,7 +4068,7 @@ export function renderTimeline(
4063
4068
  .enter()
4064
4069
  .append('stop')
4065
4070
  .attr('offset', (d) => d.offset)
4066
- .attr('stop-color', color)
4071
+ .attr('stop-color', mix(color, bg, 30))
4067
4072
  .attr('stop-opacity', (d) => d.opacity);
4068
4073
  fill = `url(#${gradientId})`;
4069
4074
  }
@@ -4075,17 +4080,19 @@ export function renderTimeline(
4075
4080
  .attr('width', rectW)
4076
4081
  .attr('height', BAR_H)
4077
4082
  .attr('rx', 4)
4078
- .attr('fill', fill);
4083
+ .attr('fill', fill)
4084
+ .attr('stroke', color)
4085
+ .attr('stroke-width', 2);
4079
4086
 
4080
4087
  if (labelFitsInside) {
4081
- // Text inside bar - always white for readability
4088
+ // Text inside bar - use textColor for readability on muted fill
4082
4089
  evG
4083
4090
  .append('text')
4084
4091
  .attr('x', x + 8)
4085
4092
  .attr('y', y)
4086
4093
  .attr('dy', '0.35em')
4087
4094
  .attr('text-anchor', 'start')
4088
- .attr('fill', '#ffffff')
4095
+ .attr('fill', textColor)
4089
4096
  .attr('font-size', '14px')
4090
4097
  .attr('font-weight', '700')
4091
4098
  .text(ev.label);
@@ -4116,9 +4123,9 @@ export function renderTimeline(
4116
4123
  .attr('cx', x)
4117
4124
  .attr('cy', y)
4118
4125
  .attr('r', 5)
4119
- .attr('fill', color)
4120
- .attr('stroke', bgColor)
4121
- .attr('stroke-width', 1.5);
4126
+ .attr('fill', mix(color, bg, 30))
4127
+ .attr('stroke', color)
4128
+ .attr('stroke-width', 2);
4122
4129
  evG
4123
4130
  .append('text')
4124
4131
  .attr('x', flipLeft ? x - 10 : x + 10)
@@ -4438,8 +4445,8 @@ export function renderTimeline(
4438
4445
  color = ev.group && groupColorMap.has(ev.group)
4439
4446
  ? groupColorMap.get(ev.group)! : textColor;
4440
4447
  }
4441
- el.selectAll('rect').attr('fill', color);
4442
- el.selectAll('circle:not(.tl-event-point-outline)').attr('fill', color);
4448
+ el.selectAll('rect').attr('fill', mix(color, bg, 30)).attr('stroke', color);
4449
+ el.selectAll('circle:not(.tl-event-point-outline)').attr('fill', mix(color, bg, 30)).attr('stroke', color);
4443
4450
  });
4444
4451
  }
4445
4452
 
@@ -4635,46 +4642,6 @@ function renderWordCloudAsync(
4635
4642
  // Venn Diagram Math Helpers
4636
4643
  // ============================================================
4637
4644
 
4638
- function radiusFromArea(area: number): number {
4639
- return Math.sqrt(area / Math.PI);
4640
- }
4641
-
4642
- function circleOverlapArea(r1: number, r2: number, d: number): number {
4643
- // No overlap
4644
- if (d >= r1 + r2) return 0;
4645
- // Full containment
4646
- if (d + Math.min(r1, r2) <= Math.max(r1, r2)) {
4647
- return Math.PI * Math.min(r1, r2) ** 2;
4648
- }
4649
- const part1 = r1 * r1 * Math.acos((d * d + r1 * r1 - r2 * r2) / (2 * d * r1));
4650
- const part2 = r2 * r2 * Math.acos((d * d + r2 * r2 - r1 * r1) / (2 * d * r2));
4651
- const part3 =
4652
- 0.5 *
4653
- Math.sqrt((-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2));
4654
- return part1 + part2 - part3;
4655
- }
4656
-
4657
- function distanceForOverlap(
4658
- r1: number,
4659
- r2: number,
4660
- targetArea: number
4661
- ): number {
4662
- if (targetArea <= 0) return r1 + r2;
4663
- const minR = Math.min(r1, r2);
4664
- if (targetArea >= Math.PI * minR * minR) return Math.abs(r1 - r2);
4665
- let lo = Math.abs(r1 - r2);
4666
- let hi = r1 + r2;
4667
- for (let i = 0; i < 64; i++) {
4668
- const mid = (lo + hi) / 2;
4669
- if (circleOverlapArea(r1, r2, mid) > targetArea) {
4670
- lo = mid;
4671
- } else {
4672
- hi = mid;
4673
- }
4674
- }
4675
- return (lo + hi) / 2;
4676
- }
4677
-
4678
4645
  interface Point {
4679
4646
  x: number;
4680
4647
  y: number;
@@ -4686,29 +4653,6 @@ interface Circle {
4686
4653
  r: number;
4687
4654
  }
4688
4655
 
4689
- function thirdCirclePosition(
4690
- ax: number,
4691
- ay: number,
4692
- dAC: number,
4693
- bx: number,
4694
- by: number,
4695
- dBC: number
4696
- ): Point {
4697
- const dx = bx - ax;
4698
- const dy = by - ay;
4699
- const dAB = Math.sqrt(dx * dx + dy * dy);
4700
- if (dAB === 0) return { x: ax + dAC, y: ay };
4701
- const cosA = (dAB * dAB + dAC * dAC - dBC * dBC) / (2 * dAB * dAC);
4702
- const sinA = Math.sqrt(Math.max(0, 1 - cosA * cosA));
4703
- const ux = dx / dAB;
4704
- const uy = dy / dAB;
4705
- // Place C above the AB line
4706
- return {
4707
- x: ax + dAC * (cosA * ux - sinA * uy),
4708
- y: ay + dAC * (cosA * uy + sinA * ux),
4709
- };
4710
- }
4711
-
4712
4656
  function fitCirclesToContainerAsymmetric(
4713
4657
  circles: Circle[],
4714
4658
  w: number,
@@ -5255,7 +5199,6 @@ export function renderQuadrant(
5255
5199
  const init = initD3Chart(container, palette, exportDims);
5256
5200
  if (!init) return;
5257
5201
  const { svg, width, height, textColor } = init;
5258
- const mutedColor = palette.textMuted;
5259
5202
  const borderColor = palette.border;
5260
5203
 
5261
5204
  // Default quadrant colors with alpha
@@ -5300,14 +5243,24 @@ export function renderQuadrant(
5300
5243
  return `#${c(ar,br)}${c(ag,bg)}${c(ab,bb)}`;
5301
5244
  };
5302
5245
 
5303
- // Opaque quadrant fills using the assigned color directly
5304
- const getQuadrantFill = (
5246
+ const bg = isDark ? palette.surface : palette.bg;
5247
+
5248
+ // Full palette color for a quadrant (used for border and label tinting)
5249
+ const getQuadrantColor = (
5305
5250
  label: QuadrantLabel | null,
5306
5251
  defaultIdx: number
5307
5252
  ): string => {
5308
5253
  return label?.color ?? defaultColors[defaultIdx % defaultColors.length];
5309
5254
  };
5310
5255
 
5256
+ // Muted fill: palette color blended 30% toward bg — matches other chart fill style
5257
+ const getQuadrantFill = (
5258
+ label: QuadrantLabel | null,
5259
+ defaultIdx: number
5260
+ ): string => {
5261
+ return mixHex(getQuadrantColor(label, defaultIdx), bg, 30);
5262
+ };
5263
+
5311
5264
  // Quadrant definitions: position, rect bounds, label position
5312
5265
  const quadrantDefs: {
5313
5266
  position: QuadrantPosition;
@@ -5378,17 +5331,16 @@ export function renderQuadrant(
5378
5331
  .attr('width', (d) => d.w)
5379
5332
  .attr('height', (d) => d.h)
5380
5333
  .attr('fill', (d) => getQuadrantFill(d.label, d.colorIdx))
5381
- .attr('stroke', borderColor)
5382
- .attr('stroke-width', 0.5);
5334
+ .attr('stroke', (d) => getQuadrantColor(d.label, d.colorIdx))
5335
+ .attr('stroke-width', 2);
5383
5336
 
5384
5337
  // White text for points; quadrant labels use a darkened shade of their fill
5385
- const contrastColor = '#ffffff';
5386
5338
  const shadowColor = 'rgba(0,0,0,0.4)';
5387
5339
 
5388
- // Darken the quadrant fill to create a watermark-style label color
5340
+ // Darken the full palette color (not the muted fill) to create a watermark-style label
5389
5341
  const getQuadrantLabelColor = (d: (typeof quadrantDefs)[number]): string => {
5390
- const fill = getQuadrantFill(d.label, d.colorIdx);
5391
- return mixHex('#000000', fill, 40);
5342
+ const color = getQuadrantColor(d.label, d.colorIdx);
5343
+ return mixHex('#000000', color, 40);
5392
5344
  };
5393
5345
 
5394
5346
  // Scale label font size to fit within quadrant bounds, wrapping into multiple lines if needed
@@ -5666,13 +5618,13 @@ export function renderQuadrant(
5666
5618
  .attr('stroke', pointColor)
5667
5619
  .attr('stroke-width', 2);
5668
5620
 
5669
- // Label (contrasting color with shadow for readability)
5621
+ // Label (palette text color adapts to light/dark mode)
5670
5622
  pointG
5671
5623
  .append('text')
5672
5624
  .attr('x', cx)
5673
5625
  .attr('y', cy - 10)
5674
5626
  .attr('text-anchor', 'middle')
5675
- .attr('fill', contrastColor)
5627
+ .attr('fill', textColor)
5676
5628
  .attr('font-size', '12px')
5677
5629
  .attr('font-weight', '700')
5678
5630
  .style('text-shadow', `0 1px 2px ${shadowColor}`)
@@ -18,63 +18,6 @@ import { looksLikeSitemap, parseSitemap } from './sitemap/parser';
18
18
  import { parseInfra } from './infra/parser';
19
19
  import type { DgmoError } from './diagnostics';
20
20
 
21
- /**
22
- * Framework identifiers used by the .dgmo router internally.
23
- * Not part of the public API — use RenderCategory instead.
24
- */
25
- type DgmoFramework = 'echart' | 'd3' | 'mermaid';
26
-
27
- /**
28
- * Maps every supported chart type string to its backing framework (internal).
29
- */
30
- const DGMO_CHART_TYPE_MAP: Record<string, DgmoFramework> = {
31
- // Standard charts (via ECharts)
32
- bar: 'echart',
33
- line: 'echart',
34
- 'multi-line': 'echart',
35
- area: 'echart',
36
- pie: 'echart',
37
- doughnut: 'echart',
38
- radar: 'echart',
39
- 'polar-area': 'echart',
40
- 'bar-stacked': 'echart',
41
-
42
- // ECharts
43
- scatter: 'echart',
44
- sankey: 'echart',
45
- chord: 'echart',
46
- function: 'echart',
47
- heatmap: 'echart',
48
- funnel: 'echart',
49
-
50
- // D3
51
- slope: 'd3',
52
- wordcloud: 'd3',
53
- arc: 'd3',
54
- timeline: 'd3',
55
- venn: 'd3',
56
- quadrant: 'd3',
57
- sequence: 'd3',
58
- flowchart: 'd3',
59
- class: 'd3',
60
- er: 'd3',
61
- org: 'd3',
62
- kanban: 'd3',
63
- c4: 'd3',
64
- 'initiative-status': 'd3',
65
- state: 'd3',
66
- sitemap: 'd3',
67
- infra: 'd3',
68
- };
69
-
70
- /**
71
- * Returns the internal framework for a given chart type, or `null` if unknown.
72
- * Internal only — use getRenderCategory() for public dispatch.
73
- */
74
- function getDgmoFramework(chartType: string): DgmoFramework | null {
75
- return DGMO_CHART_TYPE_MAP[chartType.toLowerCase()] ?? null;
76
- }
77
-
78
21
  /**
79
22
  * Extracts the `chart:` type value from raw file content.
80
23
  * Falls back to inference when no explicit `chart:` line is found