@diagrammo/dgmo 0.47.0 → 0.48.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/dist/advanced.js +11 -11
- package/dist/auto.js +16 -16
- package/dist/auto.mjs +41 -12
- package/dist/block.js +4 -4
- package/dist/{chunk-FNB3PNGF.js → chunk-7DTWEFUE.js} +3 -3
- package/dist/{chunk-2DXMZRWJ.js → chunk-B65G5DNL.js} +8 -1
- package/dist/{chunk-RZCF2VVM.js → chunk-B7JUCQUA.js} +3 -1
- package/dist/{chunk-DU7W62N5.js → chunk-FANX6UJR.js} +3 -3
- package/dist/{chunk-6FZYCKXB.js → chunk-HSH73DP6.js} +2 -2
- package/dist/{chunk-P4RH4OSP.js → chunk-ILKORZ3H.js} +2 -2
- package/dist/{chunk-Y5QGVTVC.js → chunk-JTSMEYIZ.js} +7 -3
- package/dist/{chunk-RCERWOJR.js → chunk-ODZVHU3R.js} +1 -2
- package/dist/{chunk-DZAQBW35.js → chunk-SGZYXELT.js} +3 -7
- package/dist/{chunk-CUVHQHZ7.js → chunk-THXCFRTU.js} +3 -3
- package/dist/{chunk-IH6ZMVQ5.js → chunk-UC7TFHUM.js} +29 -11
- package/dist/{chunk-EMS2SK26.js → chunk-ZBWMSD5N.js} +3 -1
- package/dist/cli.cjs +15 -15
- package/dist/{dgmo-router-DZCIDUO5.js → dgmo-router-X35VLZLM.js} +3 -3
- package/dist/element.js +16 -16
- package/dist/element.mjs +41 -12
- package/dist/{flowchart-renderer-ZAEOL4DX.js → flowchart-renderer-SNDSII4D.js} +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -4
- package/dist/{parser-FUSKFGZN.js → parser-2EWECDSA.js} +2 -2
- package/dist/{renderer-UWA5XS74.js → renderer-4JHRUBE6.js} +3 -3
- package/dist/{renderer-WZRREQ2P.js → renderer-5MYBMBYO.js} +2 -2
- package/dist/{renderer-TOCOVLHS.js → renderer-B2OMQIQG.js} +3 -3
- package/dist/{renderer-2XQQQ6QR.js → renderer-N43WR5GV.js} +3 -3
- package/dist/{renderer-T7YKC5VP.js → renderer-ZRNG54CW.js} +3 -3
- package/dist/{state-renderer-RFYHSP4K.js → state-renderer-B4DQBM6M.js} +2 -2
- package/package.json +23 -71
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/data-chart-parser.ts
DELETED
|
@@ -1,1007 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Data-chart parser + types + legend-group helpers.
|
|
3
|
-
// Extracted from the former echarts.ts when ECharts was removed — this is
|
|
4
|
-
// the pure DGMO→ParsedExtendedChart parser (no rendering, no ECharts). The
|
|
5
|
-
// hand-built D3 renderers in charts-d3/ consume these types + parsers.
|
|
6
|
-
// ============================================================
|
|
7
|
-
|
|
8
|
-
import type { LegendGroupData } from './utils/legend-types';
|
|
9
|
-
|
|
10
|
-
// ============================================================
|
|
11
|
-
// Types
|
|
12
|
-
// ============================================================
|
|
13
|
-
|
|
14
|
-
export type ExtendedChartType =
|
|
15
|
-
| 'sankey'
|
|
16
|
-
| 'chord'
|
|
17
|
-
| 'function'
|
|
18
|
-
| 'scatter'
|
|
19
|
-
| 'heatmap'
|
|
20
|
-
| 'funnel';
|
|
21
|
-
|
|
22
|
-
interface ExtendedChartDataPoint {
|
|
23
|
-
label: string;
|
|
24
|
-
value: number;
|
|
25
|
-
color?: string;
|
|
26
|
-
lineNumber: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface ParsedSankeyLink {
|
|
30
|
-
source: string;
|
|
31
|
-
target: string;
|
|
32
|
-
value: number;
|
|
33
|
-
color?: string;
|
|
34
|
-
directed?: boolean;
|
|
35
|
-
lineNumber: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ParsedFunction {
|
|
39
|
-
name: string;
|
|
40
|
-
expression: string;
|
|
41
|
-
color?: string;
|
|
42
|
-
lineNumber: number;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface ParsedScatterPoint {
|
|
46
|
-
name: string;
|
|
47
|
-
x: number;
|
|
48
|
-
y: number;
|
|
49
|
-
size?: number;
|
|
50
|
-
color?: string;
|
|
51
|
-
category?: string;
|
|
52
|
-
lineNumber: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface ParsedHeatmapRow {
|
|
56
|
-
label: string;
|
|
57
|
-
values: number[];
|
|
58
|
-
lineNumber: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
import type { DgmoError } from './diagnostics';
|
|
62
|
-
|
|
63
|
-
// ============================================================
|
|
64
|
-
// Discriminated union — Story 109.2a (arch-review). Each extended data-chart
|
|
65
|
-
// carries only its own type-specific fields; the parser fills a fat
|
|
66
|
-
// `ParsedExtendedChartFull` accumulator and returns it narrowed (type-only,
|
|
67
|
-
// runtime-identical). Shared fields live on ParsedExtendedBase.
|
|
68
|
-
// ============================================================
|
|
69
|
-
|
|
70
|
-
/** Fields shared by every extended data-chart. */
|
|
71
|
-
export interface ParsedExtendedBase {
|
|
72
|
-
title?: string;
|
|
73
|
-
titleLineNumber?: number;
|
|
74
|
-
series?: string;
|
|
75
|
-
seriesLineNumber?: number;
|
|
76
|
-
seriesNames?: string[];
|
|
77
|
-
seriesNameLineNumbers?: number[];
|
|
78
|
-
seriesNameColors?: (string | undefined)[];
|
|
79
|
-
data: ExtendedChartDataPoint[];
|
|
80
|
-
xlabel?: string;
|
|
81
|
-
xlabelLineNumber?: number;
|
|
82
|
-
ylabel?: string;
|
|
83
|
-
ylabelLineNumber?: number;
|
|
84
|
-
/** X-axis range — read by both function plots and scatter. */
|
|
85
|
-
xRange?: { min: number; max: number };
|
|
86
|
-
noName?: boolean;
|
|
87
|
-
noValue?: boolean;
|
|
88
|
-
noPercent?: boolean;
|
|
89
|
-
shade?: boolean;
|
|
90
|
-
/** Render with full intent saturation instead of the canonical 25% tint. */
|
|
91
|
-
solidFill?: boolean;
|
|
92
|
-
/** Cross-chart-type: when true, the renderer suppresses the chart title. */
|
|
93
|
-
noTitle?: boolean;
|
|
94
|
-
categoryColors?: Record<string, string>;
|
|
95
|
-
categoryLineNumbers?: Record<string, number>;
|
|
96
|
-
nodeColors?: Record<string, string>;
|
|
97
|
-
diagnostics: DgmoError[];
|
|
98
|
-
error: string | null;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export interface ParsedSankey extends ParsedExtendedBase {
|
|
102
|
-
type: 'sankey';
|
|
103
|
-
links?: ParsedSankeyLink[];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface ParsedChord extends ParsedExtendedBase {
|
|
107
|
-
type: 'chord';
|
|
108
|
-
links?: ParsedSankeyLink[];
|
|
109
|
-
/** `layout arc|chord` override (#26). `arc` re-renders the same edges as a
|
|
110
|
-
* linear arc; absent ⇒ the `chord` circular preset. */
|
|
111
|
-
layout?: 'arc' | 'chord';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface ParsedFunctionChart extends ParsedExtendedBase {
|
|
115
|
-
type: 'function';
|
|
116
|
-
functions?: ParsedFunction[];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface ParsedScatter extends ParsedExtendedBase {
|
|
120
|
-
type: 'scatter';
|
|
121
|
-
scatterPoints?: ParsedScatterPoint[];
|
|
122
|
-
sizelabel?: string;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface ParsedHeatmap extends ParsedExtendedBase {
|
|
126
|
-
type: 'heatmap';
|
|
127
|
-
heatmapRows?: ParsedHeatmapRow[];
|
|
128
|
-
columns?: string[];
|
|
129
|
-
rows?: string[];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export interface ParsedFunnel extends ParsedExtendedBase {
|
|
133
|
-
type: 'funnel';
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** What `parseExtendedChart` returns: discriminated on `type`. */
|
|
137
|
-
export type ParsedExtendedChart =
|
|
138
|
-
| ParsedSankey
|
|
139
|
-
| ParsedChord
|
|
140
|
-
| ParsedFunctionChart
|
|
141
|
-
| ParsedScatter
|
|
142
|
-
| ParsedHeatmap
|
|
143
|
-
| ParsedFunnel;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* The parser's mutable accumulator — every type-specific field present, so the
|
|
147
|
-
* single state machine can populate whichever the detected type needs.
|
|
148
|
-
* `parseExtendedChart` returns this narrowed to {@link ParsedExtendedChart}; the
|
|
149
|
-
* object is a structural superset of every variant, so the narrowing is sound
|
|
150
|
-
* and changes nothing at runtime.
|
|
151
|
-
*/
|
|
152
|
-
export interface ParsedExtendedChartFull extends ParsedExtendedBase {
|
|
153
|
-
type: ExtendedChartType;
|
|
154
|
-
links?: ParsedSankeyLink[];
|
|
155
|
-
layout?: 'arc' | 'chord';
|
|
156
|
-
functions?: ParsedFunction[];
|
|
157
|
-
scatterPoints?: ParsedScatterPoint[];
|
|
158
|
-
heatmapRows?: ParsedHeatmapRow[];
|
|
159
|
-
columns?: string[];
|
|
160
|
-
rows?: string[];
|
|
161
|
-
xRange?: { min: number; max: number };
|
|
162
|
-
sizelabel?: string;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ============================================================
|
|
166
|
-
// Nord Colors for Charts
|
|
167
|
-
// ============================================================
|
|
168
|
-
|
|
169
|
-
import type { PaletteColors } from './palettes';
|
|
170
|
-
import type { ParsedChart } from './chart';
|
|
171
|
-
import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
|
|
172
|
-
import { resolveColorWithDiagnostic } from './colors';
|
|
173
|
-
import {
|
|
174
|
-
collectIndentedValues,
|
|
175
|
-
extractColor,
|
|
176
|
-
measureIndent,
|
|
177
|
-
normalizeNumericToken,
|
|
178
|
-
parseFirstLine,
|
|
179
|
-
parseSeriesNames,
|
|
180
|
-
} from './utils/parsing';
|
|
181
|
-
import { parseDataRowValues } from './chart';
|
|
182
|
-
|
|
183
|
-
// ============================================================
|
|
184
|
-
// Shared Constants
|
|
185
|
-
// ============================================================
|
|
186
|
-
|
|
187
|
-
const VALID_EXTENDED_TYPES = new Set<ExtendedChartType>([
|
|
188
|
-
'sankey',
|
|
189
|
-
'chord',
|
|
190
|
-
'function',
|
|
191
|
-
'scatter',
|
|
192
|
-
'heatmap',
|
|
193
|
-
'funnel',
|
|
194
|
-
]);
|
|
195
|
-
|
|
196
|
-
/** Known option keywords for the extended chart parser. */
|
|
197
|
-
const KNOWN_EXTENDED_OPTIONS = new Set([
|
|
198
|
-
'chart',
|
|
199
|
-
'title',
|
|
200
|
-
'series',
|
|
201
|
-
'x-label',
|
|
202
|
-
'y-label',
|
|
203
|
-
'size-label',
|
|
204
|
-
'layout',
|
|
205
|
-
'no-name',
|
|
206
|
-
'no-value',
|
|
207
|
-
'no-percent',
|
|
208
|
-
'columns',
|
|
209
|
-
'rows',
|
|
210
|
-
'x',
|
|
211
|
-
]);
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Parse a scatter data row: "Name x, y[, size]" or "Name(color) x, y[, size]"
|
|
215
|
-
* Returns a ParsedScatterPoint or null if the line doesn't match.
|
|
216
|
-
*/
|
|
217
|
-
function parseScatterRow(
|
|
218
|
-
line: string,
|
|
219
|
-
palette: PaletteColors | undefined,
|
|
220
|
-
currentCategory: string,
|
|
221
|
-
lineNumber: number,
|
|
222
|
-
diagnostics: DgmoError[]
|
|
223
|
-
): ParsedScatterPoint | null {
|
|
224
|
-
const dataRow = parseDataRowValues(line, { multiValue: true });
|
|
225
|
-
if (!dataRow || dataRow.values.length < 2) return null;
|
|
226
|
-
const { label: rawLabel, color: pointColor } = extractColor(
|
|
227
|
-
dataRow.label,
|
|
228
|
-
palette,
|
|
229
|
-
diagnostics,
|
|
230
|
-
lineNumber
|
|
231
|
-
);
|
|
232
|
-
return {
|
|
233
|
-
name: rawLabel,
|
|
234
|
-
// In-bounds by length >= 2 guard above.
|
|
235
|
-
x: dataRow.values[0]!,
|
|
236
|
-
y: dataRow.values[1]!,
|
|
237
|
-
...(dataRow.values[2] !== undefined && { size: dataRow.values[2] }),
|
|
238
|
-
...(pointColor && { color: pointColor }),
|
|
239
|
-
...(currentCategory !== 'Default' && { category: currentCategory }),
|
|
240
|
-
lineNumber,
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Parses extended chart content into a structured object.
|
|
246
|
-
*
|
|
247
|
-
* Format (colon-free):
|
|
248
|
-
* ```
|
|
249
|
-
* scatter My Chart
|
|
250
|
-
* xlabel Weight
|
|
251
|
-
*
|
|
252
|
-
* Alice 165, 60
|
|
253
|
-
* Bob 180, 85
|
|
254
|
-
* ```
|
|
255
|
-
*/
|
|
256
|
-
export function parseExtendedChart(
|
|
257
|
-
content: string,
|
|
258
|
-
palette?: PaletteColors
|
|
259
|
-
): ParsedExtendedChart {
|
|
260
|
-
return parseExtendedChartFull(content, palette) as ParsedExtendedChart;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// ============================================================
|
|
264
|
-
// Per-type parser doors — Story 109.2a (arch-review). Each extended data-chart's
|
|
265
|
-
// typed entry point into the shared parse state machine, returning its narrowed
|
|
266
|
-
// variant. The registry binds each id to its own door; `isExtendedChartParser`
|
|
267
|
-
// recognises them by set membership (they replace the old single-identity check).
|
|
268
|
-
// ============================================================
|
|
269
|
-
|
|
270
|
-
export function parseSankey(
|
|
271
|
-
content: string,
|
|
272
|
-
palette?: PaletteColors
|
|
273
|
-
): ParsedSankey {
|
|
274
|
-
return parseExtendedChart(content, palette) as ParsedSankey;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export function parseChord(
|
|
278
|
-
content: string,
|
|
279
|
-
palette?: PaletteColors
|
|
280
|
-
): ParsedChord {
|
|
281
|
-
return parseExtendedChart(content, palette) as ParsedChord;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function parseFunctionChart(
|
|
285
|
-
content: string,
|
|
286
|
-
palette?: PaletteColors
|
|
287
|
-
): ParsedFunctionChart {
|
|
288
|
-
return parseExtendedChart(content, palette) as ParsedFunctionChart;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function parseScatter(
|
|
292
|
-
content: string,
|
|
293
|
-
palette?: PaletteColors
|
|
294
|
-
): ParsedScatter {
|
|
295
|
-
return parseExtendedChart(content, palette) as ParsedScatter;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export function parseHeatmap(
|
|
299
|
-
content: string,
|
|
300
|
-
palette?: PaletteColors
|
|
301
|
-
): ParsedHeatmap {
|
|
302
|
-
return parseExtendedChart(content, palette) as ParsedHeatmap;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
export function parseFunnel(
|
|
306
|
-
content: string,
|
|
307
|
-
palette?: PaletteColors
|
|
308
|
-
): ParsedFunnel {
|
|
309
|
-
return parseExtendedChart(content, palette) as ParsedFunnel;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/** The six extended-data-chart parser doors, for `isExtendedChartParser`. */
|
|
313
|
-
export const EXTENDED_CHART_DOORS = new Set<unknown>([
|
|
314
|
-
parseSankey,
|
|
315
|
-
parseChord,
|
|
316
|
-
parseFunctionChart,
|
|
317
|
-
parseScatter,
|
|
318
|
-
parseHeatmap,
|
|
319
|
-
parseFunnel,
|
|
320
|
-
]);
|
|
321
|
-
|
|
322
|
-
function parseExtendedChartFull(
|
|
323
|
-
content: string,
|
|
324
|
-
palette?: PaletteColors
|
|
325
|
-
): ParsedExtendedChartFull {
|
|
326
|
-
const lines = content.split('\n');
|
|
327
|
-
const result: ParsedExtendedChartFull = {
|
|
328
|
-
type: 'scatter',
|
|
329
|
-
data: [],
|
|
330
|
-
diagnostics: [],
|
|
331
|
-
error: null,
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
// Track current category for grouped scatter charts
|
|
335
|
-
let currentCategory = 'Default';
|
|
336
|
-
|
|
337
|
-
// Sankey indentation state: stack of source nodes by indent level
|
|
338
|
-
const sankeyStack: { name: string; indent: number }[] = [];
|
|
339
|
-
let firstLineParsed = false;
|
|
340
|
-
|
|
341
|
-
// Per-parse alias literal → canonical node name (TD-18). Per C8.
|
|
342
|
-
// Used by sankey + chord link slots.
|
|
343
|
-
const nameAliasMap = new Map<string, string>();
|
|
344
|
-
/** Peel `as <alias>` and resolve bare alias references in one pass. */
|
|
345
|
-
function resolveSlot(raw: string): string {
|
|
346
|
-
const trimmed = raw.trim();
|
|
347
|
-
const m = trimmed.match(/^(.*?)\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/);
|
|
348
|
-
if (m) {
|
|
349
|
-
// Regex capture groups [1] and [2] are non-optional in the pattern.
|
|
350
|
-
const canonical = m[1]!.trim();
|
|
351
|
-
nameAliasMap.set(m[2]!, canonical);
|
|
352
|
-
return canonical;
|
|
353
|
-
}
|
|
354
|
-
const aliased = nameAliasMap.get(trimmed);
|
|
355
|
-
return aliased !== undefined ? aliased : trimmed;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
for (let i = 0; i < lines.length; i++) {
|
|
359
|
-
// In-bounds by loop guard.
|
|
360
|
-
const trimmed = lines[i]!.trim();
|
|
361
|
-
const lineNumber = i + 1;
|
|
362
|
-
|
|
363
|
-
// Skip empty lines
|
|
364
|
-
if (!trimmed) continue;
|
|
365
|
-
|
|
366
|
-
// Reject legacy ## category syntax
|
|
367
|
-
if (/^#{2,}\s+/.test(trimmed)) {
|
|
368
|
-
const name = trimmed
|
|
369
|
-
.replace(/^#{2,}\s+/, '')
|
|
370
|
-
.replace(/\s*\([^)]*\)\s*$/, '')
|
|
371
|
-
.trim();
|
|
372
|
-
result.diagnostics.push(
|
|
373
|
-
makeDgmoError(
|
|
374
|
-
lineNumber,
|
|
375
|
-
`'## ${name}' is no longer supported. Use '[${name}]' instead`
|
|
376
|
-
)
|
|
377
|
-
);
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Skip comments
|
|
382
|
-
if (trimmed.startsWith('//')) continue;
|
|
383
|
-
|
|
384
|
-
// First non-empty, non-comment line: chart type + optional title
|
|
385
|
-
if (!firstLineParsed) {
|
|
386
|
-
firstLineParsed = true;
|
|
387
|
-
const firstLine = parseFirstLine(trimmed);
|
|
388
|
-
// The extended-chart parser owns its own type vocabulary. `chord` is no
|
|
389
|
-
// longer a top-level chart-type keyword (decision #29), so parseFirstLine
|
|
390
|
-
// won't return it — but the arc `layout chord` override re-emits canonical
|
|
391
|
-
// `chord …` content that must still parse here. Recognize an extended type
|
|
392
|
-
// directly from the first token when parseFirstLine declines.
|
|
393
|
-
if (!firstLine) {
|
|
394
|
-
const rawType = trimmed.split(/\s+/)[0]!.toLowerCase();
|
|
395
|
-
if (VALID_EXTENDED_TYPES.has(rawType as ExtendedChartType)) {
|
|
396
|
-
result.type = rawType as ExtendedChartType;
|
|
397
|
-
const rest = trimmed.slice(rawType.length).trim();
|
|
398
|
-
if (rest) {
|
|
399
|
-
result.title = rest;
|
|
400
|
-
result.titleLineNumber = lineNumber;
|
|
401
|
-
}
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (firstLine) {
|
|
406
|
-
const chartType =
|
|
407
|
-
firstLine.chartType.toLowerCase() as ExtendedChartType;
|
|
408
|
-
if (VALID_EXTENDED_TYPES.has(chartType)) {
|
|
409
|
-
result.type = chartType;
|
|
410
|
-
if (firstLine.title) {
|
|
411
|
-
result.title = firstLine.title;
|
|
412
|
-
result.titleLineNumber = lineNumber;
|
|
413
|
-
}
|
|
414
|
-
continue;
|
|
415
|
-
} else {
|
|
416
|
-
const validTypes = [...VALID_EXTENDED_TYPES];
|
|
417
|
-
let msg = `Unsupported chart type: ${firstLine.chartType}. Supported types: ${validTypes.join(', ')}.`;
|
|
418
|
-
const hint = suggest(chartType, validTypes);
|
|
419
|
-
if (hint) msg += ` ${hint}`;
|
|
420
|
-
const diag = makeDgmoError(lineNumber, msg);
|
|
421
|
-
result.diagnostics.push(diag);
|
|
422
|
-
result.error = formatDgmoError(diag);
|
|
423
|
-
return result;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
// If the first line is a single word (no spaces, no colon, no numbers),
|
|
427
|
-
// treat it as an unrecognized chart type rather than falling through
|
|
428
|
-
if (
|
|
429
|
-
!trimmed.includes(' ') &&
|
|
430
|
-
!trimmed.includes(':') &&
|
|
431
|
-
!/\d/.test(trimmed)
|
|
432
|
-
) {
|
|
433
|
-
const validTypes = [...VALID_EXTENDED_TYPES];
|
|
434
|
-
let msg = `Unsupported chart type: ${trimmed}. Supported types: ${validTypes.join(', ')}.`;
|
|
435
|
-
const hint = suggest(trimmed.toLowerCase(), validTypes);
|
|
436
|
-
if (hint) msg += ` ${hint}`;
|
|
437
|
-
const diag = makeDgmoError(lineNumber, msg);
|
|
438
|
-
result.diagnostics.push(diag);
|
|
439
|
-
result.error = formatDgmoError(diag);
|
|
440
|
-
return result;
|
|
441
|
-
}
|
|
442
|
-
// Fall through — first line might be a data row or option
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// [Category] container header with optional color: [Category Name] or [Category Name](color)
|
|
446
|
-
// Category brackets with optional trailing-token color (§1.5):
|
|
447
|
-
// `[Name]` or `[Name] color`. Per universal rule, color is a bare token.
|
|
448
|
-
const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s+(\S+))?\s*$/);
|
|
449
|
-
if (categoryMatch) {
|
|
450
|
-
// Regex capture group [1] is non-optional in the pattern.
|
|
451
|
-
const catName = categoryMatch[1]!.trim();
|
|
452
|
-
const rawCatColor = categoryMatch[2]?.trim();
|
|
453
|
-
const catColor = rawCatColor
|
|
454
|
-
? (resolveColorWithDiagnostic(
|
|
455
|
-
rawCatColor,
|
|
456
|
-
lineNumber,
|
|
457
|
-
result.diagnostics,
|
|
458
|
-
palette
|
|
459
|
-
) ?? null)
|
|
460
|
-
: null;
|
|
461
|
-
if (catColor) {
|
|
462
|
-
if (!result.categoryColors) result.categoryColors = {};
|
|
463
|
-
result.categoryColors[catName] = catColor;
|
|
464
|
-
}
|
|
465
|
-
if (!result.categoryLineNumbers) result.categoryLineNumbers = {};
|
|
466
|
-
result.categoryLineNumbers[catName] = lineNumber;
|
|
467
|
-
currentCategory = catName;
|
|
468
|
-
continue;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Sankey/chord link syntax (§1.5 universal trailing-token):
|
|
472
|
-
// `Source -> Target value` (directed, no link color)
|
|
473
|
-
// `Source -> Target value linkColor` (directed, trailing-token link color)
|
|
474
|
-
// `Source -- Target value` (undirected)
|
|
475
|
-
// Link color (if present) must be a recognized lowercase palette word.
|
|
476
|
-
// Source/target labels still accept trailing-token color via extractColor.
|
|
477
|
-
const arrowMatch = trimmed.match(
|
|
478
|
-
/^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d_]+(?:\.[\d]+)?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
|
|
479
|
-
);
|
|
480
|
-
if (arrowMatch) {
|
|
481
|
-
const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
|
|
482
|
-
// Captures 1-4 are non-optional in the regex pattern.
|
|
483
|
-
const val = normalizeNumericToken(rawVal!) ?? rawVal!;
|
|
484
|
-
// TD-18: peel/resolve aliases on source and target before color extraction.
|
|
485
|
-
const sourceResolved = resolveSlot(rawSource!);
|
|
486
|
-
const targetResolved = resolveSlot(rawTarget!);
|
|
487
|
-
const { label: source, color: sourceColor } = extractColor(
|
|
488
|
-
sourceResolved,
|
|
489
|
-
palette,
|
|
490
|
-
result.diagnostics,
|
|
491
|
-
lineNumber
|
|
492
|
-
);
|
|
493
|
-
const { label: target, color: targetColor } = extractColor(
|
|
494
|
-
targetResolved,
|
|
495
|
-
palette,
|
|
496
|
-
result.diagnostics,
|
|
497
|
-
lineNumber
|
|
498
|
-
);
|
|
499
|
-
if (sourceColor || targetColor) {
|
|
500
|
-
if (!result.nodeColors) result.nodeColors = {};
|
|
501
|
-
if (sourceColor) result.nodeColors[source] = sourceColor;
|
|
502
|
-
if (targetColor) result.nodeColors[target] = targetColor;
|
|
503
|
-
}
|
|
504
|
-
const linkColor = rawLinkColor
|
|
505
|
-
? resolveColorWithDiagnostic(
|
|
506
|
-
rawLinkColor.trim(),
|
|
507
|
-
lineNumber,
|
|
508
|
-
result.diagnostics,
|
|
509
|
-
palette
|
|
510
|
-
)
|
|
511
|
-
: undefined;
|
|
512
|
-
if (!result.links) result.links = [];
|
|
513
|
-
result.links.push({
|
|
514
|
-
source,
|
|
515
|
-
target,
|
|
516
|
-
value: parseFloat(val),
|
|
517
|
-
...(linkColor && { color: linkColor }),
|
|
518
|
-
directed: arrow === '->',
|
|
519
|
-
lineNumber,
|
|
520
|
-
});
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Sankey: bare label (no numeric value) at any indent = source node for indented children
|
|
525
|
-
if (result.type === 'sankey') {
|
|
526
|
-
// In-bounds by loop guard (i < lines.length).
|
|
527
|
-
const indent = measureIndent(lines[i]!);
|
|
528
|
-
// Sankey indented child: " Target value (color)" under a source on the stack
|
|
529
|
-
if (indent > 0 && sankeyStack.length > 0) {
|
|
530
|
-
// Pop entries at same or deeper indent to find the parent
|
|
531
|
-
while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
|
|
532
|
-
sankeyStack.pop();
|
|
533
|
-
}
|
|
534
|
-
if (sankeyStack.length > 0) {
|
|
535
|
-
// Indented sankey child (§1.5 trailing-token):
|
|
536
|
-
// `TargetName value` — link, no link color
|
|
537
|
-
// `TargetName value linkColor` — link with link color
|
|
538
|
-
// `TargetName nodeColor value` — node-colored child
|
|
539
|
-
// `TargetName nodeColor value linkColor` — both
|
|
540
|
-
// Strategy: peel a trailing recognized color word (after the value)
|
|
541
|
-
// first, then run parseDataRowValues on the remainder. Trailing
|
|
542
|
-
// tokens that aren't recognized colors stay in the data row.
|
|
543
|
-
const valColorMatch = trimmed.match(
|
|
544
|
-
/(-?[\d_]+(?:\.[\d]+)?)\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/
|
|
545
|
-
);
|
|
546
|
-
const strippedLine = valColorMatch
|
|
547
|
-
? trimmed.replace(
|
|
548
|
-
/\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/,
|
|
549
|
-
''
|
|
550
|
-
)
|
|
551
|
-
: trimmed;
|
|
552
|
-
const dataRow = parseDataRowValues(strippedLine);
|
|
553
|
-
if (dataRow?.values.length === 1) {
|
|
554
|
-
const source = sankeyStack.at(-1)!.name;
|
|
555
|
-
const linkColor = valColorMatch?.[2]
|
|
556
|
-
? resolveColorWithDiagnostic(
|
|
557
|
-
valColorMatch[2].trim(),
|
|
558
|
-
lineNumber,
|
|
559
|
-
result.diagnostics,
|
|
560
|
-
palette
|
|
561
|
-
)
|
|
562
|
-
: undefined;
|
|
563
|
-
// TD-18: peel/resolve alias on the indented child target name.
|
|
564
|
-
const targetResolved = resolveSlot(dataRow.label);
|
|
565
|
-
const { label: target, color: targetColor } = extractColor(
|
|
566
|
-
targetResolved,
|
|
567
|
-
palette,
|
|
568
|
-
result.diagnostics,
|
|
569
|
-
lineNumber
|
|
570
|
-
);
|
|
571
|
-
if (targetColor) {
|
|
572
|
-
if (!result.nodeColors) result.nodeColors = {};
|
|
573
|
-
result.nodeColors[target] = targetColor;
|
|
574
|
-
}
|
|
575
|
-
if (!result.links) result.links = [];
|
|
576
|
-
result.links.push({
|
|
577
|
-
source,
|
|
578
|
-
target,
|
|
579
|
-
// In-bounds by values.length === 1 guard above.
|
|
580
|
-
value: dataRow.values[0]!,
|
|
581
|
-
...(linkColor && { color: linkColor }),
|
|
582
|
-
lineNumber,
|
|
583
|
-
});
|
|
584
|
-
sankeyStack.push({ name: target, indent });
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Bare label at indent 0 (or any indent without a value) = new source node.
|
|
591
|
-
// Skip cross-chart bare-keyword options so they're handled by the
|
|
592
|
-
// bare-keyword block below instead of becoming a phantom "solid-fill" node.
|
|
593
|
-
const spaceIdx = trimmed.indexOf(' ');
|
|
594
|
-
const lastTok = trimmed.substring(trimmed.lastIndexOf(' ') + 1);
|
|
595
|
-
const hasNumericSuffix =
|
|
596
|
-
spaceIdx >= 0 &&
|
|
597
|
-
!isNaN(parseFloat(normalizeNumericToken(lastTok) ?? lastTok));
|
|
598
|
-
const isBareKeywordOption =
|
|
599
|
-
spaceIdx < 0 &&
|
|
600
|
-
/^(solid-fill|no-name|no-value|no-percent|shade|no-title)$/i.test(
|
|
601
|
-
trimmed
|
|
602
|
-
);
|
|
603
|
-
// `layout arc|chord` (#26) is a directive, not a node — let it fall through
|
|
604
|
-
// to the valued-option handler below instead of becoming a phantom node.
|
|
605
|
-
const isLayoutDirective =
|
|
606
|
-
spaceIdx >= 0 &&
|
|
607
|
-
trimmed.substring(0, spaceIdx).toLowerCase() === 'layout';
|
|
608
|
-
if (!hasNumericSuffix && !isBareKeywordOption && !isLayoutDirective) {
|
|
609
|
-
while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
|
|
610
|
-
sankeyStack.pop();
|
|
611
|
-
}
|
|
612
|
-
// TD-18: peel/resolve alias on the bare source node label.
|
|
613
|
-
const trimmedResolved = resolveSlot(trimmed);
|
|
614
|
-
const { label: nodeName, color: nodeColor } = extractColor(
|
|
615
|
-
trimmedResolved,
|
|
616
|
-
palette,
|
|
617
|
-
result.diagnostics,
|
|
618
|
-
lineNumber
|
|
619
|
-
);
|
|
620
|
-
if (nodeColor) {
|
|
621
|
-
if (!result.nodeColors) result.nodeColors = {};
|
|
622
|
-
result.nodeColors[nodeName] = nodeColor;
|
|
623
|
-
}
|
|
624
|
-
sankeyStack.push({ name: nodeName, indent });
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// Extract first token to check for known options
|
|
630
|
-
const spaceIdx = trimmed.indexOf(' ');
|
|
631
|
-
const firstToken = (
|
|
632
|
-
spaceIdx >= 0 ? trimmed.substring(0, spaceIdx) : trimmed
|
|
633
|
-
).toLowerCase();
|
|
634
|
-
|
|
635
|
-
// Known option with a value
|
|
636
|
-
if (KNOWN_EXTENDED_OPTIONS.has(firstToken) && spaceIdx >= 0) {
|
|
637
|
-
const value = trimmed.substring(spaceIdx + 1).trim();
|
|
638
|
-
|
|
639
|
-
if (firstToken === 'chart') {
|
|
640
|
-
const chartType = value.toLowerCase() as ExtendedChartType;
|
|
641
|
-
if (VALID_EXTENDED_TYPES.has(chartType)) {
|
|
642
|
-
result.type = chartType;
|
|
643
|
-
} else {
|
|
644
|
-
const validTypes = [...VALID_EXTENDED_TYPES];
|
|
645
|
-
let msg = `Unsupported chart type: ${value}. Supported types: ${validTypes.join(', ')}.`;
|
|
646
|
-
const hint = suggest(chartType, validTypes);
|
|
647
|
-
if (hint) msg += ` ${hint}`;
|
|
648
|
-
const diag = makeDgmoError(lineNumber, msg);
|
|
649
|
-
result.diagnostics.push(diag);
|
|
650
|
-
result.error = formatDgmoError(diag);
|
|
651
|
-
return result;
|
|
652
|
-
}
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
if (firstToken === 'title') {
|
|
657
|
-
result.title = value;
|
|
658
|
-
result.titleLineNumber = lineNumber;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
if (firstToken === 'series') {
|
|
663
|
-
const parsed = parseSeriesNames(value, lines, i, palette);
|
|
664
|
-
i = parsed.newIndex;
|
|
665
|
-
result.series = parsed.series;
|
|
666
|
-
result.seriesLineNumber = lineNumber;
|
|
667
|
-
if (parsed.names.length > 1) {
|
|
668
|
-
result.seriesNames = parsed.names;
|
|
669
|
-
result.seriesNameLineNumbers = parsed.nameLineNumbers;
|
|
670
|
-
}
|
|
671
|
-
if (parsed.nameColors.some(Boolean))
|
|
672
|
-
result.seriesNameColors = parsed.nameColors;
|
|
673
|
-
continue;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (firstToken === 'x-label') {
|
|
677
|
-
result.xlabel = value;
|
|
678
|
-
result.xlabelLineNumber = lineNumber;
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (firstToken === 'y-label') {
|
|
682
|
-
result.ylabel = value;
|
|
683
|
-
result.ylabelLineNumber = lineNumber;
|
|
684
|
-
continue;
|
|
685
|
-
}
|
|
686
|
-
if (firstToken === 'size-label') {
|
|
687
|
-
result.sizelabel = value;
|
|
688
|
-
continue;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// `layout arc|chord` (#26): render the same pairwise edges as the other
|
|
692
|
-
// preset. `arc` → linear arc; default `chord` → circular.
|
|
693
|
-
if (firstToken === 'layout') {
|
|
694
|
-
const v = value.toLowerCase();
|
|
695
|
-
if (v === 'arc' || v === 'chord') result.layout = v;
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
if (firstToken === 'columns') {
|
|
700
|
-
if (value) {
|
|
701
|
-
result.columns = value.includes(',')
|
|
702
|
-
? value.split(',').map((s) => s.trim())
|
|
703
|
-
: value.split(/\s+/);
|
|
704
|
-
} else {
|
|
705
|
-
const collected = collectIndentedValues(lines, i);
|
|
706
|
-
i = collected.newIndex;
|
|
707
|
-
result.columns = collected.values;
|
|
708
|
-
}
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (firstToken === 'rows') {
|
|
713
|
-
if (value) {
|
|
714
|
-
result.rows = value.includes(',')
|
|
715
|
-
? value.split(',').map((s) => s.trim())
|
|
716
|
-
: value.split(/\s+/);
|
|
717
|
-
} else {
|
|
718
|
-
const collected = collectIndentedValues(lines, i);
|
|
719
|
-
i = collected.newIndex;
|
|
720
|
-
result.rows = collected.values;
|
|
721
|
-
}
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
if (firstToken === 'x') {
|
|
726
|
-
const rangeMatch = value.match(/^(-?[\d.]+)\s+to\s+(-?[\d.]+)$/);
|
|
727
|
-
if (rangeMatch) {
|
|
728
|
-
result.xRange = {
|
|
729
|
-
// Regex capture groups [1] and [2] are non-optional in the pattern.
|
|
730
|
-
min: parseFloat(rangeMatch[1]!),
|
|
731
|
-
max: parseFloat(rangeMatch[2]!),
|
|
732
|
-
};
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
// The `x` keyword owns ONLY the `x <min> to <max>` range form. A
|
|
736
|
-
// function curve can legitimately start with `x` (e.g. `x / 2: x / 2`);
|
|
737
|
-
// such a colon-bearing line must fall through to the function-curve
|
|
738
|
-
// handler below instead of being silently swallowed here.
|
|
739
|
-
if (!(result.type === 'function' && trimmed.includes(':'))) {
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Bare boolean options
|
|
746
|
-
if (firstToken === 'no-name') {
|
|
747
|
-
result.noName = true;
|
|
748
|
-
continue;
|
|
749
|
-
}
|
|
750
|
-
if (firstToken === 'no-value') {
|
|
751
|
-
result.noValue = true;
|
|
752
|
-
continue;
|
|
753
|
-
}
|
|
754
|
-
if (firstToken === 'no-percent') {
|
|
755
|
-
result.noPercent = true;
|
|
756
|
-
continue;
|
|
757
|
-
}
|
|
758
|
-
if (firstToken === 'shade') {
|
|
759
|
-
result.shade = true;
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
if (firstToken === 'solid-fill') {
|
|
763
|
-
result.solidFill = true;
|
|
764
|
-
continue;
|
|
765
|
-
}
|
|
766
|
-
if (firstToken === 'no-title' && spaceIdx < 0) {
|
|
767
|
-
result.noTitle = true;
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
// Silent-ignore unrecognized no-* flags (typos, future flags).
|
|
771
|
-
if (firstToken.startsWith('no-') && spaceIdx < 0) {
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Bare keyword options (no value)
|
|
776
|
-
if (firstToken === 'series' && spaceIdx === -1) {
|
|
777
|
-
const parsed = parseSeriesNames('', lines, i, palette);
|
|
778
|
-
i = parsed.newIndex;
|
|
779
|
-
result.series = parsed.series;
|
|
780
|
-
result.seriesLineNumber = lineNumber;
|
|
781
|
-
if (parsed.names.length > 1) {
|
|
782
|
-
result.seriesNames = parsed.names;
|
|
783
|
-
result.seriesNameLineNumbers = parsed.nameLineNumbers;
|
|
784
|
-
}
|
|
785
|
-
if (parsed.nameColors.some(Boolean))
|
|
786
|
-
result.seriesNameColors = parsed.nameColors;
|
|
787
|
-
continue;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
if (firstToken === 'columns' && spaceIdx === -1) {
|
|
791
|
-
const collected = collectIndentedValues(lines, i);
|
|
792
|
-
i = collected.newIndex;
|
|
793
|
-
result.columns = collected.values;
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
if (firstToken === 'rows' && spaceIdx === -1) {
|
|
798
|
-
const collected = collectIndentedValues(lines, i);
|
|
799
|
-
i = collected.newIndex;
|
|
800
|
-
result.rows = collected.values;
|
|
801
|
-
continue;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Function chart: "name expression" where name may contain parens like f(x)
|
|
805
|
-
// Must use colon to separate name from expression since both can contain spaces
|
|
806
|
-
if (result.type === 'function') {
|
|
807
|
-
const colonIndex = trimmed.indexOf(':');
|
|
808
|
-
if (colonIndex >= 0) {
|
|
809
|
-
const { label: fnName, color: fnColor } = extractColor(
|
|
810
|
-
trimmed.substring(0, colonIndex).trim(),
|
|
811
|
-
palette,
|
|
812
|
-
result.diagnostics,
|
|
813
|
-
lineNumber
|
|
814
|
-
);
|
|
815
|
-
const fnValue = trimmed.substring(colonIndex + 1).trim();
|
|
816
|
-
if (!result.functions) result.functions = [];
|
|
817
|
-
result.functions.push({
|
|
818
|
-
name: fnName,
|
|
819
|
-
expression: fnValue,
|
|
820
|
-
...(fnColor && { color: fnColor }),
|
|
821
|
-
lineNumber,
|
|
822
|
-
});
|
|
823
|
-
continue;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Scatter chart: "Name x, y" or "Name x, y, size"
|
|
828
|
-
if (result.type === 'scatter') {
|
|
829
|
-
// Parse from right: trailing comma-separated numbers are x, y [, size]
|
|
830
|
-
const scatterData = parseScatterRow(
|
|
831
|
-
trimmed,
|
|
832
|
-
palette,
|
|
833
|
-
currentCategory,
|
|
834
|
-
lineNumber,
|
|
835
|
-
result.diagnostics
|
|
836
|
-
);
|
|
837
|
-
if (scatterData) {
|
|
838
|
-
if (!result.scatterPoints) result.scatterPoints = [];
|
|
839
|
-
result.scatterPoints.push(scatterData);
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Heatmap data row: "RowLabel val1, val2, val3, ..." or "RowLabel val1 val2 val3"
|
|
845
|
-
if (result.type === 'heatmap') {
|
|
846
|
-
const dataRow = parseDataRowValues(trimmed, { multiValue: true });
|
|
847
|
-
if (dataRow && dataRow.values.length > 0) {
|
|
848
|
-
if (!result.heatmapRows) result.heatmapRows = [];
|
|
849
|
-
result.heatmapRows.push({
|
|
850
|
-
label: dataRow.label,
|
|
851
|
-
values: dataRow.values,
|
|
852
|
-
lineNumber,
|
|
853
|
-
});
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// Funnel / generic data point: "Label value"
|
|
859
|
-
const dataRow = parseDataRowValues(trimmed);
|
|
860
|
-
if (dataRow?.values.length === 1) {
|
|
861
|
-
const { label: rawLabel, color: pointColor } = extractColor(
|
|
862
|
-
dataRow.label,
|
|
863
|
-
palette,
|
|
864
|
-
result.diagnostics,
|
|
865
|
-
lineNumber
|
|
866
|
-
);
|
|
867
|
-
result.data.push({
|
|
868
|
-
label: rawLabel,
|
|
869
|
-
// In-bounds by values.length === 1 guard above.
|
|
870
|
-
value: dataRow.values[0]!,
|
|
871
|
-
...(pointColor && { color: pointColor }),
|
|
872
|
-
lineNumber,
|
|
873
|
-
});
|
|
874
|
-
continue;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// Catch-all: nothing matched this line
|
|
878
|
-
result.diagnostics.push(
|
|
879
|
-
makeDgmoError(
|
|
880
|
-
lineNumber,
|
|
881
|
-
`Unexpected line: '${trimmed}'. Expected a data row ('Label value', space-separated) or a known option.`,
|
|
882
|
-
'warning'
|
|
883
|
-
)
|
|
884
|
-
);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
const warn = (line: number, message: string): void => {
|
|
888
|
-
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
889
|
-
};
|
|
890
|
-
|
|
891
|
-
if (!result.error) {
|
|
892
|
-
if (result.type === 'sankey') {
|
|
893
|
-
if (!result.links || result.links.length === 0) {
|
|
894
|
-
warn(1, 'No links found. Add links in format: Source -> Target 123');
|
|
895
|
-
}
|
|
896
|
-
} else if (result.type === 'chord') {
|
|
897
|
-
if (!result.links || result.links.length === 0) {
|
|
898
|
-
warn(1, 'No links found. Add links in format: Source -> Target 123');
|
|
899
|
-
}
|
|
900
|
-
} else if (result.type === 'function') {
|
|
901
|
-
if (!result.functions || result.functions.length === 0) {
|
|
902
|
-
warn(
|
|
903
|
-
1,
|
|
904
|
-
'No functions found. Add functions in format: Name: expression'
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
if (!result.xRange) {
|
|
908
|
-
result.xRange = { min: -10, max: 10 }; // Default range
|
|
909
|
-
}
|
|
910
|
-
} else if (result.type === 'scatter') {
|
|
911
|
-
if (!result.scatterPoints || result.scatterPoints.length === 0) {
|
|
912
|
-
warn(
|
|
913
|
-
1,
|
|
914
|
-
'No scatter points found. Add points in format: Name: x, y or Name: x, y, size'
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
} else if (result.type === 'heatmap') {
|
|
918
|
-
if (!result.heatmapRows || result.heatmapRows.length === 0) {
|
|
919
|
-
warn(
|
|
920
|
-
1,
|
|
921
|
-
'No heatmap data found. Add data in format: RowLabel: val1, val2, val3'
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
if (!result.columns || result.columns.length === 0) {
|
|
925
|
-
warn(
|
|
926
|
-
1,
|
|
927
|
-
'No columns defined. Add columns in format: columns: Col1, Col2, Col3'
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
} else if (result.type === 'funnel') {
|
|
931
|
-
if (result.data.length === 0) {
|
|
932
|
-
warn(1, 'No data found. Add data in format: Label: value');
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
return result;
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
// ============================================================
|
|
941
|
-
// ECharts Option Builder
|
|
942
|
-
// ============================================================
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Extracts legend group data from standard chart types (multi-series line/bar).
|
|
946
|
-
* Returns empty array if chart has no multi-series legend.
|
|
947
|
-
*/
|
|
948
|
-
export function getSimpleChartLegendGroups(
|
|
949
|
-
parsed: ParsedChart,
|
|
950
|
-
colors: string[]
|
|
951
|
-
): LegendGroupData[] {
|
|
952
|
-
if (!parsed.seriesNames || parsed.seriesNames.length <= 1) return [];
|
|
953
|
-
return [
|
|
954
|
-
{
|
|
955
|
-
name: 'Series',
|
|
956
|
-
entries: parsed.seriesNames.map((name, i) => ({
|
|
957
|
-
value: name,
|
|
958
|
-
// colors is a non-empty palette array; modulo index is always in-bounds.
|
|
959
|
-
color: parsed.seriesNameColors?.[i] ?? colors[i % colors.length]!,
|
|
960
|
-
})),
|
|
961
|
-
},
|
|
962
|
-
];
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
/**
|
|
966
|
-
* Extracts legend group data from extended chart types.
|
|
967
|
-
* Supports scatter (categories), chord (nodes), and function (series).
|
|
968
|
-
*/
|
|
969
|
-
export function getExtendedChartLegendGroups(
|
|
970
|
-
parsed: ParsedExtendedChart,
|
|
971
|
-
colors: string[]
|
|
972
|
-
): LegendGroupData[] {
|
|
973
|
-
if (parsed.type === 'scatter') {
|
|
974
|
-
const points = parsed.scatterPoints ?? [];
|
|
975
|
-
const categories = [
|
|
976
|
-
...new Set(points.map((p) => p.category).filter(Boolean)),
|
|
977
|
-
] as string[];
|
|
978
|
-
if (categories.length === 0) return [];
|
|
979
|
-
return [
|
|
980
|
-
{
|
|
981
|
-
name: 'Group',
|
|
982
|
-
entries: categories.map((cat, i) => ({
|
|
983
|
-
value: cat,
|
|
984
|
-
// colors is a non-empty palette array; modulo index is always in-bounds.
|
|
985
|
-
color: parsed.categoryColors?.[cat] ?? colors[i % colors.length]!,
|
|
986
|
-
})),
|
|
987
|
-
},
|
|
988
|
-
];
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
if (parsed.type === 'function') {
|
|
992
|
-
const fns = parsed.functions ?? [];
|
|
993
|
-
if (fns.length === 0) return [];
|
|
994
|
-
return [
|
|
995
|
-
{
|
|
996
|
-
name: 'Function',
|
|
997
|
-
entries: fns.map((fn, i) => ({
|
|
998
|
-
value: fn.name,
|
|
999
|
-
// colors is a non-empty palette array; modulo index is always in-bounds.
|
|
1000
|
-
color: fn.color ?? colors[i % colors.length]!,
|
|
1001
|
-
})),
|
|
1002
|
-
},
|
|
1003
|
-
];
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
return [];
|
|
1007
|
-
}
|