@diagrammo/dgmo 0.8.22 → 0.8.23

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 (53) hide show
  1. package/dist/cli.cjs +111 -109
  2. package/dist/editor.cjs +3 -0
  3. package/dist/editor.cjs.map +1 -1
  4. package/dist/editor.js +3 -0
  5. package/dist/editor.js.map +1 -1
  6. package/dist/highlight.cjs +3 -0
  7. package/dist/highlight.cjs.map +1 -1
  8. package/dist/highlight.js +3 -0
  9. package/dist/highlight.js.map +1 -1
  10. package/dist/index.cjs +1010 -215
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +97 -11
  13. package/dist/index.d.ts +97 -11
  14. package/dist/index.js +1001 -213
  15. package/dist/index.js.map +1 -1
  16. package/dist/internal.cjs +380 -0
  17. package/dist/internal.cjs.map +1 -0
  18. package/dist/internal.d.cts +179 -0
  19. package/dist/internal.d.ts +179 -0
  20. package/dist/internal.js +337 -0
  21. package/dist/internal.js.map +1 -0
  22. package/docs/guide/chart-cycle.md +156 -0
  23. package/docs/guide/chart-journey-map.md +179 -0
  24. package/docs/guide/chart-pyramid.md +111 -0
  25. package/docs/guide/registry.json +5 -0
  26. package/docs/language-reference.md +62 -1
  27. package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
  28. package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
  29. package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
  30. package/package.json +11 -1
  31. package/src/cli.ts +5 -35
  32. package/src/completion.ts +9 -44
  33. package/src/cycle/layout.ts +19 -28
  34. package/src/cycle/renderer.ts +59 -32
  35. package/src/cycle/types.ts +21 -0
  36. package/src/d3.ts +21 -1
  37. package/src/dgmo-router.ts +73 -3
  38. package/src/echarts.ts +1 -1
  39. package/src/editor/keywords.ts +3 -0
  40. package/src/index.ts +13 -2
  41. package/src/infra/parser.ts +2 -2
  42. package/src/internal.ts +16 -0
  43. package/src/journey-map/renderer.ts +112 -47
  44. package/src/org/collapse.ts +81 -0
  45. package/src/org/renderer.ts +212 -4
  46. package/src/pyramid/parser.ts +172 -0
  47. package/src/pyramid/renderer.ts +684 -0
  48. package/src/pyramid/types.ts +28 -0
  49. package/src/render.ts +2 -8
  50. package/src/sequence/parser.ts +62 -20
  51. package/src/sequence/renderer.ts +2 -2
  52. package/src/tech-radar/interactive.ts +54 -0
  53. package/src/utils/parsing.ts +1 -0
package/src/completion.ts CHANGED
@@ -16,6 +16,7 @@ import { extractSymbols as extractFlowchartSymbols } from './graph/flowchart-par
16
16
  import { extractSymbols as extractInfraSymbols } from './infra/parser';
17
17
  import { extractSymbols as extractClassSymbols } from './class/parser';
18
18
  import { parseFirstLine, ALL_CHART_TYPES } from './utils/parsing';
19
+ import { CHART_TYPE_DESCRIPTIONS } from './dgmo-router';
19
20
 
20
21
  // ============================================================
21
22
  // Symbol extraction
@@ -381,56 +382,20 @@ export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
381
382
  persona: { description: 'Define the journey persona' },
382
383
  }),
383
384
  ],
385
+ [
386
+ 'pyramid',
387
+ withGlobals({
388
+ inverted: { description: 'Flip apex to the bottom (funnel orientation)' },
389
+ color: { description: 'Override layer color (pipe metadata)' },
390
+ description: { description: 'Layer description (pipe or indented body)' },
391
+ }),
392
+ ],
384
393
  ]);
385
394
 
386
395
  // ============================================================
387
396
  // Chart types array (for chart type completion popup)
388
397
  // ============================================================
389
398
 
390
- const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
391
- // Data charts
392
- bar: 'Bar chart',
393
- line: 'Line chart',
394
- pie: 'Pie chart',
395
- doughnut: 'Doughnut chart',
396
- area: 'Area chart',
397
- 'polar-area': 'Polar area chart',
398
- radar: 'Radar chart',
399
- 'bar-stacked': 'Stacked bar chart',
400
- // Extended charts
401
- scatter: 'Scatter plot',
402
- heatmap: 'Heatmap',
403
- sankey: 'Sankey flow diagram',
404
- chord: 'Chord diagram',
405
- funnel: 'Funnel chart',
406
- function: 'Mathematical function plot',
407
- // Visualizations
408
- slope: 'Slope chart',
409
- wordcloud: 'Word cloud',
410
- arc: 'Arc diagram',
411
- timeline: 'Timeline',
412
- venn: 'Venn diagram',
413
- quadrant: 'Quadrant chart',
414
- // Diagrams
415
- sequence: 'Sequence diagram',
416
- flowchart: 'Flowchart',
417
- class: 'Class diagram',
418
- er: 'Entity-relationship diagram',
419
- org: 'Organization chart',
420
- kanban: 'Kanban board',
421
- c4: 'C4 architecture diagram',
422
- state: 'State diagram',
423
- sitemap: 'Sitemap diagram',
424
- infra: 'Infrastructure diagram',
425
- gantt: 'Gantt chart',
426
- 'boxes-and-lines': 'Boxes and lines diagram',
427
- mindmap: 'Mindmap diagram',
428
- wireframe: 'UI wireframe diagram',
429
- 'tech-radar': 'Technology adoption radar (ThoughtWorks style)',
430
- cycle: 'Cycle diagram (circular process flow)',
431
- 'journey-map': 'User journey map with emotion curve',
432
- };
433
-
434
399
  /** All chart types with descriptions, for chart type autocomplete. Excludes `multi-line` alias. */
435
400
  export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
436
401
  [...ALL_CHART_TYPES]
@@ -2,11 +2,14 @@
2
2
  // Cycle Diagram — Layout Engine
3
3
  // ============================================================
4
4
 
5
- import type {
6
- ParsedCycle,
7
- CycleLayoutNode,
8
- CycleLayoutEdge,
9
- CycleLayoutResult,
5
+ import {
6
+ DEFAULT_EDGE_WIDTH,
7
+ MIN_EDGE_WIDTH,
8
+ arrowHeadLength,
9
+ type ParsedCycle,
10
+ type CycleLayoutNode,
11
+ type CycleLayoutEdge,
12
+ type CycleLayoutResult,
10
13
  } from './types';
11
14
 
12
15
  /** Minimum arc angle in radians (~15°) to keep arcs readable. */
@@ -519,11 +522,6 @@ function circleRectExitAngle(
519
522
  return (insideAngle + outsideAngle) / 2;
520
523
  }
521
524
 
522
- /** Default edge stroke width (must match renderer). */
523
- const DEFAULT_EDGE_WIDTH = 3;
524
- /** Arrowhead marker width in stroke-width units (must match renderer). */
525
- const ARROWHEAD_MARKER_W = 8;
526
-
527
525
  /** Compute edge paths for all edges in the parsed diagram. */
528
526
  function computeEdgePaths(
529
527
  layoutNodes: CycleLayoutNode[],
@@ -536,9 +534,13 @@ function computeEdgePaths(
536
534
  return parsed.edges.map((edge) => {
537
535
  const src = layoutNodes[edge.sourceIndex];
538
536
  const tgt = layoutNodes[edge.targetIndex];
539
- const strokeWidth = edge.width ?? DEFAULT_EDGE_WIDTH;
540
- // Arrowhead rendered length in pixels (markerUnits = strokeWidth)
541
- const arrowLen = ARROWHEAD_MARKER_W * strokeWidth;
537
+ const strokeWidth = Math.max(
538
+ edge.width ?? DEFAULT_EDGE_WIDTH,
539
+ MIN_EDGE_WIDTH
540
+ );
541
+ // Arrowhead effective reach: full length minus the 10% overlap that
542
+ // slides the marker back to cover the stroke/arrowhead junction line.
543
+ const arrowLen = arrowHeadLength(strokeWidth) * 0.9;
542
544
  const { path, labelX, labelY, labelAngle } = buildEdgeArc(
543
545
  src,
544
546
  tgt,
@@ -578,7 +580,7 @@ function fitToCanvas(
578
580
  height: number,
579
581
  _isClockwise: boolean
580
582
  ): { radius: number } | null {
581
- const PADDING = 10;
583
+ const PADDING = 30;
582
584
  let contentMinX = Infinity,
583
585
  contentMaxX = -Infinity;
584
586
  let contentMinY = Infinity,
@@ -672,20 +674,9 @@ function buildEdgeArc(
672
674
  ): { path: string; labelX: number; labelY: number; labelAngle: number } {
673
675
  const dir = isClockwise ? 1 : -1;
674
676
 
675
- // Find where the cycle circle exits the source node
676
- const startAngle = src.isCircle
677
- ? circleNodeExitAngle(src.width / 2, radius, src.angle, dir)
678
- : circleRectExitAngle(
679
- src.x,
680
- src.y,
681
- src.width / 2,
682
- src.height / 2,
683
- cx,
684
- cy,
685
- radius,
686
- src.angle,
687
- dir
688
- );
677
+ // Start arc from the source node's center angle — the node renders on top
678
+ // of the edge, so the overlap is hidden and there's no visible gap.
679
+ const startAngle = src.angle;
689
680
 
690
681
  // Find where the cycle circle exits the target node
691
682
  const nodeEndAngle = tgt.isCircle
@@ -23,13 +23,15 @@ import { renderInlineText } from '../utils/inline-markdown';
23
23
  import type { PaletteColors } from '../palettes';
24
24
  import type { D3ExportDimensions } from '../utils/d3-types';
25
25
  import type { CompactViewState } from '../sharing';
26
- import type { ParsedCycle } from './types';
26
+ import {
27
+ DEFAULT_EDGE_WIDTH,
28
+ MIN_EDGE_WIDTH,
29
+ arrowHeadLength,
30
+ type ParsedCycle,
31
+ } from './types';
27
32
  import { computeCycleLayout } from './layout';
28
33
 
29
34
  // ── Constants ────────────────────────────────────────────────
30
- const DEFAULT_EDGE_WIDTH = 3;
31
- const ARROWHEAD_W = 8;
32
- const ARROWHEAD_H = 8;
33
35
  const NODE_FONT_SIZE = 13;
34
36
  const DESC_FONT_SIZE = 11;
35
37
  const EDGE_LABEL_FONT_SIZE = 11;
@@ -183,22 +185,28 @@ export function renderCycle(
183
185
  // Resolve default node color: first palette color (uniform)
184
186
  const defaultNodeColor = palette.primary;
185
187
 
186
- // ── Arrowhead markers ──
187
- const arrowColors = new Set<string>();
188
- for (let i = 0; i < parsed.edges.length; i++) {
189
- const edge = parsed.edges[i];
188
+ // ── Arrowhead markers (per color+width, markerUnits=strokeWidth) ──
189
+ const markerKeys = new Set<string>();
190
+ for (const edge of parsed.edges) {
190
191
  const color = resolveEdgeColor(edge, parsed, palette, defaultNodeColor);
191
- arrowColors.add(color);
192
+ const sw = Math.max(edge.width ?? DEFAULT_EDGE_WIDTH, MIN_EDGE_WIDTH);
193
+ const key = `${color}|${sw}`;
194
+ if (!markerKeys.has(key)) {
195
+ markerKeys.add(key);
196
+ ensureArrowMarker(defs, color, sw);
197
+ }
192
198
  }
193
- ensureArrowMarkers(defs, arrowColors);
194
199
 
195
200
  // ── Render edges (below nodes) ──
196
201
  for (let i = 0; i < layout.edges.length; i++) {
197
202
  const le = layout.edges[i];
198
203
  const edge = parsed.edges[i];
199
204
  const color = resolveEdgeColor(edge, parsed, palette, defaultNodeColor);
200
- const strokeWidth = edge.width ?? DEFAULT_EDGE_WIDTH;
201
- const markerId = `cycle-arrow-${color.replace('#', '')}`;
205
+ const strokeWidth = Math.max(
206
+ edge.width ?? DEFAULT_EDGE_WIDTH,
207
+ MIN_EDGE_WIDTH
208
+ );
209
+ const markerId = arrowMarkerId(color, strokeWidth);
202
210
 
203
211
  const edgeG = g.append('g').attr('class', 'cycle-edge');
204
212
 
@@ -514,26 +522,45 @@ function resolveEdgeColor(
514
522
  return defaultNodeColor;
515
523
  }
516
524
 
517
- function ensureArrowMarkers(
525
+ /** Stable marker ID for a (color, strokeWidth) pair. */
526
+ function arrowMarkerId(color: string, strokeWidth: number): string {
527
+ return `cycle-arrow-${color.replace('#', '')}-w${strokeWidth}`;
528
+ }
529
+
530
+ /**
531
+ * Create an arrowhead marker using markerUnits="strokeWidth" (SVG default)
532
+ * with per-edge dimensions. The marker base automatically equals the stroke
533
+ * width — no gaps or lollipop effects. Marker dimensions are computed so
534
+ * the rendered arrowhead length follows a sublinear formula:
535
+ *
536
+ * rendered length = markerWidth × strokeWidth = arrowHeadLength(sw)
537
+ * → markerWidth = arrowHeadLength(sw) / sw
538
+ *
539
+ * The height is fixed at 1 strokeWidth unit so the base = stroke width.
540
+ */
541
+ function ensureArrowMarker(
518
542
  defs: d3Selection.Selection<SVGDefsElement, unknown, null, undefined>,
519
- colors: Set<string>
543
+ color: string,
544
+ strokeWidth: number
520
545
  ): void {
521
- for (const color of colors) {
522
- const id = `cycle-arrow-${color.replace('#', '')}`;
523
- defs
524
- .append('marker')
525
- .attr('id', id)
526
- .attr('viewBox', `0 0 ${ARROWHEAD_W * 2} ${ARROWHEAD_H * 2}`)
527
- .attr('refX', 0)
528
- .attr('refY', ARROWHEAD_H)
529
- .attr('markerWidth', ARROWHEAD_W)
530
- .attr('markerHeight', ARROWHEAD_H)
531
- .attr('orient', 'auto')
532
- .append('polygon')
533
- .attr(
534
- 'points',
535
- `0,0 ${ARROWHEAD_W * 2},${ARROWHEAD_H} 0,${ARROWHEAD_H * 2}`
536
- )
537
- .attr('fill', color);
538
- }
546
+ const id = arrowMarkerId(color, strokeWidth);
547
+ // Marker dimensions in strokeWidth units.
548
+ // Rendered size = mw × sw (length) and mh × sw (height).
549
+ const mw = arrowHeadLength(strokeWidth) / strokeWidth;
550
+ // Height proportional to length (½ ratio) but at least 1.5× stroke width
551
+ // so the arrowhead is always visibly wider than the stroke.
552
+ const mh = Math.max(1.5, mw * 0.5);
553
+
554
+ defs
555
+ .append('marker')
556
+ .attr('id', id)
557
+ .attr('viewBox', `0 0 ${mw} ${mh}`)
558
+ .attr('refX', mw * 0.1)
559
+ .attr('refY', mh / 2)
560
+ .attr('markerWidth', mw)
561
+ .attr('markerHeight', mh)
562
+ .attr('orient', 'auto')
563
+ .append('polygon')
564
+ .attr('points', `0,0 ${mw},${mh / 2} 0,${mh}`)
565
+ .attr('fill', color);
539
566
  }
@@ -64,6 +64,27 @@ export interface CycleLayoutEdge {
64
64
  label?: string;
65
65
  }
66
66
 
67
+ // ============================================================
68
+ // Shared arrow-sizing helpers (used by both layout + renderer)
69
+ // ============================================================
70
+
71
+ /** Default edge stroke width. */
72
+ export const DEFAULT_EDGE_WIDTH = 3;
73
+ /** Minimum rendered stroke width — thinner strokes produce unusable arrowheads. */
74
+ export const MIN_EDGE_WIDTH = 2;
75
+
76
+ /**
77
+ * Compute the desired arrowhead length in user-space pixels using sublinear
78
+ * scaling. The renderer uses markerUnits="strokeWidth" with computed marker
79
+ * dimensions so the arrowhead base always matches the stroke width (no gaps,
80
+ * no lollipop effect) while the rendered length follows this formula.
81
+ */
82
+ const BASE_ARROW_SIZE = 8;
83
+ const ARROW_SCALE = 6;
84
+ export function arrowHeadLength(strokeWidth: number): number {
85
+ return BASE_ARROW_SIZE + ARROW_SCALE * Math.sqrt(strokeWidth);
86
+ }
87
+
67
88
  export interface CycleLayoutResult {
68
89
  nodes: CycleLayoutNode[];
69
90
  edges: CycleLayoutEdge[];
package/src/d3.ts CHANGED
@@ -21,7 +21,8 @@ export type VisualizationType =
21
21
  | 'quadrant'
22
22
  | 'sequence'
23
23
  | 'tech-radar'
24
- | 'cycle';
24
+ | 'cycle'
25
+ | 'pyramid';
25
26
 
26
27
  interface D3DataItem {
27
28
  label: string;
@@ -7120,6 +7121,25 @@ export async function renderForExport(
7120
7121
  return finalizeSvgExport(container, theme, effectivePalette);
7121
7122
  }
7122
7123
 
7124
+ if (detectedType === 'pyramid') {
7125
+ const { parsePyramid } = await import('./pyramid/parser');
7126
+ const { renderPyramidForExport } = await import('./pyramid/renderer');
7127
+
7128
+ const effectivePalette = await resolveExportPalette(theme, palette);
7129
+ const pyramidParsed = parsePyramid(content);
7130
+ if (pyramidParsed.error || pyramidParsed.layers.length === 0) return '';
7131
+
7132
+ const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
7133
+ renderPyramidForExport(
7134
+ container,
7135
+ pyramidParsed,
7136
+ effectivePalette,
7137
+ theme === 'dark',
7138
+ { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
7139
+ );
7140
+ return finalizeSvgExport(container, theme, effectivePalette);
7141
+ }
7142
+
7123
7143
  const parsed = parseVisualization(content, palette);
7124
7144
  // Allow sequence diagrams through even if parseVisualization errors —
7125
7145
  // sequence is parsed by its own dedicated parser (parseSequenceDgmo)
@@ -22,6 +22,7 @@ import { parseWireframe } from './wireframe/parser';
22
22
  import { parseTechRadar } from './tech-radar/parser';
23
23
  import { parseCycle } from './cycle/parser';
24
24
  import { parseJourneyMap } from './journey-map/parser';
25
+ import { parsePyramid } from './pyramid/parser';
25
26
  import { parseFirstLine } from './utils/parsing';
26
27
  import { makeDgmoError, suggest } from './diagnostics';
27
28
  import type { DgmoError } from './diagnostics';
@@ -142,6 +143,7 @@ const VISUALIZATION_TYPES = new Set([
142
143
  'quadrant',
143
144
  'tech-radar',
144
145
  'cycle',
146
+ 'pyramid',
145
147
  ]);
146
148
  const DIAGRAM_TYPES = new Set([
147
149
  'sequence',
@@ -211,6 +213,63 @@ export function getAllChartTypes(): string[] {
211
213
  return [...DATA_CHART_TYPES, ...VISUALIZATION_TYPES, ...DIAGRAM_TYPES];
212
214
  }
213
215
 
216
+ /**
217
+ * Canonical descriptions for every supported chart type. Shared by the CLI
218
+ * `--chart-types` flag, the editor autocomplete popup, and the MCP
219
+ * `list_chart_types` tool so all three surfaces stay in sync.
220
+ */
221
+ export const CHART_TYPE_DESCRIPTIONS: Record<string, string> = {
222
+ bar: 'Bar chart — categorical comparisons',
223
+ line: 'Line chart — trends over time; supports era bands (era start -> end Label (color)) for annotating named periods',
224
+ 'multi-line':
225
+ 'Multi-line chart — multiple series trends over time; supports era bands',
226
+ area: 'Area chart — filled line chart; supports era bands',
227
+ pie: 'Pie chart — part-to-whole proportions',
228
+ doughnut: 'Doughnut chart — ring-style pie chart',
229
+ radar: 'Radar chart — multi-dimensional metrics',
230
+ 'polar-area': 'Polar area chart — radial bar chart',
231
+ 'bar-stacked': 'Stacked bar chart — multi-series categorical',
232
+ scatter: 'Scatter plot — 2D data points or bubble chart',
233
+ sankey: 'Sankey diagram — flow/allocation visualization',
234
+ chord: 'Chord diagram — circular flow relationships',
235
+ function: 'Function plot — mathematical expressions',
236
+ heatmap: 'Heatmap — matrix intensity visualization',
237
+ funnel: 'Funnel chart — conversion pipeline',
238
+ slope: 'Slope chart — change between two periods',
239
+ wordcloud: 'Word cloud — term frequency visualization',
240
+ arc: 'Arc diagram — network relationships',
241
+ timeline: 'Timeline — events, eras, and date ranges',
242
+ venn: 'Venn diagram — set overlaps',
243
+ quadrant: 'Quadrant chart — 2x2 positioning matrix',
244
+ 'tech-radar':
245
+ 'Tech radar — technology adoption quadrants (adopt/trial/assess/hold)',
246
+ cycle:
247
+ 'Cycle diagram — cyclical process visualization (PDCA, OODA, DevOps loops)',
248
+ sequence: 'Sequence diagram — message/interaction flows',
249
+ flowchart: 'Flowchart — decision trees and process flows',
250
+ class: 'Class diagram — UML class hierarchies',
251
+ er: 'ER diagram — database schemas and relationships',
252
+ org: 'Org chart — hierarchical tree structures',
253
+ kanban: 'Kanban board — task/workflow columns',
254
+ c4: 'C4 diagram — system architecture (context, container, component, deployment)',
255
+ state: 'State diagram — state machine / lifecycle transitions',
256
+ sitemap:
257
+ 'Sitemap — navigable UI structure with pages, groups, and cross-link arrows',
258
+ infra:
259
+ 'Infrastructure diagram — traffic flow with RPS computation, capacity modeling, and latency analysis',
260
+ gantt:
261
+ 'Gantt chart — project scheduling with task dependencies and milestones',
262
+ 'boxes-and-lines':
263
+ 'Boxes and lines — general-purpose node-edge diagrams with nested groups, tags, and shape inference',
264
+ mindmap: 'Mindmap — radial hierarchy of ideas branching from a central topic',
265
+ wireframe:
266
+ 'Wireframe — low-fidelity UI layout with panels, controls, and annotations',
267
+ 'journey-map':
268
+ 'Journey map — user experience flow with emotion scores, phases, and annotations',
269
+ pyramid:
270
+ 'Pyramid — hierarchical layered pyramid (Maslow, DIKW, learning pyramid); inverted for funnel-of-learning style',
271
+ };
272
+
214
273
  // ECharts-native types parsed by parseExtendedChart
215
274
  const ECHART_TYPES = new Set([
216
275
  'scatter',
@@ -243,6 +302,7 @@ const PARSE_DISPATCH = new Map<
243
302
  ['tech-radar', (c) => parseTechRadar(c)],
244
303
  ['cycle', (c) => parseCycle(c)],
245
304
  ['journey-map', (c) => parseJourneyMap(c)],
305
+ ['pyramid', (c) => parsePyramid(c)],
246
306
  ]);
247
307
 
248
308
  /**
@@ -260,7 +320,10 @@ const ALL_KNOWN_TYPES = new Set([
260
320
  * Parse DGMO content and return diagnostics without rendering.
261
321
  * Useful for the CLI and editor to surface all errors before attempting render.
262
322
  */
263
- export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
323
+ export function parseDgmo(content: string): {
324
+ diagnostics: DgmoError[];
325
+ chartType: string | null;
326
+ } {
264
327
  const chartType = parseDgmoChartType(content);
265
328
 
266
329
  if (!chartType) {
@@ -268,11 +331,14 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
268
331
  const colonDiag = detectColonChartType(content);
269
332
  if (colonDiag) {
270
333
  const fallback = parseVisualization(content).diagnostics;
271
- return { diagnostics: [colonDiag, ...fallback] };
334
+ return { diagnostics: [colonDiag, ...fallback], chartType: null };
272
335
  }
273
336
 
274
337
  // No chart type detected — try visualization parser as fallback
275
- return { diagnostics: parseVisualization(content).diagnostics };
338
+ return {
339
+ diagnostics: parseVisualization(content).diagnostics,
340
+ chartType: null,
341
+ };
276
342
  }
277
343
 
278
344
  const directParser = PARSE_DISPATCH.get(chartType);
@@ -280,6 +346,7 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
280
346
  const result = directParser(content);
281
347
  return {
282
348
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
349
+ chartType,
283
350
  };
284
351
  }
285
352
 
@@ -287,12 +354,14 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
287
354
  const result = parseChart(content);
288
355
  return {
289
356
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
357
+ chartType,
290
358
  };
291
359
  }
292
360
  if (ECHART_TYPES.has(chartType)) {
293
361
  const result = parseExtendedChart(content);
294
362
  return {
295
363
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
364
+ chartType,
296
365
  };
297
366
  }
298
367
 
@@ -300,6 +369,7 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
300
369
  const result = parseVisualization(content);
301
370
  return {
302
371
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
372
+ chartType,
303
373
  };
304
374
  }
305
375
 
package/src/echarts.ts CHANGED
@@ -1913,7 +1913,7 @@ function buildFunnelOption(
1913
1913
  bottom: 20,
1914
1914
  width: '60%',
1915
1915
  sort: 'descending' as const,
1916
- gap: 2,
1916
+ gap: 0,
1917
1917
  minSize: '8%',
1918
1918
  };
1919
1919
 
@@ -17,6 +17,7 @@ export const CHART_TYPES = new Set([
17
17
  'tech-radar',
18
18
  'mindmap',
19
19
  'journey-map',
20
+ 'pyramid',
20
21
  // Data chart types
21
22
  'bar',
22
23
  'line',
@@ -158,6 +159,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
158
159
  // Layout
159
160
  'direction-tb',
160
161
  'direction-lr',
162
+ // Pyramid
163
+ 'inverted',
161
164
  // Data chart metadata
162
165
  'title',
163
166
  'series',
package/src/index.ts CHANGED
@@ -32,6 +32,8 @@ export {
32
32
  parseDgmo,
33
33
  getRenderCategory,
34
34
  isExtendedChartType,
35
+ getAllChartTypes,
36
+ CHART_TYPE_DESCRIPTIONS,
35
37
  } from './dgmo-router';
36
38
  export type { RenderCategory } from './dgmo-router';
37
39
 
@@ -63,6 +65,7 @@ export type {
63
65
 
64
66
  export {
65
67
  parseSequenceDgmo,
68
+ parseSequenceDgmo as parseSequenceDiagram,
66
69
  looksLikeSequence,
67
70
  isSequenceBlock,
68
71
  isSequenceNote,
@@ -331,8 +334,12 @@ export type {
331
334
  ResolvedGroup,
332
335
  } from './gantt/types';
333
336
 
334
- export { collapseOrgTree } from './org/collapse';
335
- export type { CollapsedOrgResult } from './org/collapse';
337
+ export { collapseOrgTree, focusOrgTree } from './org/collapse';
338
+ export type {
339
+ CollapsedOrgResult,
340
+ FocusOrgResult,
341
+ AncestorInfo,
342
+ } from './org/collapse';
336
343
 
337
344
  export { parseMindmap } from './mindmap/parser';
338
345
  export type {
@@ -407,6 +414,10 @@ export type {
407
414
  } from './journey-map/types';
408
415
  export type { JourneyMapInteractiveOptions } from './journey-map/renderer';
409
416
 
417
+ export { parsePyramid } from './pyramid/parser';
418
+ export { renderPyramid, renderPyramidForExport } from './pyramid/renderer';
419
+ export type { ParsedPyramid, PyramidLayer } from './pyramid/types';
420
+
410
421
  export { resolveOrgImports } from './org/resolver';
411
422
  export type {
412
423
  ReadFileFn,
@@ -113,8 +113,8 @@ function extractPipeMetadata(rest: string): {
113
113
  const tags: Record<string, string> = {};
114
114
  let clean = rest;
115
115
  let match: RegExpExecArray | null;
116
- const re = new RegExp(PIPE_META_RE.source, 'g');
117
- while ((match = re.exec(rest)) !== null) {
116
+ PIPE_META_RE.lastIndex = 0;
117
+ while ((match = PIPE_META_RE.exec(rest)) !== null) {
118
118
  tags[match[1].trim()] = match[2].trim();
119
119
  clean = clean.replace(match[0], '');
120
120
  }
@@ -0,0 +1,16 @@
1
+ // ============================================================
2
+ // @diagrammo/dgmo/internal — internal helpers for app consumers.
3
+ // Not part of the public API; may change between versions.
4
+ // ============================================================
5
+
6
+ export { parseDataRowValues } from './chart';
7
+ export {
8
+ computeCardArchive,
9
+ computeCardMove,
10
+ isArchiveColumn,
11
+ } from './kanban/mutations';
12
+ export {
13
+ groupMessagesBySection,
14
+ buildNoteMessageMap,
15
+ collectNoteLineNumbers,
16
+ } from './sequence/renderer';