@diagrammo/dgmo 0.0.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.
@@ -0,0 +1,247 @@
1
+ // ============================================================
2
+ // .dgmo → Mermaid Translation Layer
3
+ // Parses dgmo quadrant syntax and generates valid Mermaid code.
4
+ // ============================================================
5
+
6
+ import { resolveColor } from './colors';
7
+
8
+ // ============================================================
9
+ // Types
10
+ // ============================================================
11
+
12
+ interface QuadrantLabel {
13
+ text: string;
14
+ color: string | null;
15
+ lineNumber: number;
16
+ }
17
+
18
+ export interface ParsedQuadrant {
19
+ title: string | null;
20
+ titleLineNumber: number | null;
21
+ xAxis: [string, string] | null;
22
+ xAxisLineNumber: number | null;
23
+ yAxis: [string, string] | null;
24
+ yAxisLineNumber: number | null;
25
+ quadrants: {
26
+ topRight: QuadrantLabel | null;
27
+ topLeft: QuadrantLabel | null;
28
+ bottomLeft: QuadrantLabel | null;
29
+ bottomRight: QuadrantLabel | null;
30
+ };
31
+ points: { label: string; x: number; y: number; lineNumber: number }[];
32
+ error: string | null;
33
+ }
34
+
35
+ // ============================================================
36
+ // Parser
37
+ // ============================================================
38
+
39
+ /** Regex for quadrant label lines: `top-right: Promote (green)` */
40
+ const QUADRANT_LABEL_RE = /^(.+?)(?:\s*\(([^)]+)\))?\s*$/;
41
+
42
+ /** Regex for data point lines: `Label: 0.9, 0.5` */
43
+ const DATA_POINT_RE = /^(.+?):\s*([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*$/;
44
+
45
+ const QUADRANT_POSITIONS = new Set([
46
+ 'top-right',
47
+ 'top-left',
48
+ 'bottom-left',
49
+ 'bottom-right',
50
+ ]);
51
+
52
+ /**
53
+ * Parses a .dgmo quadrant document into a structured object.
54
+ * Lines are processed sequentially; unknown lines are silently skipped.
55
+ */
56
+ export function parseQuadrant(content: string): ParsedQuadrant {
57
+ const result: ParsedQuadrant = {
58
+ title: null,
59
+ titleLineNumber: null,
60
+ xAxis: null,
61
+ xAxisLineNumber: null,
62
+ yAxis: null,
63
+ yAxisLineNumber: null,
64
+ quadrants: {
65
+ topRight: null,
66
+ topLeft: null,
67
+ bottomLeft: null,
68
+ bottomRight: null,
69
+ },
70
+ points: [],
71
+ error: null,
72
+ };
73
+
74
+ const lines = content.split('\n');
75
+
76
+ for (let i = 0; i < lines.length; i++) {
77
+ const line = lines[i].trim();
78
+ const lineNumber = i + 1; // 1-indexed for editor
79
+
80
+ // Skip empty lines and comments
81
+ if (!line || line.startsWith('#') || line.startsWith('//')) continue;
82
+
83
+ // Skip the chart: directive (already consumed by router)
84
+ if (/^chart\s*:/i.test(line)) continue;
85
+
86
+ // title: <text>
87
+ const titleMatch = line.match(/^title\s*:\s*(.+)/i);
88
+ if (titleMatch) {
89
+ result.title = titleMatch[1].trim();
90
+ result.titleLineNumber = lineNumber;
91
+ continue;
92
+ }
93
+
94
+ // x-axis: Low, High
95
+ const xMatch = line.match(/^x-axis\s*:\s*(.+)/i);
96
+ if (xMatch) {
97
+ const parts = xMatch[1].split(',').map((s) => s.trim());
98
+ if (parts.length >= 2) {
99
+ result.xAxis = [parts[0], parts[1]];
100
+ result.xAxisLineNumber = lineNumber;
101
+ }
102
+ continue;
103
+ }
104
+
105
+ // y-axis: Low, High
106
+ const yMatch = line.match(/^y-axis\s*:\s*(.+)/i);
107
+ if (yMatch) {
108
+ const parts = yMatch[1].split(',').map((s) => s.trim());
109
+ if (parts.length >= 2) {
110
+ result.yAxis = [parts[0], parts[1]];
111
+ result.yAxisLineNumber = lineNumber;
112
+ }
113
+ continue;
114
+ }
115
+
116
+ // Quadrant position labels: top-right: Label (color)
117
+ const posMatch = line.match(
118
+ /^(top-right|top-left|bottom-left|bottom-right)\s*:\s*(.+)/i
119
+ );
120
+ if (posMatch) {
121
+ const position = posMatch[1].toLowerCase();
122
+ const labelMatch = posMatch[2].match(QUADRANT_LABEL_RE);
123
+ if (labelMatch) {
124
+ const label: QuadrantLabel = {
125
+ text: labelMatch[1].trim(),
126
+ color: labelMatch[2] ? resolveColor(labelMatch[2].trim()) : null,
127
+ lineNumber,
128
+ };
129
+ if (position === 'top-right') result.quadrants.topRight = label;
130
+ else if (position === 'top-left') result.quadrants.topLeft = label;
131
+ else if (position === 'bottom-left')
132
+ result.quadrants.bottomLeft = label;
133
+ else if (position === 'bottom-right')
134
+ result.quadrants.bottomRight = label;
135
+ }
136
+ continue;
137
+ }
138
+
139
+ // Data points: Label: x, y
140
+ const pointMatch = line.match(DATA_POINT_RE);
141
+ if (pointMatch) {
142
+ // Make sure this isn't a quadrant position keyword
143
+ const key = pointMatch[1].trim().toLowerCase();
144
+ if (!QUADRANT_POSITIONS.has(key)) {
145
+ result.points.push({
146
+ label: pointMatch[1].trim(),
147
+ x: parseFloat(pointMatch[2]),
148
+ y: parseFloat(pointMatch[3]),
149
+ lineNumber,
150
+ });
151
+ }
152
+ continue;
153
+ }
154
+ }
155
+
156
+ if (result.points.length === 0) {
157
+ result.error = 'No data points found. Add lines like: Label: 0.5, 0.7';
158
+ }
159
+
160
+ return result;
161
+ }
162
+
163
+ // ============================================================
164
+ // Mermaid Builder
165
+ // ============================================================
166
+
167
+ /**
168
+ * Generates valid Mermaid quadrantChart syntax from a parsed quadrant.
169
+ * Returns a string ready for the Mermaid renderer.
170
+ */
171
+ export function buildMermaidQuadrant(
172
+ parsed: ParsedQuadrant,
173
+ options: {
174
+ isDark?: boolean;
175
+ textColor?: string;
176
+ mutedTextColor?: string;
177
+ } = {}
178
+ ): string {
179
+ const { isDark = false, textColor, mutedTextColor } = options;
180
+ const lines: string[] = [];
181
+
182
+ // %%{init}%% block — fill colors with reduced opacity + text color overrides
183
+ const fillAlpha = isDark ? '30' : '55';
184
+ const primaryText = textColor ?? (isDark ? '#d0d0d0' : '#333333');
185
+ const quadrantLabelText = mutedTextColor ?? (isDark ? '#888888' : '#666666');
186
+
187
+ const colorMap: Record<string, string> = {};
188
+ if (parsed.quadrants.topRight?.color)
189
+ colorMap.quadrant1Fill = parsed.quadrants.topRight.color + fillAlpha;
190
+ if (parsed.quadrants.topLeft?.color)
191
+ colorMap.quadrant2Fill = parsed.quadrants.topLeft.color + fillAlpha;
192
+ if (parsed.quadrants.bottomLeft?.color)
193
+ colorMap.quadrant3Fill = parsed.quadrants.bottomLeft.color + fillAlpha;
194
+ if (parsed.quadrants.bottomRight?.color)
195
+ colorMap.quadrant4Fill = parsed.quadrants.bottomRight.color + fillAlpha;
196
+
197
+ // Quadrant labels use muted color, points use primary text color
198
+ colorMap.quadrant1TextFill = quadrantLabelText;
199
+ colorMap.quadrant2TextFill = quadrantLabelText;
200
+ colorMap.quadrant3TextFill = quadrantLabelText;
201
+ colorMap.quadrant4TextFill = quadrantLabelText;
202
+ colorMap.quadrantPointTextFill = primaryText;
203
+ colorMap.quadrantXAxisTextFill = primaryText;
204
+ colorMap.quadrantYAxisTextFill = primaryText;
205
+ colorMap.quadrantTitleFill = primaryText;
206
+
207
+ const vars = JSON.stringify(colorMap);
208
+ lines.push(`%%{init: {"themeVariables": ${vars}}}%%`);
209
+
210
+ lines.push('quadrantChart');
211
+
212
+ if (parsed.title) {
213
+ lines.push(` title ${parsed.title}`);
214
+ }
215
+
216
+ if (parsed.xAxis) {
217
+ lines.push(` x-axis ${parsed.xAxis[0]} --> ${parsed.xAxis[1]}`);
218
+ }
219
+
220
+ if (parsed.yAxis) {
221
+ lines.push(` y-axis ${parsed.yAxis[0]} --> ${parsed.yAxis[1]}`);
222
+ }
223
+
224
+ // Helper to quote labels that need it (contain spaces or special chars)
225
+ const quote = (s: string): string => (/[\s,:[\]]/.test(s) ? `"${s}"` : s);
226
+
227
+ // Quadrant labels: 1=top-right, 2=top-left, 3=bottom-left, 4=bottom-right
228
+ if (parsed.quadrants.topRight) {
229
+ lines.push(` quadrant-1 ${quote(parsed.quadrants.topRight.text)}`);
230
+ }
231
+ if (parsed.quadrants.topLeft) {
232
+ lines.push(` quadrant-2 ${quote(parsed.quadrants.topLeft.text)}`);
233
+ }
234
+ if (parsed.quadrants.bottomLeft) {
235
+ lines.push(` quadrant-3 ${quote(parsed.quadrants.bottomLeft.text)}`);
236
+ }
237
+ if (parsed.quadrants.bottomRight) {
238
+ lines.push(` quadrant-4 ${quote(parsed.quadrants.bottomRight.text)}`);
239
+ }
240
+
241
+ // Data points
242
+ for (const point of parsed.points) {
243
+ lines.push(` ${quote(point.label)}: [${point.x}, ${point.y}]`);
244
+ }
245
+
246
+ return lines.join('\n');
247
+ }
@@ -0,0 +1,77 @@
1
+ // ============================================================
2
+ // .dgmo Unified Format — Chart Type Router
3
+ // ============================================================
4
+
5
+ import { looksLikeSequence } from './sequence/parser';
6
+
7
+ /**
8
+ * Framework identifiers used by the .dgmo router.
9
+ * Maps to the existing preview components and export paths.
10
+ */
11
+ export type DgmoFramework = 'chartjs' | 'echart' | 'd3' | 'mermaid';
12
+
13
+ /**
14
+ * Maps every supported chart type string to its backing framework.
15
+ *
16
+ * Chart.js: standard chart types (bar, line, pie, etc.)
17
+ * ECharts: scatter, flow/relationship diagrams, math, heatmap
18
+ * D3: slope, wordcloud, arc diagram, timeline
19
+ */
20
+ export const DGMO_CHART_TYPE_MAP: Record<string, DgmoFramework> = {
21
+ // Chart.js
22
+ bar: 'chartjs',
23
+ line: 'chartjs',
24
+ 'multi-line': 'chartjs',
25
+ area: 'chartjs',
26
+ pie: 'chartjs',
27
+ doughnut: 'chartjs',
28
+ radar: 'chartjs',
29
+ 'polar-area': 'chartjs',
30
+ 'bar-stacked': 'chartjs',
31
+
32
+ // ECharts
33
+ scatter: 'echart',
34
+ sankey: 'echart',
35
+ chord: 'echart',
36
+ function: 'echart',
37
+ heatmap: 'echart',
38
+ funnel: 'echart',
39
+
40
+ // D3
41
+ slope: 'd3',
42
+ wordcloud: 'd3',
43
+ arc: 'd3',
44
+ timeline: 'd3',
45
+ venn: 'd3',
46
+ quadrant: 'd3',
47
+ sequence: 'd3',
48
+ };
49
+
50
+ /**
51
+ * Returns the framework for a given chart type, or `null` if unknown.
52
+ */
53
+ export function getDgmoFramework(chartType: string): DgmoFramework | null {
54
+ return DGMO_CHART_TYPE_MAP[chartType.toLowerCase()] ?? null;
55
+ }
56
+
57
+ /**
58
+ * Extracts the `chart:` type value from raw file content.
59
+ * Falls back to inference when no explicit `chart:` line is found
60
+ * (e.g. content containing `->` is inferred as `sequence`).
61
+ */
62
+ export function parseDgmoChartType(content: string): string | null {
63
+ const lines = content.split('\n');
64
+ for (const line of lines) {
65
+ const trimmed = line.trim();
66
+ // Skip empty lines and comments
67
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//'))
68
+ continue;
69
+ const match = trimmed.match(/^chart\s*:\s*(.+)/i);
70
+ if (match) return match[1].trim().toLowerCase();
71
+ }
72
+
73
+ // Infer sequence chart type when content contains arrow patterns
74
+ if (looksLikeSequence(content)) return 'sequence';
75
+
76
+ return null;
77
+ }