@opendata-ai/openchart-core 8.0.0-rc.2 → 8.0.0-rc.21

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 (82) hide show
  1. package/dist/index.d.ts +490 -25
  2. package/dist/index.js +57 -19
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/dist/styles.css.map +1 -1
  6. package/package.json +1 -2
  7. package/schema/chart.schema.json +485 -35
  8. package/schema/table.schema.json +35 -1
  9. package/schema/vizspec.schema.json +1305 -120
  10. package/src/accessibility/__tests__/alt-text.test.ts +0 -110
  11. package/src/accessibility/__tests__/aria.test.ts +0 -125
  12. package/src/accessibility/alt-text.ts +0 -155
  13. package/src/accessibility/aria.ts +0 -75
  14. package/src/accessibility/index.ts +0 -6
  15. package/src/colors/__tests__/colorblind.test.ts +0 -75
  16. package/src/colors/__tests__/contrast.test.ts +0 -119
  17. package/src/colors/__tests__/palettes.test.ts +0 -74
  18. package/src/colors/colorblind.ts +0 -131
  19. package/src/colors/contrast.ts +0 -143
  20. package/src/colors/index.ts +0 -36
  21. package/src/colors/palettes.ts +0 -206
  22. package/src/helpers/__tests__/deep-merge.test.ts +0 -54
  23. package/src/helpers/__tests__/spec-builders.test.ts +0 -338
  24. package/src/helpers/deep-merge.ts +0 -29
  25. package/src/helpers/spec-builders.ts +0 -479
  26. package/src/index.ts +0 -215
  27. package/src/labels/__tests__/collision.test.ts +0 -310
  28. package/src/labels/collision.ts +0 -249
  29. package/src/labels/index.ts +0 -13
  30. package/src/layout/__tests__/chrome.test.ts +0 -208
  31. package/src/layout/__tests__/text-measure.test.ts +0 -57
  32. package/src/layout/chrome.ts +0 -470
  33. package/src/layout/index.ts +0 -18
  34. package/src/layout/text-measure.ts +0 -404
  35. package/src/layout/text-wrap.ts +0 -93
  36. package/src/locale/__tests__/format.test.ts +0 -606
  37. package/src/locale/format.ts +0 -459
  38. package/src/locale/index.ts +0 -21
  39. package/src/responsive/__tests__/breakpoints.test.ts +0 -146
  40. package/src/responsive/__tests__/metrics.test.ts +0 -52
  41. package/src/responsive/breakpoints.ts +0 -172
  42. package/src/responsive/index.ts +0 -53
  43. package/src/responsive/metrics.ts +0 -299
  44. package/src/schema/__tests__/generated-artifacts.test.ts +0 -42
  45. package/src/schema/__tests__/schema.test.ts +0 -179
  46. package/src/styles/__tests__/reduced-motion-selectors.test.ts +0 -28
  47. package/src/styles/animation.css +0 -199
  48. package/src/styles/base.css +0 -102
  49. package/src/styles/chrome.css +0 -174
  50. package/src/styles/dark.css +0 -44
  51. package/src/styles/graph.css +0 -135
  52. package/src/styles/index.css +0 -28
  53. package/src/styles/keyframes.css +0 -135
  54. package/src/styles/legend.css +0 -18
  55. package/src/styles/reduced-motion.css +0 -67
  56. package/src/styles/series-search.css +0 -153
  57. package/src/styles/sparkline.css +0 -16
  58. package/src/styles/table-animation.css +0 -93
  59. package/src/styles/table.css +0 -398
  60. package/src/styles/token-definitions.ts +0 -199
  61. package/src/styles/tokens.css +0 -156
  62. package/src/styles/tooltip.css +0 -126
  63. package/src/styles/you-draw-it.css +0 -126
  64. package/src/theme/__tests__/dark-mode.test.ts +0 -232
  65. package/src/theme/__tests__/defaults.test.ts +0 -73
  66. package/src/theme/__tests__/resolve.test.ts +0 -277
  67. package/src/theme/dark-mode.ts +0 -258
  68. package/src/theme/defaults.ts +0 -110
  69. package/src/theme/index.ts +0 -8
  70. package/src/theme/presets.ts +0 -64
  71. package/src/theme/resolve.ts +0 -312
  72. package/src/types/__tests__/encoding.test.ts +0 -368
  73. package/src/types/__tests__/gradient.test.ts +0 -95
  74. package/src/types/__tests__/spec.test.ts +0 -499
  75. package/src/types/encoding.ts +0 -280
  76. package/src/types/events.ts +0 -154
  77. package/src/types/index.ts +0 -287
  78. package/src/types/layout.ts +0 -1966
  79. package/src/types/schema-roots.ts +0 -32
  80. package/src/types/spec.ts +0 -2867
  81. package/src/types/table.ts +0 -108
  82. package/src/types/theme.ts +0 -209
package/src/types/spec.ts DELETED
@@ -1,2867 +0,0 @@
1
- /**
2
- * Spec types: the user-facing input contract.
3
- *
4
- * These types define what a user (or Claude) writes to describe a visualization.
5
- * The engine validates, normalizes, and compiles specs into layout objects.
6
- *
7
- * Encoding vocabulary follows Vega-Lite conventions (field/type/aggregate/mark)
8
- * with editorial extensions for chrome, annotations, responsive, and dark mode.
9
- */
10
-
11
- // Re-import for use in LegendConfig and overrides (avoids circular by importing from sibling)
12
- import type { Breakpoint, LegendPosition } from '../responsive/breakpoints';
13
- import type { ColumnConfig } from './table';
14
- import type { SeriesStrategy, TokenValue } from './theme';
15
-
16
- // ---------------------------------------------------------------------------
17
- // Mark type union (Vega-Lite aligned)
18
- // ---------------------------------------------------------------------------
19
-
20
- /**
21
- * Supported mark types, following Vega-Lite conventions.
22
- *
23
- * Mapping from previous OpenChart chart types:
24
- * - 'bar' covers both horizontal bars (old 'bar') and vertical columns (old 'column')
25
- * - 'arc' covers both pie (old 'pie') and donut (old 'donut')
26
- * - 'point' replaces old 'scatter'
27
- * - 'circle' replaces old 'dot'
28
- * - 'line' and 'area' unchanged
29
- *
30
- * New mark types not in previous OpenChart:
31
- * - 'text': data-positioned text labels
32
- * - 'rule': reference lines as data marks
33
- * - 'tick': strip/rug plot marks
34
- * - 'rect': heatmaps and 2D binned plots
35
- * - 'range': dumbbell / arrow / range-bar plots spanning two values per category
36
- * - 'waffle': unit grids ("x of 100") for part-to-whole counts
37
- * - 'calendar': GitHub-style calendar heatmap (weeks x weekdays, daily dates)
38
- * - 'parliament': hemicycle seat-dot chart for election/legislature results
39
- */
40
- export type MarkType =
41
- | 'bar'
42
- | 'line'
43
- | 'area'
44
- | 'point'
45
- | 'circle'
46
- | 'arc'
47
- | 'text'
48
- | 'rule'
49
- | 'tick'
50
- | 'rect'
51
- | 'lollipop'
52
- | 'beeswarm'
53
- | 'range'
54
- | 'waffle'
55
- | 'calendar'
56
- | 'parliament';
57
-
58
- // ---------------------------------------------------------------------------
59
- // Gradient definitions (Vega-aligned)
60
- // ---------------------------------------------------------------------------
61
-
62
- /** A single color stop in a gradient definition. */
63
- export interface GradientStop {
64
- /** Position along the gradient, 0 to 1. */
65
- offset: number;
66
- /** CSS color string at this stop. */
67
- color: string;
68
- /** Opacity at this stop, 0 to 1. Maps to SVG stop-opacity. */
69
- opacity?: number;
70
- }
71
-
72
- /**
73
- * Linear gradient definition.
74
- * Coordinates are in [0,1] normalized space relative to the mark's bounding box
75
- * (maps to SVG gradientUnits="objectBoundingBox").
76
- */
77
- export interface LinearGradient {
78
- gradient: 'linear';
79
- /** Color stops from start to end. */
80
- stops: GradientStop[];
81
- /** Start x coordinate (0-1). Default: 0. */
82
- x1?: number;
83
- /** Start y coordinate (0-1). Default: 0. */
84
- y1?: number;
85
- /** End x coordinate (0-1). Default: 0. */
86
- x2?: number;
87
- /** End y coordinate (0-1). Default: 1 (top-to-bottom). */
88
- y2?: number;
89
- }
90
-
91
- /**
92
- * Radial gradient definition.
93
- * Coordinates are in [0,1] normalized space relative to the mark's bounding box.
94
- */
95
- export interface RadialGradient {
96
- gradient: 'radial';
97
- /** Color stops from inner to outer. */
98
- stops: GradientStop[];
99
- /** Inner circle center x (0-1). Default: 0.5. */
100
- x1?: number;
101
- /** Inner circle center y (0-1). Default: 0.5. */
102
- y1?: number;
103
- /** Inner circle radius (0-1). Default: 0. */
104
- r1?: number;
105
- /** Outer circle center x (0-1). Default: 0.5. */
106
- x2?: number;
107
- /** Outer circle center y (0-1). Default: 0.5. */
108
- y2?: number;
109
- /** Outer circle radius (0-1). Default: 0.5. */
110
- r2?: number;
111
- }
112
-
113
- /** A gradient definition, either linear or radial. */
114
- export type GradientDef = LinearGradient | RadialGradient;
115
-
116
- /** Type guard: check if a value is a GradientDef object. */
117
- export function isGradientDef(value: unknown): value is GradientDef {
118
- return (
119
- typeof value === 'object' &&
120
- value !== null &&
121
- 'gradient' in value &&
122
- 'stops' in value &&
123
- ((value as GradientDef).gradient === 'linear' || (value as GradientDef).gradient === 'radial')
124
- );
125
- }
126
-
127
- /**
128
- * Extract a single representative color from a fill value.
129
- * Returns the fill directly if it's a string, or the last stop color
130
- * if it's a gradient. Used by tooltips, labels, legends, and voronoi
131
- * overlays that need a flat color.
132
- */
133
- export function getRepresentativeColor(fill: string | GradientDef): string {
134
- if (typeof fill === 'string') return fill;
135
- const stops = fill.stops;
136
- return stops.length > 0 ? stops[stops.length - 1].color : '#000000';
137
- }
138
-
139
- // ---------------------------------------------------------------------------
140
- // Mark definition (Vega-Lite aligned)
141
- // ---------------------------------------------------------------------------
142
-
143
- /**
144
- * Mark definition object with visual properties.
145
- *
146
- * When `mark` is a string, it's shorthand for `{ type: markString }`.
147
- * When it's an object, it can include properties like interpolation,
148
- * point markers, orientation, and more.
149
- */
150
- export interface MarkDef {
151
- /** The mark type. */
152
- type: MarkType;
153
- /**
154
- * Show point markers on line/area marks.
155
- * - true: filled circles at each data point
156
- * - 'transparent': invisible hover targets (legacy behavior)
157
- * - 'endpoints': show only first and last point per series (hollow dots)
158
- * - 'last' / 'first': show only the last (or first) point — filled dot,
159
- * no white halo. Useful for highlighting the latest reading on a line
160
- * chart and the default for sparkline mode.
161
- * - false: no point marks (default; uses voronoi overlay for tooltips)
162
- */
163
- point?: boolean | 'transparent' | 'endpoints' | 'last' | 'first';
164
- /**
165
- * Curve interpolation for line/area marks.
166
- * Maps to d3-shape curve factories.
167
- */
168
- interpolate?:
169
- | 'linear'
170
- | 'monotone'
171
- | 'step'
172
- | 'step-before'
173
- | 'step-after'
174
- | 'basis'
175
- | 'cardinal'
176
- | 'natural';
177
- /** Explicit orientation override for bar marks. */
178
- orient?: 'horizontal' | 'vertical';
179
- /** Inner radius for arc marks. >0 produces a donut, 0 or omitted produces a pie. */
180
- innerRadius?: number;
181
- /** Outer radius for arc marks. */
182
- outerRadius?: number;
183
- /**
184
- * Start angle in radians for arc marks. Defaults to 0 (full pie/donut).
185
- * Combined with `endAngle`, restricts the pie/donut to a partial sweep,
186
- * e.g. `startAngle: -Math.PI / 2, endAngle: Math.PI / 2` for a half-donut
187
- * (election-style results donut). Angles follow d3's convention: 0 is
188
- * straight up, increasing clockwise. Only meaningful when `type` is `'arc'`.
189
- */
190
- startAngle?: number;
191
- /**
192
- * End angle in radians for arc marks. Defaults to 2*Math.PI (full pie/donut).
193
- * See `startAngle`. Only meaningful when `type` is `'arc'`.
194
- */
195
- endAngle?: number;
196
- /** Corner radius for rect/bar marks. 'pill' sets rx to half the bar thickness. */
197
- cornerRadius?: number | 'pill';
198
- /** Fixed bar thickness in pixels for bar/column marks. When set, bars are this height (horizontal) or width (vertical), centered within the band. */
199
- size?: number;
200
- /** Whether the mark is filled (vs stroked only). */
201
- filled?: boolean;
202
- /** Default opacity (0-1). */
203
- opacity?: number;
204
- /** Default fill color or gradient. */
205
- fill?: string | GradientDef;
206
- /** Default stroke color. */
207
- stroke?: string;
208
- /** Default stroke width. */
209
- strokeWidth?: number;
210
- /** Tooltip behavior. null disables tooltips. */
211
- tooltip?: boolean | null;
212
- /** Clip marks to the chart area. */
213
- clip?: boolean;
214
- /**
215
- * Overlay a least-squares regression line on scatter (point) marks.
216
- * Defaults to true. Set `mark: { type: 'point', trendline: false }` to
217
- * suppress it when the chart already carries its own reference line (e.g. a
218
- * manual x=y diagonal in a separate layer), which would otherwise produce
219
- * two competing diagonals.
220
- */
221
- trendline?: boolean;
222
- /**
223
- * Horizontal pixel offset from the data anchor. Only meaningful when `type`
224
- * is `'text'`. Use it to lift a label clear of the mark it annotates instead
225
- * of shifting the underlying data value.
226
- */
227
- dx?: number;
228
- /**
229
- * Vertical pixel offset from the data anchor (negative = up). Only meaningful
230
- * when `type` is `'text'`. `{ type: 'text', dy: -10 }` floats each label ten
231
- * pixels above its point.
232
- */
233
- dy?: number;
234
- /** Horizontal alignment of text marks relative to their anchor. Defaults to `'center'`. */
235
- align?: 'left' | 'center' | 'right';
236
- /** Vertical alignment of text marks relative to their anchor. Defaults to `'middle'`. */
237
- baseline?: 'top' | 'middle' | 'bottom';
238
- /**
239
- * Font size in pixels for text marks. Ignored when the `size` encoding channel
240
- * is present (which scales font size from a data field). `size` is already
241
- * taken by bar thickness, hence the distinct name.
242
- */
243
- fontSize?: number;
244
- /**
245
- * Pattern-fill reinforcement for filled marks (bar, area, arc).
246
- *
247
- * - `'auto'`: assign a per-series SVG pattern (diagonal hatch, dots,
248
- * crosshatch, vertical lines) layered over each series color so series
249
- * remain distinguishable without color vision. Marks too small to show
250
- * a legible pattern keep their solid fill.
251
- * - `'none'` (default): solid fills only.
252
- */
253
- fillPattern?: 'auto' | 'none';
254
- /**
255
- * Visual style for range marks. Only meaningful when `type` is `'range'`.
256
- * - 'dumbbell' (default): a dot at each end joined by a connector line.
257
- * The start dot is muted, the end dot carries the accent color.
258
- * - 'arrow': a line with an arrowhead at the x2/y2 end, the strongest
259
- * "change between two points in time" form.
260
- * - 'bar': a plain floating range bar spanning start to end.
261
- */
262
- style?: 'dumbbell' | 'arrow' | 'bar';
263
- /**
264
- * Color range marks by direction of change. Only meaningful when `type` is
265
- * `'range'`. When true, increases (x2 > x, or y2 > y) use the theme's
266
- * `positive` semantic color and decreases use `negative`. A field-based
267
- * `encoding.color` takes precedence over direction coloring.
268
- */
269
- colorByDirection?: boolean;
270
- /**
271
- * Total number of cells in a waffle grid. Only meaningful when `type` is
272
- * `'waffle'`. Category values normalize to this many cells via
273
- * largest-remainder rounding so the grid always sums exactly. Default 100.
274
- */
275
- units?: number;
276
- /**
277
- * Number of columns in a waffle grid. Only meaningful when `type` is
278
- * `'waffle'`. Rows derive from `units / columns`. Default 10.
279
- */
280
- columns?: number;
281
- /**
282
- * First day of the week for calendar marks. Only meaningful when `type` is
283
- * `'calendar'`. Controls which weekday occupies the top row of each band.
284
- * Defaults to 'monday' (ISO week convention).
285
- */
286
- weekStart?: 'monday' | 'sunday';
287
- /**
288
- * Corner radius in pixels for calendar day cells. Only meaningful when
289
- * `type` is `'calendar'`. Defaults to 1.
290
- */
291
- cellRadius?: number;
292
- /**
293
- * Seat layout shape for parliament marks. Only meaningful when `type` is
294
- * `'parliament'`. `'hemicycle'` (default, and currently the only shape)
295
- * packs seats into concentric semicircular arcs, party-grouped left to
296
- * right.
297
- */
298
- shape?: 'hemicycle';
299
- /**
300
- * Seat dot radius in pixels for parliament marks. `'auto'` (default) sizes
301
- * dots to fill the available hemicycle rings for the given seat count.
302
- * Only meaningful when `type` is `'parliament'`.
303
- */
304
- seatRadius?: number | 'auto';
305
- /**
306
- * The majority-threshold line and "N to win" label on parliament marks. Only
307
- * meaningful when `type` is `'parliament'`. `true` (default) draws the line at
308
- * a simple majority (half the seats plus one); `false` hides it. The object
309
- * form overrides the threshold and label: `{ seats }` sets a custom seat count
310
- * (e.g. a two-thirds supermajority) and `{ label }` overrides the default
311
- * "N to win" text. A `seats` value outside `1..totalSeats` warns and falls
312
- * back to the simple-majority default.
313
- */
314
- majorityLine?: boolean | { seats?: number; label?: string };
315
- }
316
-
317
- // ---------------------------------------------------------------------------
318
- // Encoding
319
- // ---------------------------------------------------------------------------
320
-
321
- /** Data field type, following Vega-Lite conventions. */
322
- export type FieldType = 'quantitative' | 'temporal' | 'nominal' | 'ordinal';
323
-
324
- /** Aggregate function applied to a field before encoding. */
325
- export type AggregateOp =
326
- | 'count'
327
- | 'sum'
328
- | 'mean'
329
- | 'median'
330
- | 'min'
331
- | 'max'
332
- | 'variance'
333
- | 'stdev'
334
- | 'distinct'
335
- | 'q1'
336
- | 'q3';
337
-
338
- /** Axis configuration for an encoding channel. */
339
- export interface AxisConfig {
340
- /** Axis title text. If omitted, the field name is used. */
341
- title?: string;
342
- /**
343
- * Number format: d3-format string (e.g. ",.0f"), or semantic keyword
344
- * `'percent'` / `'currency'`. When omitted, compact notation (1k, 2.5M)
345
- * is applied automatically.
346
- */
347
- format?: string;
348
- /** Override tick count. Engine picks a sensible default if omitted. */
349
- tickCount?: number;
350
- /** Whether to show gridlines for this axis. */
351
- grid?: boolean;
352
- /** Rotation angle in degrees for tick labels. Common values: -45, -90, 90. */
353
- labelAngle?: number;
354
- /** Axis orientation override. */
355
- orient?: 'top' | 'bottom' | 'left' | 'right';
356
- /** Explicit tick values. */
357
- values?: unknown[];
358
- /** How to handle overlapping labels. */
359
- labelOverlap?: boolean | 'parity' | 'greedy';
360
- /** Whether to flush labels to the axis edges. */
361
- labelFlush?: boolean;
362
- /** Whether to show the axis domain line. */
363
- domain?: boolean;
364
- /** Whether to show tick marks. */
365
- ticks?: boolean;
366
- /** Axis position offset in pixels. */
367
- offset?: number;
368
- /** Padding between axis title and axis. */
369
- titlePadding?: number;
370
- /** Padding between tick labels and axis. */
371
- labelPadding?: number;
372
- /** Color override for axis tick labels and title. Useful in dual-axis charts to match axis color to its series. */
373
- labelColor?: string;
374
- /** Literal string appended to every formatted tick label. e.g. "B" gives "$4.5B" when format is "$,.1~f". */
375
- labelSuffix?: string;
376
- /** Secondary data field to display alongside each tick label. Renders in lighter weight/color. Only effective on categorical y-axis labels (horizontal bar charts). */
377
- labelField?: string;
378
- /**
379
- * Where tick labels render relative to the chart area. Editorial line/area
380
- * y-axes default to `'inline'`: labels sit above their gridlines at the
381
- * chart's left edge, with no left gutter, axis line, or tick marks. Other
382
- * axis types default to `'gutter'` (the classic placement outside the chart
383
- * area).
384
- */
385
- tickPosition?: 'inline' | 'gutter';
386
- }
387
-
388
- /** Scale configuration for an encoding channel. */
389
- export interface ScaleConfig {
390
- /**
391
- * Explicit domain override. Auto-derived from data if omitted.
392
- * Continuous scales take `[min, max]`; threshold scales take the full
393
- * list of class break values; categorical scales take the value list.
394
- */
395
- domain?: number[] | string[];
396
- /** Scale type override. Usually inferred from field type. */
397
- type?: ScaleType;
398
- /** Whether to nice-ify the domain for clean tick values. Defaults to true. */
399
- nice?: boolean;
400
- /** Whether the domain should include zero. Defaults to true for quantitative. */
401
- zero?: boolean;
402
- /** When true and domain is set, filter out data rows with values outside the domain range. */
403
- clip?: boolean;
404
- /** Explicit range override. */
405
- range?: unknown[];
406
- /**
407
- * Named color scheme (Vega-Lite aligned). Resolved against the core palette
408
- * registry (sequential: blue, green, orange, purple, teal; diverging:
409
- * redBlue, brownTeal; categorical) during the pre-validation sugar
410
- * expansion; common VL names (blues, greens, category10, ...) are accepted
411
- * as aliases. Unknown names fail validation with the supported list.
412
- * An explicit `range` wins over `scheme`.
413
- */
414
- scheme?: string;
415
- /** Reverse the range direction. */
416
- reverse?: boolean;
417
- /** Clamp output to the range. */
418
- clamp?: boolean;
419
- /** Padding for band/point scales. */
420
- padding?: number;
421
- /** Inner padding for band scales. */
422
- paddingInner?: number;
423
- /** Outer padding for band scales. */
424
- paddingOuter?: number;
425
- /** Exponent for pow scales. */
426
- exponent?: number;
427
- /** Base for log scales (default 10). */
428
- base?: number;
429
- /** Constant for symlog scales. */
430
- constant?: number;
431
- }
432
-
433
- /** Scale type, following Vega-Lite conventions. */
434
- export type ScaleType =
435
- | 'linear'
436
- | 'log'
437
- | 'pow'
438
- | 'sqrt'
439
- | 'symlog'
440
- | 'time'
441
- | 'utc'
442
- | 'ordinal'
443
- | 'band'
444
- | 'point'
445
- | 'quantile'
446
- | 'quantize'
447
- | 'threshold';
448
-
449
- /**
450
- * Sort a categorical domain by the aggregated values of another field
451
- * (Vega-Lite EncodingSortField).
452
- */
453
- export interface SortByField {
454
- /** Data field whose values determine the category order. */
455
- field: string;
456
- /** Aggregate applied per category before comparing. Defaults to 'sum'. */
457
- op?: AggregateOp;
458
- /** Sort direction. Defaults to 'ascending'. */
459
- order?: 'ascending' | 'descending';
460
- }
461
-
462
- /**
463
- * A constant value definition for an encoding channel (Vega-Lite aligned).
464
- * The VL idiom for a fixed visual property, e.g. `color: { value: '#1b7fa3' }`.
465
- * Expanded to the matching mark-level property (fill/stroke, size, opacity)
466
- * during the pre-validation sugar expansion.
467
- */
468
- export interface ValueDef {
469
- /** The constant value: CSS color string, size number, opacity (0-1), or gradient. */
470
- value: string | number | boolean | null | GradientDef;
471
- }
472
-
473
- /**
474
- * A single encoding channel mapping a data field to a visual property.
475
- *
476
- * Follows the Vega-Lite encoding model: field identifies the column,
477
- * type determines how the engine interprets values, aggregate applies
478
- * a transformation before encoding.
479
- *
480
- * @template TData - The shape of a data row. When `ChartSpec<TData>` is used
481
- * with a typed data array, `field` is constrained to `keyof TData & string`,
482
- * giving IDE autocomplete and compile-time typo detection. Defaults to `DataRow`
483
- * which degrades to plain `string` (no constraint), keeping untyped specs working.
484
- */
485
- export interface EncodingChannel<TData extends DataRow = DataRow> {
486
- /**
487
- * Data field name (column in the data array).
488
- *
489
- * When using `ChartSpec<TData>` with a typed data array, this is constrained
490
- * to the actual column names of `TData`. Typos fail at compile time and IDEs
491
- * autocomplete your column names.
492
- */
493
- field: keyof TData & string;
494
- /**
495
- * How to interpret the field values.
496
- * - quantitative: continuous numbers (scale: linear)
497
- * - temporal: dates/times (scale: time)
498
- * - nominal: unordered categories (scale: ordinal)
499
- * - ordinal: ordered categories (scale: ordinal)
500
- */
501
- type: FieldType;
502
- /** Optional aggregate to apply before encoding. */
503
- aggregate?: AggregateOp;
504
- /**
505
- * Axis configuration. Set to `false` to suppress axis entirely (no space
506
- * reserved). `null` is accepted as a VL-aligned alias for `false`.
507
- */
508
- axis?: AxisConfig | false | null;
509
- /** Scale configuration. */
510
- scale?: ScaleConfig;
511
- /**
512
- * Stacking behavior for quantitative channels (Vega-Lite aligned).
513
- *
514
- * Vega-Lite accepts `'zero' | 'normalize' | 'center' | null`. OpenChart adds
515
- * `true` (sugar for `'zero'`) and `false` (sugar for `null`) so callers can
516
- * write `stack: true` / `stack: false` without learning the string forms.
517
- * The string forms are still the canonical input — the boolean shorthand is
518
- * normalized to the matching string before reaching layout.
519
- *
520
- * - undefined: chart-type default (see below) -- stacked for bar/column/area
521
- * - true | 'zero': stack from zero baseline
522
- * - 'normalize': stack and normalize to fraction of total (0-1 per category)
523
- * - 'center': center stacks around zero (streamgraph style)
524
- * - null | false: no stacking -- renders grouped/dodged (bar) or overlap (area)
525
- *
526
- * **Defaults differ by chart type:**
527
- * - **Bar/Column**: defaults to stacked (`'zero'`), Vega-Lite aligned. Use
528
- * `stack: null` (or `false`) for grouped/side-by-side bars.
529
- * - **Area**: defaults to stacked (`'zero'`), Vega-Lite aligned. Use
530
- * `stack: null` (or `false`) to opt into overlap rendering, where each
531
- * series renders as a translucent gradient band anchored at the
532
- * y-domain baseline.
533
- * - **Line**: stacking is not applied (lines always overlap).
534
- *
535
- * @example
536
- * // Grouped horizontal bars (opt-out; default is stacked):
537
- * "x": { "field": "revenue", "type": "quantitative", "stack": null }
538
- *
539
- * @example
540
- * // Overlap area (opt-out; default is stacked):
541
- * "y": { "field": "value", "type": "quantitative", "stack": null }
542
- */
543
- stack?: boolean | 'zero' | 'normalize' | 'center' | null;
544
- /**
545
- * Encoding-level bin shorthand (Vega-Lite aligned).
546
- * When set, auto-generates a BinTransform during normalization and updates
547
- * the field reference to the binned output (convention: `bin_<fieldName>`).
548
- * - true: bin with default params
549
- * - BinParams: bin with custom params (maxbins, step, etc.)
550
- */
551
- bin?: boolean | BinParams;
552
- /**
553
- * Encoding-level timeUnit shorthand (Vega-Lite aligned).
554
- * When set, auto-generates a TimeUnitTransform during normalization and
555
- * updates the field reference to the output (convention: `<timeUnit>_<fieldName>`).
556
- */
557
- timeUnit?: TimeUnit;
558
- /**
559
- * Sort order for categorical (nominal/ordinal) scale domains (Vega-Lite aligned).
560
- * - 'ascending': sort domain values ascending (default VL behavior)
561
- * - 'descending': sort domain values descending
562
- * - null: use data order (no sorting)
563
- * - undefined: ascending (VL default)
564
- * - 'x' | '-x' | 'y' | '-y': sort by another channel's values ('-' prefix
565
- * for descending). The VL ranked-bar idiom: `sort: '-y'` orders categories
566
- * by value, largest first. Resolved into an explicit domain during the
567
- * pre-validation sugar expansion.
568
- * - string[]: explicit value order. Listed values come first (in the given
569
- * order); unlisted data values follow in data order.
570
- * - SortByField: sort by the aggregated values of another field.
571
- */
572
- sort?: 'ascending' | 'descending' | null | 'x' | '-x' | 'y' | '-y' | string[] | SortByField;
573
- /**
574
- * Legend configuration for this channel (Vega-Lite aligned).
575
- * Only meaningful on the `color` channel. `null` hides the legend (the VL
576
- * idiom for `legend.show = false`); a config object merges into the
577
- * top-level `legend` (top-level keys win on conflict).
578
- */
579
- legend?: LegendConfig | null;
580
- /**
581
- * Display title override (Vega-Lite aligned).
582
- * Used as the label in tooltips instead of the raw field name.
583
- * Also usable for axis titles, but `axis.title` takes precedence there.
584
- */
585
- title?: string;
586
- /**
587
- * Format string for values (Vega-Lite aligned).
588
- * For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f"),
589
- * or semantic keyword `'percent'` / `'currency'`.
590
- * For temporal fields: d3-time-format string (e.g. "%Y", "%b %d").
591
- * Used in tooltips; `axis.format` takes precedence for axis tick labels.
592
- */
593
- format?: string;
594
- /**
595
- * Emphasis highlight for the color channel. Names one or more series values
596
- * to visually emphasize; all other series are muted to a neutral gray.
597
- *
598
- * Only meaningful on the `color` encoding channel. On other channels this
599
- * property is ignored.
600
- *
601
- * @example
602
- * encoding: {
603
- * color: { field: 'region', type: 'nominal', highlight: 'West' }
604
- * }
605
- */
606
- highlight?: string | string[];
607
- }
608
-
609
- /**
610
- * Facet channel definition: partitions data into small-multiple panels.
611
- *
612
- * Unlike positional channels, facet only needs a field, a categorical type,
613
- * and optional layout hints (columns, sort). It does not carry scale/axis
614
- * config because it produces a grid, not a visual encoding.
615
- */
616
- export interface FacetChannel<TData extends DataRow = DataRow> {
617
- /** Data field to partition by. Each unique value produces one panel. */
618
- field: keyof TData & string;
619
- /** Must be categorical. */
620
- type: 'nominal' | 'ordinal';
621
- /** Number of columns in the wrap grid. Auto-computed when omitted. */
622
- columns?: number;
623
- /** Sort order for panel values. Default ascending. */
624
- sort?: 'ascending' | 'descending' | null;
625
- }
626
-
627
- /**
628
- * Encoding object mapping visual channels to data fields.
629
- * Which channels are required depends on the mark type — see the per-mark
630
- * encoding interfaces (ArcEncoding, LineEncoding, etc.) and MARK_ENCODING_RULES
631
- * in encoding.ts for the full requirements table.
632
- *
633
- * @template TData - Propagated from ChartSpec<TData>. Constrains `field` in
634
- * every channel to `keyof TData & string` when a typed data row is provided.
635
- */
636
- export interface Encoding<TData extends DataRow = DataRow> {
637
- /**
638
- * Horizontal position channel. Required for: bar, line, area, point, tick, rect, lollipop.
639
- * Maps a field to the x-axis. Use `type: 'temporal'` for dates, `'nominal'` for categories,
640
- * `'quantitative'` for numbers.
641
- */
642
- x?: EncodingChannel<TData>;
643
- /**
644
- * Vertical position channel. Required for: bar, line, area, point, tick, rect, arc, lollipop.
645
- * For arc marks, this is the value field (slice size). For all others it's the y-axis position.
646
- */
647
- y?: EncodingChannel<TData>;
648
- /**
649
- * Color channel. Required for arc marks (determines pie/donut slice coloring).
650
- * Optional for all other marks -- used for series differentiation on multi-series charts,
651
- * or heatmap intensity. Accepts a conditional definition to apply colors based on data predicates,
652
- * or a bare `{ value }` constant (VL aligned; expanded to the mark-level fill/stroke).
653
- */
654
- color?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
655
- /**
656
- * Size channel. Used by point/bubble charts to scale dot area by a quantitative field.
657
- * Accepts a conditional definition to vary size based on data predicates,
658
- * or a bare `{ value }` constant (VL aligned; expanded to `mark.size`).
659
- */
660
- size?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
661
- /**
662
- * Detail channel. Groups data into multiple series without mapping to a visual property.
663
- * Useful when you want separate lines per category but don't need the color to differ.
664
- */
665
- detail?: EncodingChannel<TData>;
666
- /**
667
- * Identity channel for data-update transitions. Maps a field whose value
668
- * uniquely identifies each datum across updates. Mirrors Vega-Lite's key channel.
669
- * Not visually encoded. Optional: charts derive sensible default keys.
670
- */
671
- key?: EncodingChannel<TData>;
672
- /**
673
- * Secondary x position. Used with `x` to define a horizontal span (rect marks, error bars).
674
- * Both `x` and `x2` must be quantitative.
675
- */
676
- x2?: EncodingChannel<TData>;
677
- /**
678
- * Secondary y position. Used with `y` to define a vertical span (rect marks, error bands).
679
- * Both `y` and `y2` must be quantitative.
680
- */
681
- y2?: EncodingChannel<TData>;
682
- /**
683
- * Data-driven opacity (0-1 range). Accepts a conditional definition to vary opacity
684
- * based on data predicates (e.g., highlight selected points), or a bare `{ value }`
685
- * constant (VL aligned; expanded to `mark.opacity`).
686
- */
687
- opacity?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
688
- /**
689
- * Stroke dash pattern encoding. Maps a nominal/ordinal field to different
690
- * dash patterns on line and rule marks. Useful when color alone doesn't
691
- * distinguish series well.
692
- */
693
- strokeDash?: EncodingChannel<TData>;
694
- /** Rotation angle encoding for point marks. Maps a quantitative field to 0-360 degrees. */
695
- angle?: EncodingChannel<TData>;
696
- /**
697
- * Text content for `text` marks. Required when mark is `'text'`.
698
- * Not meaningful for other mark types.
699
- */
700
- text?: EncodingChannel<TData>;
701
- /**
702
- * Tooltip field(s). Shown on hover. Can be a single channel or an array for
703
- * multi-field tooltips. Independent of the x/y/color encoding.
704
- */
705
- tooltip?: EncodingChannel<TData> | EncodingChannel<TData>[];
706
- /**
707
- * Angular position (slice value) for arc marks (pie/donut), the canonical
708
- * value channel in v8. Waffle and parliament marks accept it too (the same
709
- * part-to-whole value channel). `y` is still accepted on these marks as a
710
- * deprecated alias for backward compatibility (a compile warning is emitted
711
- * and `y` is populated from `theta` internally). Not used by any other mark
712
- * type.
713
- */
714
- theta?: EncodingChannel<TData>;
715
- /**
716
- * Facet channel: partitions data into a grid of small-multiple panels.
717
- * Each unique value of the facet field produces one panel. Panels share
718
- * scales by default; use `resolve: { scale: { y: 'independent' } }` for
719
- * per-panel axes.
720
- */
721
- facet?: FacetChannel<TData>;
722
- }
723
-
724
- // ---------------------------------------------------------------------------
725
- // Graph-specific encoding
726
- // ---------------------------------------------------------------------------
727
-
728
- /** Encoding channel for graph nodes and edges. */
729
- export interface GraphEncodingChannel {
730
- /** Data field name on the node/edge object. */
731
- field: string;
732
- /** How to interpret the field values. */
733
- type?: FieldType;
734
- /** Scale configuration. Auto-derived from data if omitted. */
735
- scale?: ScaleConfig;
736
- }
737
-
738
- /** Graph-specific encoding mapping visual properties to node/edge data fields. */
739
- export interface GraphEncoding {
740
- /** Color mapping for nodes. */
741
- nodeColor?: GraphEncodingChannel;
742
- /** Size mapping for nodes. */
743
- nodeSize?: GraphEncodingChannel;
744
- /** Color mapping for edges. */
745
- edgeColor?: GraphEncodingChannel;
746
- /** Width mapping for edges. */
747
- edgeWidth?: GraphEncodingChannel;
748
- /** Style mapping for edges (solid, dashed, dotted). */
749
- edgeStyle?: GraphEncodingChannel;
750
- /** Label field for nodes. */
751
- nodeLabel?: GraphEncodingChannel;
752
- }
753
-
754
- /** Layout algorithm for graph visualization. */
755
- export interface GraphLayoutConfig {
756
- /** Layout algorithm type. */
757
- type: 'force' | 'radial' | 'hierarchical';
758
- /** Optional clustering configuration. */
759
- clustering?: {
760
- /** Field to group nodes by for cluster forces. */
761
- field: string;
762
- };
763
- /** Charge strength for force layout. Negative values create repulsion. */
764
- chargeStrength?: number;
765
- /** Target distance between linked nodes. */
766
- linkDistance?: number;
767
- /** Extra px added to node radius for collision detection (default 2). */
768
- collisionPadding?: number;
769
- /** Link force strength override. */
770
- linkStrength?: number;
771
- /** Whether to apply center force (default true). */
772
- centerForce?: boolean;
773
- }
774
-
775
- // ---------------------------------------------------------------------------
776
- // Chrome (editorial text elements)
777
- // ---------------------------------------------------------------------------
778
-
779
- /** Style overrides for a chrome text element. */
780
- export interface ChromeTextStyle {
781
- /** Font size in pixels. */
782
- fontSize?: number;
783
- /** Font weight (400 = normal, 600 = semibold, 700 = bold). */
784
- fontWeight?: number;
785
- /** Font family override. */
786
- fontFamily?: string;
787
- /** Text color (CSS color string). */
788
- color?: string;
789
- }
790
-
791
- /** A chrome text element with optional style overrides. */
792
- export interface ChromeText {
793
- /** The text content to display. */
794
- text: string;
795
- /** Optional style overrides. Theme defaults are used for any omitted property. */
796
- style?: ChromeTextStyle;
797
- /** Pixel offset for fine-tuning position. */
798
- offset?: AnnotationOffset;
799
- }
800
-
801
- /**
802
- * Editorial chrome elements: eyebrow, title, subtitle, source attribution, byline, footer.
803
- * These are first-class structural elements, not string-only afterthoughts.
804
- * Each element can be a simple string or a ChromeText object with style overrides.
805
- */
806
- export interface Chrome {
807
- /**
808
- * Editorial kicker/category label rendered above the title. Typically
809
- * uppercase, tracked, and tinted with the accent color (e.g.
810
- * "Equities · Single Ticker"). Term follows IBM Carbon and Atlassian
811
- * Design System conventions; not part of Vega-Lite's title model.
812
- */
813
- eyebrow?: string | ChromeText;
814
- /** Main title displayed above the visualization. */
815
- title?: string | ChromeText;
816
- /** Subtitle displayed below the title, typically providing context. */
817
- subtitle?: string | ChromeText;
818
- /** Data source attribution, displayed below the chart area. */
819
- source?: string | ChromeText;
820
- /** Author or organization byline. */
821
- byline?: string | ChromeText;
822
- /** Footer text, displayed at the very bottom. */
823
- footer?: string | ChromeText;
824
- /**
825
- * Right-anchored brand block on the footer row, paired with a small accent
826
- * dot to its left. Visually balances the source/byline left-anchored text.
827
- * When set, suppresses the default `tryOpenData.ai` watermark for this chart.
828
- */
829
- brand?: string | ChromeText;
830
- }
831
-
832
- // ---------------------------------------------------------------------------
833
- // Annotations
834
- // ---------------------------------------------------------------------------
835
-
836
- /** Pixel offset for fine-grained annotation positioning. */
837
- export interface AnnotationOffset {
838
- /** Horizontal pixel offset. */
839
- dx?: number;
840
- /** Vertical pixel offset. */
841
- dy?: number;
842
- }
843
-
844
- /** Anchor direction for annotation label placement relative to the data point. */
845
- export type AnnotationAnchor = 'top' | 'bottom' | 'left' | 'right' | 'auto';
846
-
847
- /** Connector type for annotation labels. */
848
- export type ConnectorType = 'straight' | 'curve' | 'drop-line';
849
-
850
- /** Object form of the connector config, allowing arrow control. */
851
- export interface ConnectorConfig {
852
- /** Connector style. */
853
- type: ConnectorType;
854
- /** Whether to draw an arrowhead at the data-point end. Default: true for curve, false for straight. Drop-line never takes a head. */
855
- arrow?: boolean;
856
- }
857
-
858
- /** Style overrides for the dot marker drawn at the data point. */
859
- export interface AnnotationDot {
860
- /** Circle radius in pixels. Default 4. */
861
- radius?: number;
862
- /** Fill color. Defaults to theme background for an "open ring" look. */
863
- fill?: string;
864
- /** Stroke color. Defaults to the connector's resolved stroke, so marker and leader read as one system. */
865
- stroke?: string;
866
- /** Stroke width in pixels. Default 1.5. */
867
- strokeWidth?: number;
868
- }
869
-
870
- /** Base properties shared by all annotation types. */
871
- interface AnnotationBase {
872
- /** Stable identifier for selection and edit callbacks. When provided, edit events include this ID for reliable element matching. */
873
- id?: string;
874
- /** Human-readable label for the annotation. */
875
- label?: string;
876
- /** Fill color for the annotation element. */
877
- fill?: string;
878
- /** Stroke color for the annotation element. */
879
- stroke?: string;
880
- /** Opacity from 0 to 1. */
881
- opacity?: number;
882
- /** Z-index for render ordering. Higher values render on top. */
883
- zIndex?: number;
884
- /** When false, the annotation is always shown even at compact breakpoints. Default true. */
885
- responsive?: boolean;
886
- }
887
-
888
- /**
889
- * Text annotation positioned at a data coordinate.
890
- * Shows a callout label at a specific point in the chart.
891
- */
892
- export interface TextAnnotation extends AnnotationBase {
893
- type: 'text';
894
- /** X-axis data value or position. */
895
- x: string | number;
896
- /** Y-axis data value or position. */
897
- y: string | number;
898
- /**
899
- * The annotation text. Required for text annotations.
900
- *
901
- * Supports inline `**bold**` spans, so emphasis lands on the key phrase rather
902
- * than the whole block: `'Inflation peaked at **8.5%**'`. Unmatched `**` (and
903
- * empty `****`) render literally. `\n` starts a new line.
904
- */
905
- text: string;
906
- /** Thinning priority (lower = kept longer at narrow widths). When omitted, spec order is used. */
907
- priority?: number;
908
- /**
909
- * Optional muted second-tone text rendered below the primary `text`.
910
- * Used for supporting context (e.g. methodology, source). Newlines in
911
- * `text` still produce multi-line primary; subtitle is a separate block.
912
- *
913
- * Supports the same inline `**bold**` spans as `text`. When a subtitle is
914
- * present and no `fontWeight` is set, the primary `text` resolves to weight
915
- * 700 — the lede + context stack.
916
- */
917
- subtitle?: string;
918
- /**
919
- * Open-ring marker drawn on the data point itself (not on the pulled-back
920
- * connector tip).
921
- *
922
- * Left unset, a default marker appears whenever a connector is enabled and
923
- * carries no arrowhead — the leader points, the ring lands. An arrowed
924
- * connector gets no marker unless you ask for one: the head already marks the
925
- * spot. `dot: false` is always bare; an explicit `dot` always wins.
926
- *
927
- * `true` uses the default style; pass an object to override radius, fill,
928
- * stroke, or strokeWidth.
929
- */
930
- dot?: boolean | AnnotationDot;
931
- /** Font size override. */
932
- fontSize?: number;
933
- /** Font weight override. */
934
- fontWeight?: number;
935
- /** Pixel offset from the computed position. */
936
- offset?: AnnotationOffset;
937
- /** Anchor direction for label placement relative to the data point. */
938
- anchor?: AnnotationAnchor;
939
- /**
940
- * Connector from label to anchor point.
941
- * - `true` (default): straight line, no arrow
942
- * - `'straight'`: straight line, no arrow
943
- * - `'curve'`: curved arrow with arrowhead
944
- * - `'drop-line'`: vertical line through the data point's x
945
- * - `{ type, arrow? }`: object form for explicit arrow control
946
- * - `false`: no connector
947
- *
948
- * The connector leaves the text block on the side facing the data point, with
949
- * a small standoff gap. A leader shorter than 8px is dropped as noise (the
950
- * marker alone reads better), as is a connector whose target sits inside the
951
- * text block. Widen `offset` if you want the line back.
952
- */
953
- connector?: boolean | ConnectorType | ConnectorConfig;
954
- /** Per-endpoint offsets for the connector line. Allows fine-tuning where the connector starts and ends. */
955
- connectorOffset?: {
956
- /** Offset for the label-end of the connector. */
957
- from?: AnnotationOffset;
958
- /** Offset for the data-point-end of the connector. */
959
- to?: AnnotationOffset;
960
- };
961
- /**
962
- * Background plate behind the text, for readability over chart lines.
963
- *
964
- * `true` uses the theme surface, so the plate follows light/dark mode. A
965
- * color string sets it explicitly. Prefer `true`: a hardcoded '#ffffff' stays
966
- * white in dark mode and the text turns light-gray-on-white.
967
- */
968
- background?: string | true;
969
- /** Whether to show the paint-order stroke halo behind text. Default true. Set false for white text on colored backgrounds. */
970
- halo?: boolean;
971
- }
972
-
973
- /**
974
- * Range annotation highlighting a region of the chart.
975
- * Defined by x1/x2 (vertical band) or y1/y2 (horizontal band) or both (rectangle).
976
- */
977
- export interface RangeAnnotation extends AnnotationBase {
978
- type: 'range';
979
- /** Start of the range on the x-axis. */
980
- x1?: string | number;
981
- /** End of the range on the x-axis. */
982
- x2?: string | number;
983
- /** Start of the range on the y-axis. */
984
- y1?: string | number;
985
- /** End of the range on the y-axis. */
986
- y2?: string | number;
987
- /**
988
- * For band/point (ordinal) x or y scales, whether the range extends from the
989
- * data point's center out to the band/step edge. Default `true`: a range on a
990
- * bar chart spans full columns, and a range on a line chart extends half a step
991
- * past the first/last point so the band reaches the plot edge. Set `false` to
992
- * anchor the range exactly at the data point centers instead — useful when you
993
- * want the band inset from the axis (e.g. starting at the first data point
994
- * rather than flush against the y-axis guide). No effect on linear/time scales.
995
- */
996
- extendToEdges?: boolean;
997
- /** Pixel offset for the range label. */
998
- labelOffset?: AnnotationOffset;
999
- /** Anchor direction for the range label. */
1000
- labelAnchor?: AnnotationAnchor;
1001
- /** Font size override for the range label (px). Default: 11. */
1002
- fontSize?: number;
1003
- /** Font weight override for the range label. Default: 500. */
1004
- fontWeight?: number;
1005
- }
1006
-
1007
- /**
1008
- * Reference line annotation: a horizontal or vertical line at a data value.
1009
- * Useful for baselines (zero), targets, or thresholds.
1010
- */
1011
- export interface RefLineAnnotation extends AnnotationBase {
1012
- /**
1013
- * Discriminant. `'refline'` is canonical and the only value in the type.
1014
- * `'rule'` (it collides with the `rule` mark type) is still accepted at
1015
- * runtime for backward compatibility -- the spec-sugar pass rewrites it to
1016
- * `'refline'` with a deprecation warning before validation -- but it is not
1017
- * part of this type. Use `'refline'`.
1018
- */
1019
- type: 'refline';
1020
- /** X-axis value for a vertical reference line. */
1021
- x?: string | number;
1022
- /** Y-axis value for a horizontal reference line. */
1023
- y?: string | number;
1024
- /** Line style. */
1025
- style?: 'solid' | 'dashed' | 'dotted';
1026
- /** Raw SVG dash pattern override, e.g. [4, 4]. Takes precedence over style. */
1027
- strokeDash?: number[];
1028
- /** Line width in pixels. */
1029
- strokeWidth?: number;
1030
- /** Pixel offset for the reference line label. */
1031
- labelOffset?: AnnotationOffset;
1032
- /** Anchor direction for the reference line label. */
1033
- labelAnchor?: AnnotationAnchor;
1034
- /** Label font size in pixels. Default: 11. */
1035
- fontSize?: number;
1036
- /** Label font weight. Default: 400. */
1037
- fontWeight?: number;
1038
- }
1039
-
1040
- /** Discriminated union of all annotation types. */
1041
- export type Annotation = TextAnnotation | RangeAnnotation | RefLineAnnotation;
1042
-
1043
- // ---------------------------------------------------------------------------
1044
- // Theme + Dark Mode
1045
- // ---------------------------------------------------------------------------
1046
-
1047
- /**
1048
- * Dark mode behavior.
1049
- *
1050
- * - `"auto"` - Checks the `prefers-color-scheme` media query to detect the
1051
- * user's system-level preference. This does NOT detect class-based dark mode
1052
- * toggles (e.g. `document.documentElement.classList.toggle('dark')`). If your
1053
- * app uses class-based dark mode, use VizThemeProvider with `"force"` or
1054
- * `"off"` instead of `"auto"` and toggle based on your app's state.
1055
- * - `"force"` - Always render in dark mode regardless of system preference.
1056
- * - `"off"` - Always render in light mode (default).
1057
- *
1058
- * All components (Chart, Sankey, Graph) inherit darkMode from VizThemeProvider
1059
- * when no explicit prop is passed.
1060
- */
1061
- export type DarkMode = 'auto' | 'force' | 'off';
1062
-
1063
- /**
1064
- * Per-element chrome theme override. Pass a plain string (color only,
1065
- * backward-compatible) or an object with full typography control.
1066
- */
1067
- export interface ChromeThemeOverride {
1068
- /** Text color. Accepts a TokenValue for light/dark pairs. */
1069
- color?: TokenValue;
1070
- /** Font size in pixels. */
1071
- fontSize?: number;
1072
- /** Font weight. */
1073
- fontWeight?: number;
1074
- /** Line height multiplier. */
1075
- lineHeight?: number;
1076
- }
1077
-
1078
- /**
1079
- * User-facing theme configuration for overriding defaults.
1080
- * All fields are optional. The engine deep-merges these onto the default theme.
1081
- */
1082
- export interface ThemeConfig {
1083
- /**
1084
- * Color palette overrides.
1085
- * Pass a flat string[] as shorthand for categorical colors,
1086
- * or an object for full control over categorical, sequential, diverging, etc.
1087
- */
1088
- colors?:
1089
- | string[]
1090
- | {
1091
- /** Categorical palette for nominal data (array of CSS color strings). */
1092
- categorical?: string[];
1093
- /** Sequential palettes keyed by name. Each is an array of color stops. */
1094
- sequential?: Record<string, string[]>;
1095
- /** Diverging palettes keyed by name. Each is an array of color stops with a neutral midpoint. */
1096
- diverging?: Record<string, string[]>;
1097
- /** Background color. Accepts a TokenValue for light/dark pairs. */
1098
- background?: TokenValue;
1099
- /** Default text color. Accepts a TokenValue for light/dark pairs. */
1100
- text?: TokenValue;
1101
- /** Gridline color. Accepts a TokenValue for light/dark pairs. */
1102
- gridline?: TokenValue;
1103
- /** Axis line and tick color. Accepts a TokenValue for light/dark pairs. */
1104
- axis?: TokenValue;
1105
- /** Annotation fill color. Accepts a TokenValue for light/dark pairs. */
1106
- annotationFill?: TokenValue;
1107
- /** Annotation text color. Accepts a TokenValue for light/dark pairs. */
1108
- annotationText?: TokenValue;
1109
- /** Semantic color for positive/up-trend values. Accepts a TokenValue for light/dark pairs. */
1110
- positive?: TokenValue;
1111
- /** Semantic color for negative/down-trend values. Accepts a TokenValue for light/dark pairs. */
1112
- negative?: TokenValue;
1113
- };
1114
- /** Font overrides. */
1115
- fonts?: {
1116
- /** Primary font family. */
1117
- family?: string;
1118
- /** Monospace font family (for tabular numbers). */
1119
- mono?: string;
1120
- /** Font size overrides in pixels. Partial — only specified keys are overridden. */
1121
- sizes?: {
1122
- /** Chart title. Default: 26. */
1123
- title?: number;
1124
- /** Subtitle below the title. Default: 14. */
1125
- subtitle?: number;
1126
- /** Body text (tooltips, legend labels). Default: 13. */
1127
- body?: number;
1128
- /** Small text (source line, footer). Default: 11. */
1129
- small?: number;
1130
- /** Axis tick labels. Default: 11. */
1131
- axisTick?: number;
1132
- /** KPI metric uppercase label. Default: 10. */
1133
- metricLabel?: number;
1134
- /** KPI metric primary value. Default: 22. Delta/secondary derive from this. */
1135
- metricValue?: number;
1136
- };
1137
- /** Font weight overrides. Partial — only specified keys are overridden. */
1138
- weights?: {
1139
- /** Normal text weight. Default: 450. */
1140
- normal?: number;
1141
- /** Medium text weight. Default: 550. */
1142
- medium?: number;
1143
- /** Semibold text weight. Default: 590. */
1144
- semibold?: number;
1145
- /** Bold text weight. Default: 700. */
1146
- bold?: number;
1147
- };
1148
- };
1149
- /** Spacing overrides in pixels. */
1150
- spacing?: {
1151
- /** Padding inside the chart container. */
1152
- padding?: number;
1153
- /** Gap between chrome elements (title to subtitle, etc.). */
1154
- chromeGap?: number;
1155
- /** Gap between the last chrome element and the chart area. */
1156
- chromeToChart?: number;
1157
- /** Gap between chart area and source/footer below. */
1158
- chartToFooter?: number;
1159
- /** Internal padding within the chart area (axes margins). */
1160
- axisMargin?: number;
1161
- /** Height reserved below chart area for x-axis tick labels. Increase when large axisTick font sizes cause label clipping. */
1162
- xAxisHeight?: number;
1163
- /** Gap in pixels between the x-axis line and tick label text. Increase when larger axisTick fonts sit too close to the axis line. */
1164
- xAxisLabelPadding?: number;
1165
- };
1166
- /** Border radius for chart container and tooltips. */
1167
- borderRadius?: number;
1168
- /**
1169
- * Per-element chrome style overrides. Pass a plain string for color-only
1170
- * (backward-compatible), or a ChromeThemeOverride object for full
1171
- * typography control (color, fontSize, fontWeight, lineHeight).
1172
- */
1173
- chrome?: {
1174
- /** Eyebrow (kicker) style override. */
1175
- eyebrow?: string | ChromeThemeOverride;
1176
- /** Title style override. */
1177
- title?: string | ChromeThemeOverride;
1178
- /** Subtitle style override. */
1179
- subtitle?: string | ChromeThemeOverride;
1180
- /** Source/attribution style override. */
1181
- source?: string | ChromeThemeOverride;
1182
- /** Byline style override. */
1183
- byline?: string | ChromeThemeOverride;
1184
- /** Footer style override. */
1185
- footer?: string | ChromeThemeOverride;
1186
- };
1187
- /**
1188
- * Series color assignment strategy.
1189
- * Default: 'palette' (full categorical palette always, zero visual drift).
1190
- */
1191
- seriesStrategy?: SeriesStrategy;
1192
- }
1193
-
1194
- // ---------------------------------------------------------------------------
1195
- // Label configuration
1196
- // ---------------------------------------------------------------------------
1197
-
1198
- /**
1199
- * Label density mode controlling how many data labels are shown.
1200
- * - 'all': show every label, skip collision detection
1201
- * - 'auto': show labels with collision detection (default)
1202
- * - 'endpoints': show only first and last per series (useful for line charts)
1203
- * - 'none': hide all labels (rely on tooltips and legend)
1204
- */
1205
- export type LabelDensity = 'all' | 'auto' | 'endpoints' | 'none';
1206
-
1207
- /** Label display configuration for chart data labels. */
1208
- export interface LabelConfig {
1209
- /** How many labels to show. Defaults to 'auto'. */
1210
- density?: LabelDensity;
1211
- /** Number format override for label values (d3-format string, e.g. ",.0f"). */
1212
- format?: string;
1213
- /** Literal string prepended to each formatted label value (e.g. "-" or "$"). */
1214
- prefix?: string;
1215
- /** Literal string appended to each formatted label value (e.g. "%" or "x"). */
1216
- suffix?: string;
1217
- /** Fixed CSS color for all labels. Overrides the default fill-derived color. */
1218
- color?: string;
1219
- /**
1220
- * Per-series pixel offsets for fine-tuning label positions, keyed by series name.
1221
- * @deprecated Per-series pixel offsets are a workaround for collision bugs
1222
- * now fixed by the layout engine. Will be removed in a future version.
1223
- */
1224
- offsets?: Record<string, AnnotationOffset>;
1225
- /** Font size in pixels for bar/column value labels. */
1226
- fontSize?: number;
1227
- }
1228
-
1229
- /** Shorthand: `false` disables all labels, `true` uses defaults, or pass a full config object. */
1230
- export type LabelSpec = boolean | LabelConfig;
1231
-
1232
- // ---------------------------------------------------------------------------
1233
- // Legend configuration
1234
- // ---------------------------------------------------------------------------
1235
-
1236
- /** Legend display configuration. Overrides the responsive-default position. */
1237
- export interface LegendConfig {
1238
- /** Override the legend position. If omitted, the responsive strategy decides. */
1239
- position?: LegendPosition;
1240
- /** Pixel offset for fine-tuning legend position. */
1241
- offset?: AnnotationOffset;
1242
- /** Whether to show the legend. Defaults to true. Set to false to hide. */
1243
- show?: boolean;
1244
- /** Number of columns for horizontal legend layout. Overrides the default row limit. */
1245
- columns?: number;
1246
- /** Max number of legend entries before truncation. Remaining entries show as "+N more". */
1247
- symbolLimit?: number;
1248
- /** Maximum number of rows for top-positioned legends before truncation. Defaults to 2. */
1249
- maxRows?: number;
1250
- /** Series names to exclude from the legend. Excluded series still render in the chart. */
1251
- exclude?: string[];
1252
- }
1253
-
1254
- /**
1255
- * Configuration for the searchable series highlight input (`seriesSearch`).
1256
- * The boolean form (`seriesSearch: true`) uses the defaults.
1257
- */
1258
- export interface SeriesSearchConfig {
1259
- /** Placeholder text for the search input. Defaults to "Find a series". */
1260
- placeholder?: string;
1261
- }
1262
-
1263
- /**
1264
- * Configuration for the "you draw it" interactive engagement format
1265
- * (`youDrawIt`). The reader draws their guess of the trend from `from` to the
1266
- * right edge before the real line reveals. Line marks only, single-series only.
1267
- */
1268
- export interface YouDrawItConfig {
1269
- /**
1270
- * The x value (matching `encoding.x`'s domain) where drawing starts. Data
1271
- * before this point renders normally; data at or after it is hidden behind
1272
- * a hatched "draw here" region until reveal.
1273
- */
1274
- from: string | number;
1275
- /** Prompt text shown in the hatched drawing region. Defaults to "Draw your guess". */
1276
- prompt?: string;
1277
- /** Label for the skip-to-reveal button. Defaults to "Show me". */
1278
- revealLabel?: string;
1279
- /**
1280
- * Optional extra comparison line (e.g. "what everyone else guessed"),
1281
- * supplied by the host app. Rendered in a third, visually distinct style
1282
- * alongside the reader's guess and the real line after reveal.
1283
- */
1284
- comparisonLine?: Array<{ x: string | number; y: number }>;
1285
- }
1286
-
1287
- /**
1288
- * Content composition for an endpoint-label entry: the series name, the
1289
- * formatted value, or both joined on one line. See
1290
- * {@link EndpointLabelsConfig.content}.
1291
- */
1292
- export type EndpointLabelContent = 'label' | 'value' | 'label value';
1293
-
1294
- /**
1295
- * Configuration for the endpoint labels column rendered at the chart's right edge
1296
- * for multi-series line/area charts. Each entry pairs the series name with its
1297
- * last formatted value, optionally anchored to the line by an open-circle marker.
1298
- *
1299
- * The column is independent of the traditional `legend` and the legacy
1300
- * end-of-line labels. Together with `legend.show`, the three suppression toggles
1301
- * follow this truth table for ≥2-series line/area charts:
1302
- *
1303
- * | `legend.show` | `endpointLabels` | Traditional legend | Endpoint column | End-of-line labels |
1304
- * |--|--|--|--|--|
1305
- * | unset | unset | hidden (auto-suppressed) | shown (default) | hidden |
1306
- * | true | unset | shown | shown | hidden |
1307
- * | unset | false | shown (auto-suppress revoked) | hidden | hidden |
1308
- * | false | false | hidden | hidden | shown (last-resort) |
1309
- * | true | false | shown | hidden | hidden |
1310
- * | false | true | hidden | shown | hidden |
1311
- * | true | true | shown | shown | hidden |
1312
- *
1313
- * Single-series charts: column is hidden by default (nothing to identify).
1314
- *
1315
- * The implementation is in `packages/engine/src/legend/suppression.ts` —
1316
- * `resolveSuppression` is the single source of truth. The table above is
1317
- * a user-facing mirror; if the two ever diverge, the engine wins. Tests
1318
- * in `legend/__tests__/suppression.test.ts` enforce every cell.
1319
- */
1320
- export interface EndpointLabelsConfig {
1321
- /** Explicit on/off. When undefined, the chart auto-decides based on series count. */
1322
- show?: boolean;
1323
- /** Maximum number of series before endpoint labels switch to a legend. Default 8. */
1324
- maxSeries?: number;
1325
- /**
1326
- * Which ends of the chart get label columns.
1327
- * - `'end'` (default): labels on the right (trailing) edge only.
1328
- * - `'both'`: labels on both left (leading) and right (trailing) edges.
1329
- */
1330
- ends?: 'end' | 'both';
1331
- /**
1332
- * What each entry renders.
1333
- * - `'label'`: series name only.
1334
- * - `'value'`: formatted value only.
1335
- * - `'label value'`: series name and value joined on one line.
1336
- *
1337
- * When omitted, entries render the series name with the formatted value on
1338
- * a second line (the classic endpoint column). Pass an object to set the
1339
- * leading and trailing columns independently, e.g. the classic slope-chart
1340
- * look is `{ leading: 'label value', trailing: 'value' }`.
1341
- */
1342
- content?:
1343
- | EndpointLabelContent
1344
- | { leading?: EndpointLabelContent; trailing?: EndpointLabelContent };
1345
- /** Field to read the displayed value from. Defaults to `encoding.y.field`. */
1346
- valueField?: string;
1347
- /** d3-format string for the value. Defaults to `encoding.y.axis.format`. */
1348
- format?: string;
1349
- /** Max wrap width in pixels for long series names. Default 96. */
1350
- width?: number;
1351
- /** Render an open-circle marker on the line at the right edge. Default true. */
1352
- showMarker?: boolean;
1353
- /** Override the marker style. */
1354
- markerStyle?: {
1355
- fill?: string;
1356
- stroke?: string;
1357
- strokeWidth?: number;
1358
- radius?: number;
1359
- };
1360
- /**
1361
- * Render a thin leader line from the swatch row back to the line endpoint
1362
- * when collision-sweep displaces a label off its data point. Default false:
1363
- * the marker on the line plus the swatch in the column already pair label
1364
- * to data; the connector tends to add visual noise for small displacements.
1365
- * Opt in when labels collide hard and you need the explicit tie-back.
1366
- */
1367
- showLeader?: boolean;
1368
- }
1369
-
1370
- // ---------------------------------------------------------------------------
1371
- // Spec types (the top-level discriminated union)
1372
- // ---------------------------------------------------------------------------
1373
-
1374
- /** Data row: a plain object with string keys. */
1375
- export type DataRow = Record<string, unknown>;
1376
-
1377
- /** Per-series visual style overrides for line/area charts. */
1378
- export interface SeriesStyle {
1379
- /** Line dash style. Defaults to 'solid'. */
1380
- lineStyle?: 'solid' | 'dashed' | 'dotted';
1381
- /** Whether to show data point markers. Defaults to true. */
1382
- showPoints?: boolean;
1383
- /** Stroke width override. */
1384
- strokeWidth?: number;
1385
- /** Opacity override (0-1). */
1386
- opacity?: number;
1387
- }
1388
-
1389
- // ---------------------------------------------------------------------------
1390
- // Animation
1391
- // ---------------------------------------------------------------------------
1392
-
1393
- /**
1394
- * Named easing presets for entrance animations.
1395
- * Uses CSS linear() curves. Named 'ease' (not 'easing') to match Vega convention.
1396
- */
1397
- export type AnimationEase = 'smooth' | 'snappy';
1398
-
1399
- /** Stagger configuration for sequential element reveal. */
1400
- export interface AnimationStagger {
1401
- /** Delay between each element in ms. Default: 80 */
1402
- delay?: number;
1403
- /** Ordering strategy. Default: 'index' (DOM order) */
1404
- order?: 'index' | 'value' | 'reverse';
1405
- }
1406
-
1407
- /** Fine-grained responsive behavior configuration. */
1408
- export interface ResponsiveConfig {
1409
- /**
1410
- * Automatic annotation thinning at narrow widths. When true (default),
1411
- * annotations that can't be placed without overlap are demoted to numbered
1412
- * dot markers with footnote text below the chart. Set false to restore
1413
- * the pre-auto-thinning behavior (annotations hidden at compact breakpoints).
1414
- */
1415
- autoThin?: boolean;
1416
- }
1417
-
1418
- /**
1419
- * Animation phase config. Follows Vega's enter/update/exit model.
1420
- * Each phase can be true (use defaults) or a config object.
1421
- */
1422
- export interface AnimationPhaseConfig {
1423
- /** Duration in ms. Default: 500 for enter. */
1424
- duration?: number;
1425
- /** Easing preset. Default: 'smooth'. */
1426
- ease?: AnimationEase;
1427
- /** Stagger config. true = defaults, false = no stagger. Default: true for enter. */
1428
- stagger?: AnimationStagger | boolean;
1429
- }
1430
-
1431
- /**
1432
- * Full animation config object. Structured as enter/update/exit phases
1433
- * following Vega's encoding set model.
1434
- */
1435
- export interface AnimationConfig {
1436
- /** Entrance animation when chart first renders. */
1437
- enter?: AnimationPhaseConfig | boolean;
1438
- /** Transition animation when data updates. */
1439
- update?: AnimationPhaseConfig | boolean;
1440
- /** Exit animation when marks are removed. */
1441
- exit?: AnimationPhaseConfig | boolean;
1442
- /** Delay before annotations animate in (ms after marks). Default: 200. */
1443
- annotationDelay?: number;
1444
- }
1445
-
1446
- /**
1447
- * Animation spec property.
1448
- * - true: enable entrance animation with sensible defaults
1449
- * - false/omitted: no animation (current behavior)
1450
- * - AnimationConfig: full control via enter/update/exit phases
1451
- */
1452
- export type AnimationSpec = boolean | AnimationConfig;
1453
-
1454
- /**
1455
- * Chart display mode.
1456
- * - `'full'` (default): standard chart with chrome, axes, legend, padding.
1457
- * - `'sparkline'`: minimal inline mini-chart. Strips chrome, axes, legend,
1458
- * watermark, animation, and reduces margins to a tiny safety pad. The mark
1459
- * fills the container edge-to-edge. Best with `mark: 'line' | 'area' | 'bar'`.
1460
- * Explicit user fields (chrome, legend, encoding.x.axis, etc.) still render
1461
- * when set, so users can opt in to specific elements.
1462
- */
1463
- export type Display = 'full' | 'sparkline';
1464
-
1465
- /**
1466
- * Breakpoint-conditional overrides for chart specs.
1467
- *
1468
- * Allows specifying different chrome, labels, legend, or annotations
1469
- * per breakpoint. Merged shallowly into the base spec at compile time
1470
- * when the container matches the breakpoint.
1471
- */
1472
- export interface ChartSpecOverride {
1473
- /** Override editorial chrome at this breakpoint. */
1474
- chrome?: Chrome;
1475
- /** Override label configuration at this breakpoint. */
1476
- labels?: LabelSpec;
1477
- /** Override legend configuration at this breakpoint. */
1478
- legend?: LegendConfig;
1479
- /** Override annotations at this breakpoint. */
1480
- annotations?: Annotation[];
1481
- /** Override animation at this breakpoint. */
1482
- animation?: AnimationSpec;
1483
- /** Override display mode (`'full'` or `'sparkline'`) at this breakpoint. */
1484
- display?: Display;
1485
- /** Override watermark visibility at this breakpoint. */
1486
- watermark?: boolean;
1487
- /** Override crosshair behavior at this breakpoint. */
1488
- crosshair?: boolean;
1489
- }
1490
-
1491
- /**
1492
- * A KPI/metric cell rendered above the chart in a horizontal row.
1493
- *
1494
- * Used for editorial dashboards (e.g. "CLOSE $186.10 +1.4%") where the chart
1495
- * is paired with summary statistics. Cells lay out evenly across the chart
1496
- * width. Hidden in sparkline mode; auto-stripped when the container can't
1497
- * fit the laid-out values.
1498
- */
1499
- export interface Metric {
1500
- /** Uppercase eyebrow label, e.g. "CLOSE". */
1501
- label: string;
1502
- /** Primary numeric value, e.g. "$186.10". */
1503
- value: string;
1504
- /** Optional change indicator, e.g. "+1.4%". Rendered next to the value. */
1505
- delta?: string;
1506
- /** Tone for the delta. 'up' = positive (green), 'down' = negative (red). Default 'up'. */
1507
- deltaTone?: 'up' | 'down';
1508
- /** Optional secondary value (e.g. multiplier "10.3×"). Rendered after the delta. */
1509
- secondary?: string;
1510
- }
1511
-
1512
- // ---------------------------------------------------------------------------
1513
- // Per-mark encoding interfaces (Task 2)
1514
- // Required channels derived directly from MARK_ENCODING_RULES in encoding.ts.
1515
- // TypeScript types must stay in sync with those runtime rules.
1516
- // ---------------------------------------------------------------------------
1517
-
1518
- /**
1519
- * Encoding for arc marks (pie/donut charts).
1520
- * - `theta`: required (quantitative — the canonical slice value channel)
1521
- * - `color`: required (nominal/ordinal — the category)
1522
- * - `y`: deprecated alias for `theta`, accepted for backward compatibility
1523
- */
1524
- export interface ArcEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1525
- /** Canonical value channel for arc marks (quantitative — the slice value). */
1526
- theta: EncodingChannel<TData>;
1527
- color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1528
- }
1529
-
1530
- /**
1531
- * Encoding for line marks.
1532
- * - `x`: required (temporal or ordinal — the time/category axis)
1533
- * - `y`: required (quantitative — the value axis)
1534
- */
1535
- export interface LineEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1536
- x: EncodingChannel<TData>;
1537
- y: EncodingChannel<TData>;
1538
- }
1539
-
1540
- /**
1541
- * Encoding for bar marks (vertical columns and horizontal bars).
1542
- * - `x`: required
1543
- * - `y`: required
1544
- */
1545
- export interface BarEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1546
- x: EncodingChannel<TData>;
1547
- y: EncodingChannel<TData>;
1548
- }
1549
-
1550
- /**
1551
- * Encoding for area marks.
1552
- * - `x`: required (temporal or ordinal)
1553
- * - `y`: required (quantitative)
1554
- */
1555
- export interface AreaEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1556
- x: EncodingChannel<TData>;
1557
- y: EncodingChannel<TData>;
1558
- }
1559
-
1560
- /**
1561
- * Encoding for point marks (scatter plots).
1562
- * - `x`: required
1563
- * - `y`: required
1564
- */
1565
- export interface PointEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1566
- x: EncodingChannel<TData>;
1567
- y: EncodingChannel<TData>;
1568
- }
1569
-
1570
- /**
1571
- * Encoding for circle marks (dot plots).
1572
- * - `x`: required (quantitative)
1573
- * - `y`: required (nominal/ordinal — the category axis)
1574
- */
1575
- export interface CircleEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1576
- x: EncodingChannel<TData>;
1577
- y: EncodingChannel<TData>;
1578
- }
1579
-
1580
- /**
1581
- * Encoding for lollipop marks.
1582
- * - `x`: required (quantitative)
1583
- * - `y`: required (nominal/ordinal)
1584
- */
1585
- export interface LollipopEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1586
- x: EncodingChannel<TData>;
1587
- y: EncodingChannel<TData>;
1588
- }
1589
-
1590
- /**
1591
- * Encoding for beeswarm marks (dodged distribution dots).
1592
- * - Exactly one positional channel is quantitative: the value axis
1593
- * (x = horizontal swarm, y = vertical swarm).
1594
- * - The other positional channel is optional (nominal/ordinal): grouped
1595
- * swarms, one lane per category. The cross axis has no scale; the dodge
1596
- * layout computes pixel offsets around each lane center.
1597
- */
1598
- export interface BeeswarmEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1599
- x?: EncodingChannel<TData>;
1600
- y?: EncodingChannel<TData>;
1601
- }
1602
-
1603
- /**
1604
- * Encoding for range marks (dumbbell / arrow / range-bar plots).
1605
- * - Horizontal (the common editorial form): `y` nominal/ordinal category,
1606
- * `x` quantitative start, `x2` quantitative end.
1607
- * - Vertical: `x` nominal/ordinal category, `y` quantitative start,
1608
- * `y2` quantitative end.
1609
- */
1610
- export interface RangeEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1611
- x: EncodingChannel<TData>;
1612
- y: EncodingChannel<TData>;
1613
- }
1614
-
1615
- /**
1616
- * Encoding for waffle marks (unit grids for part-to-whole counts).
1617
- * - `color`: required (nominal/ordinal, the category)
1618
- * - `theta`: the canonical quantitative share, the same part-to-whole value
1619
- * channel arc marks use. `y` is a deprecated alias, accepted for backward
1620
- * compatibility.
1621
- */
1622
- export interface WaffleEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1623
- color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1624
- }
1625
-
1626
- /**
1627
- * Encoding for parliament marks (hemicycle seat-dot charts).
1628
- * - `color`: required (nominal/ordinal, the party)
1629
- * - `theta`: the canonical quantitative seat count, the same part-to-whole
1630
- * value channel arc/waffle marks use. `y` is a deprecated alias, accepted
1631
- * for backward compatibility.
1632
- */
1633
- export interface ParliamentEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1634
- color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1635
- }
1636
-
1637
- /**
1638
- * Encoding for calendar marks (GitHub-style calendar heatmaps).
1639
- * - `x`: required (temporal, daily dates, one row per day)
1640
- * - `color`: required (quantitative, the per-day value)
1641
- *
1642
- * The calendar computes its own weeks-x-weekdays geometry: no positional
1643
- * scales or axes. Multi-year data partitions into one stacked band per year,
1644
- * all bands sharing a single color scale and legend.
1645
- */
1646
- export interface CalendarEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1647
- x: EncodingChannel<TData>;
1648
- color: EncodingChannel<TData>;
1649
- }
1650
-
1651
- /**
1652
- * Encoding for text marks (data-positioned labels).
1653
- * - `text`: required (the field to render as text)
1654
- * - `x`, `y`: optional positioning
1655
- */
1656
- export interface TextEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1657
- text: EncodingChannel<TData>;
1658
- }
1659
-
1660
- /**
1661
- * Encoding for tick marks (strip/rug plots).
1662
- * - `x`: required
1663
- * - `y`: required
1664
- */
1665
- export interface TickEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1666
- x: EncodingChannel<TData>;
1667
- y: EncodingChannel<TData>;
1668
- }
1669
-
1670
- /**
1671
- * Encoding for rect marks (heatmaps, 2D binned plots).
1672
- * - `x`: required
1673
- * - `y`: required
1674
- */
1675
- export interface RectEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1676
- x: EncodingChannel<TData>;
1677
- y: EncodingChannel<TData>;
1678
- }
1679
-
1680
- // ---------------------------------------------------------------------------
1681
- // Accessibility configuration
1682
- // ---------------------------------------------------------------------------
1683
-
1684
- /**
1685
- * Author-facing accessibility overrides.
1686
- *
1687
- * Everything here is optional; when omitted, openchart auto-generates alt
1688
- * text, ARIA labels, and a screen-reader data table from the spec and data.
1689
- */
1690
- export interface A11yConfig {
1691
- /**
1692
- * Alt text announced by screen readers for the chart as a whole. Replaces
1693
- * the auto-generated description. Also settable via the top-level
1694
- * `description` field (Vega-Lite convention); this field wins when both
1695
- * are present.
1696
- */
1697
- description?: string;
1698
- /**
1699
- * Hide the chart from assistive technology entirely (`aria-hidden`).
1700
- * Use only when the chart is decorative or the surrounding content
1701
- * already conveys the same information. Also suppresses the hidden
1702
- * screen-reader data table and keyboard mark navigation.
1703
- */
1704
- hidden?: boolean;
1705
- }
1706
-
1707
- // ---------------------------------------------------------------------------
1708
- // Shared (non-mark-specific) ChartSpec properties
1709
- // ---------------------------------------------------------------------------
1710
-
1711
- /**
1712
- * Properties shared across all ChartSpec mark variants.
1713
- * Extracted to avoid repeating them in every union member.
1714
- *
1715
- * @internal
1716
- */
1717
- interface BaseChartSpec<TData extends DataRow = DataRow> {
1718
- /**
1719
- * Data array: each element is a row with field values.
1720
- * The Vega-Lite object form `{ values: [...] }` is also accepted at runtime
1721
- * and unwrapped during the pre-validation sugar expansion. `{ url }` is not
1722
- * supported (openchart does not fetch data).
1723
- */
1724
- data: TData[];
1725
- /** Data transforms applied in order before encoding (filter, bin, calculate, timeUnit). */
1726
- transform?: Transform[];
1727
- /** Editorial chrome (title, subtitle, source, etc.). */
1728
- chrome?: Chrome;
1729
- /**
1730
- * Top-level title sugar (Vega-Lite aligned). Expanded into `chrome.title`
1731
- * (and `chrome.subtitle` for the object form). `chrome.title` wins when
1732
- * both are set.
1733
- */
1734
- title?: string | { text: string; subtitle?: string };
1735
- /** Top-level subtitle sugar. Expanded into `chrome.subtitle`; `chrome.subtitle` wins. */
1736
- subtitle?: string;
1737
- /**
1738
- * Alt text for the rendered chart (Vega-Lite aligned). Sugar for
1739
- * `a11y.description`; `a11y.description` wins when both are set.
1740
- * When absent, alt text is auto-generated from the spec and data.
1741
- */
1742
- description?: string;
1743
- /** Accessibility overrides. See {@link A11yConfig}. */
1744
- a11y?: A11yConfig;
1745
- /**
1746
- * Fixed render width in pixels (Vega-Lite aligned). Overrides the
1747
- * container-derived width. When both `width` and `height` are set,
1748
- * `responsive: false` is implied unless `responsive` is set explicitly.
1749
- * Omit for the default container-driven sizing.
1750
- */
1751
- width?: number;
1752
- /** Fixed render height in pixels (Vega-Lite aligned). See `width`. */
1753
- height?: number;
1754
- /** Accepted for Vega-Lite compatibility and ignored (with a compile warning). */
1755
- $schema?: string;
1756
- /**
1757
- * KPI/metric cells rendered as a horizontal row between subtitle and chart
1758
- * area. Each cell shows a label/value pair with optional delta and secondary
1759
- * value. Hidden in sparkline mode and auto-stripped when the container is
1760
- * too narrow or short, or when value text would overflow its cell.
1761
- */
1762
- metrics?: Metric[];
1763
- /** Data annotations (text callouts, highlighted ranges, reference lines). */
1764
- annotations?: Annotation[];
1765
- /** Label display configuration. `false` disables all labels, `true` uses defaults. */
1766
- labels?: LabelSpec;
1767
- /** Legend display configuration (position override). */
1768
- legend?: LegendConfig;
1769
- /**
1770
- * Right-side endpoint labels column for multi-series line/area charts.
1771
- *
1772
- * - `true` or `EndpointLabelsConfig`: render the column.
1773
- * - `false`: hide the column.
1774
- * - omitted: auto-enable for multi-series line/area charts, hide otherwise.
1775
- *
1776
- * See {@link EndpointLabelsConfig} for the full suppression truth table that
1777
- * relates this flag to `legend.show` and the legacy end-of-line labels.
1778
- */
1779
- endpointLabels?: boolean | EndpointLabelsConfig;
1780
- /**
1781
- * Responsive behavior. `true`/`false` enables/disables container-width
1782
- * adaptation. Pass a `ResponsiveConfig` object for fine-grained control
1783
- * (e.g. `{ autoThin: false }` to disable automatic annotation thinning).
1784
- */
1785
- responsive?: boolean | ResponsiveConfig;
1786
- /** Theme configuration overrides. */
1787
- theme?: ThemeConfig;
1788
- /** Dark mode behavior. Defaults to "off". */
1789
- darkMode?: DarkMode;
1790
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
1791
- watermark?: boolean;
1792
- /** Series names to hide from rendering. Hidden series remain in the legend but are visually dimmed. */
1793
- hiddenSeries?: string[];
1794
- /** Per-series visual overrides, keyed by series name (the color field value). */
1795
- seriesStyles?: Record<string, SeriesStyle>;
1796
- /**
1797
- * Breakpoint-conditional overrides. Keys are breakpoint names.
1798
- * At compile time, if the container matches a breakpoint, its overrides
1799
- * are shallow-merged into the spec before layout computation.
1800
- */
1801
- overrides?: Partial<Record<Breakpoint, ChartSpecOverride>>;
1802
- /**
1803
- * Animation configuration.
1804
- * - true: enable entrance animation with sensible defaults
1805
- * - false/omitted: no animation (current behavior)
1806
- * - AnimationConfig: full control via enter/update/exit phases
1807
- */
1808
- animation?: AnimationSpec;
1809
- /**
1810
- * Show a vertical crosshair line that tracks the nearest data point on
1811
- * line and area charts. Only active when a voronoi overlay is present.
1812
- * Defaults to false. In sparkline display mode, defaults to false unless
1813
- * explicitly set.
1814
- */
1815
- crosshair?: boolean;
1816
- /**
1817
- * Searchable series highlight ("find your country"). Renders a compact
1818
- * search input above the chart with typeahead over the color channel's
1819
- * distinct values. Selecting a value adds it to the highlight set
1820
- * (multi-select chips, removable); clearing restores the authored
1821
- * `encoding.color.highlight` baseline. Requires a categorical color
1822
- * encoding. Mutually exclusive with edit mode (search wins).
1823
- */
1824
- seriesSearch?: boolean | SeriesSearchConfig;
1825
- /**
1826
- * "You draw it" interactive engagement format: the reader draws their
1827
- * guess of the trend before the real line reveals. Line marks only,
1828
- * single-series only. Mutually exclusive with edit mode (validation
1829
- * warning if both are enabled; youDrawIt is disabled).
1830
- */
1831
- youDrawIt?: YouDrawItConfig;
1832
- /**
1833
- * Display mode controlling how much chart chrome is rendered.
1834
- *
1835
- * - `'full'` (default): full publication chart with chrome, axes, legend, padding.
1836
- * - `'sparkline'`: inline mini-chart for dashboards/KPI cards. Strips chrome,
1837
- * axes, legend, watermark, animation, and crosshair. The mark fills the
1838
- * container edge-to-edge with a small safety margin. Best with
1839
- * `mark: 'line' | 'area' | 'bar' | 'point'`.
1840
- *
1841
- * **Override precedence:** explicit user fields always win, even in sparkline
1842
- * mode. Setting `chrome.title` on a sparkline still renders the title.
1843
- * Setting `legend: { show: true }` still renders the legend. This applies at
1844
- * top-level and per-breakpoint overrides.
1845
- */
1846
- display?: Display;
1847
- /**
1848
- * Resolution strategy for shared/independent scales and axes across facet
1849
- * panels. Only meaningful when `encoding.facet` is present. Shared scales
1850
- * (the default) let readers compare across panels; independent scales give
1851
- * each panel its own domain.
1852
- */
1853
- resolve?: ResolveConfig;
1854
- /**
1855
- * Render order within a LayerSpec. Higher values render on top.
1856
- * When omitted, layers render in array order (later layers paint on top).
1857
- * Axis assignment (left/right y) is always determined by array position,
1858
- * not zIndex.
1859
- */
1860
- zIndex?: number;
1861
- }
1862
-
1863
- /**
1864
- * Chart specification: the primary input for standard chart types.
1865
- *
1866
- * Uses the Vega-Lite `mark` property instead of `type` to specify
1867
- * the visualization mark. The mark can be a string shorthand or an
1868
- * object with additional properties (interpolation, point markers, etc.).
1869
- *
1870
- * This is a discriminated union — the `mark` value determines which encoding
1871
- * channels are required. TypeScript enforces required channels at compile time,
1872
- * matching the runtime rules in `MARK_ENCODING_RULES`.
1873
- *
1874
- * @template TData - The shape of a single data row. When provided, `encoding.*.field`
1875
- * values are constrained to `keyof TData` and IDEs autocomplete your column names.
1876
- * Defaults to `DataRow` (no constraint) — existing untyped specs work unchanged.
1877
- *
1878
- * @example
1879
- * // Typed: field autocomplete + typo detection
1880
- * type SalesRow = { date: string; revenue: number; region: string };
1881
- * const spec: ChartSpec<SalesRow> = {
1882
- * mark: 'line',
1883
- * data: rows,
1884
- * encoding: {
1885
- * x: { field: 'date', type: 'temporal' }, // autocompletes
1886
- * y: { field: 'revenue', type: 'quantitative' }, // typos fail at compile time
1887
- * }
1888
- * };
1889
- *
1890
- * @example
1891
- * // Untyped: works exactly as before (no migration needed)
1892
- * const spec: ChartSpec = {
1893
- * mark: 'bar',
1894
- * data: myData,
1895
- * encoding: { x: { field: 'category', type: 'nominal' }, y: { field: 'value', type: 'quantitative' } }
1896
- * };
1897
- */
1898
- export type ChartSpec<TData extends DataRow = DataRow> =
1899
- | (BaseChartSpec<TData> & {
1900
- mark: 'arc' | (MarkDef & { type: 'arc' });
1901
- encoding: ArcEncoding<TData>;
1902
- })
1903
- | (BaseChartSpec<TData> & {
1904
- mark: 'line' | (MarkDef & { type: 'line' });
1905
- encoding: LineEncoding<TData>;
1906
- })
1907
- | (BaseChartSpec<TData> & {
1908
- mark: 'bar' | (MarkDef & { type: 'bar' });
1909
- encoding: BarEncoding<TData>;
1910
- })
1911
- | (BaseChartSpec<TData> & {
1912
- mark: 'area' | (MarkDef & { type: 'area' });
1913
- encoding: AreaEncoding<TData>;
1914
- })
1915
- | (BaseChartSpec<TData> & {
1916
- mark: 'point' | (MarkDef & { type: 'point' });
1917
- encoding: PointEncoding<TData>;
1918
- })
1919
- | (BaseChartSpec<TData> & {
1920
- mark: 'circle' | (MarkDef & { type: 'circle' });
1921
- encoding: CircleEncoding<TData>;
1922
- })
1923
- | (BaseChartSpec<TData> & {
1924
- mark: 'lollipop' | (MarkDef & { type: 'lollipop' });
1925
- encoding: LollipopEncoding<TData>;
1926
- })
1927
- | (BaseChartSpec<TData> & {
1928
- mark: 'beeswarm' | (MarkDef & { type: 'beeswarm' });
1929
- encoding: BeeswarmEncoding<TData>;
1930
- })
1931
- | (BaseChartSpec<TData> & {
1932
- mark: 'range' | (MarkDef & { type: 'range' });
1933
- encoding: RangeEncoding<TData>;
1934
- })
1935
- | (BaseChartSpec<TData> & {
1936
- mark: 'waffle' | (MarkDef & { type: 'waffle' });
1937
- encoding: WaffleEncoding<TData>;
1938
- })
1939
- | (BaseChartSpec<TData> & {
1940
- mark: 'text' | (MarkDef & { type: 'text' });
1941
- encoding: TextEncoding<TData>;
1942
- })
1943
- | (BaseChartSpec<TData> & {
1944
- mark: 'tick' | (MarkDef & { type: 'tick' });
1945
- encoding: TickEncoding<TData>;
1946
- })
1947
- | (BaseChartSpec<TData> & {
1948
- mark: 'rect' | (MarkDef & { type: 'rect' });
1949
- encoding: RectEncoding<TData>;
1950
- })
1951
- | (BaseChartSpec<TData> & {
1952
- mark: 'rule' | (MarkDef & { type: 'rule' });
1953
- encoding: Encoding<TData>;
1954
- })
1955
- | (BaseChartSpec<TData> & {
1956
- mark: 'calendar' | (MarkDef & { type: 'calendar' });
1957
- encoding: CalendarEncoding<TData>;
1958
- })
1959
- | (BaseChartSpec<TData> & {
1960
- mark: 'parliament' | (MarkDef & { type: 'parliament' });
1961
- encoding: ParliamentEncoding<TData>;
1962
- });
1963
-
1964
- /**
1965
- * Table specification: input for data table visualizations.
1966
- *
1967
- * Tables are a visualization type, not just an HTML grid. They support
1968
- * heatmap coloring, inline sparklines, sorted columns, search, and pagination.
1969
- */
1970
- export interface TableSpec {
1971
- /** Discriminant: always "table". */
1972
- type: 'table';
1973
- /** Data array: each element is a row. */
1974
- data: DataRow[];
1975
- /** Column definitions controlling display, sorting, formatting, and mini-charts. */
1976
- columns: ColumnConfig[];
1977
- /** Optional field to use as a unique row identifier. */
1978
- rowKey?: string;
1979
- /** Editorial chrome. */
1980
- chrome?: Chrome;
1981
- /** Theme configuration overrides. */
1982
- theme?: ThemeConfig;
1983
- /** Dark mode behavior. */
1984
- darkMode?: DarkMode;
1985
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
1986
- watermark?: boolean;
1987
- /** Enable client-side search/filter. */
1988
- search?: boolean;
1989
- /** Pagination configuration. True for defaults, or an object with pageSize. */
1990
- pagination?: boolean | { pageSize: number };
1991
- /** Whether to stick the first column during horizontal scroll. */
1992
- stickyFirstColumn?: boolean;
1993
- /** Compact mode: reduced padding and font sizes. */
1994
- compact?: boolean;
1995
- /** Whether the table adapts to container width. Defaults to true. */
1996
- responsive?: boolean;
1997
- /** Animation configuration for entrance animations. */
1998
- animation?: AnimationSpec;
1999
- }
2000
-
2001
- /** Graph node: must have an id, plus arbitrary data fields. */
2002
- export interface GraphNode {
2003
- /** Unique identifier for the node. */
2004
- id: string;
2005
- /** Arbitrary data fields. */
2006
- [key: string]: unknown;
2007
- }
2008
-
2009
- /** Graph edge: connects two nodes by id. */
2010
- export interface GraphEdge {
2011
- /** Source node id. */
2012
- source: string;
2013
- /** Target node id. */
2014
- target: string;
2015
- /** Arbitrary data fields (weight, type, confidence, etc.). */
2016
- [key: string]: unknown;
2017
- }
2018
-
2019
- /** Per-node visual overrides, keyed by node id. */
2020
- export interface NodeOverride {
2021
- /** Override fill color. */
2022
- fill?: string;
2023
- /** Override radius. */
2024
- radius?: number;
2025
- /** Override stroke width. */
2026
- strokeWidth?: number;
2027
- /** Override stroke color. */
2028
- stroke?: string;
2029
- /** Force label to always show regardless of zoom/priority. */
2030
- alwaysShowLabel?: boolean;
2031
- }
2032
-
2033
- export interface GraphSpec {
2034
- /** Discriminant: always "graph". */
2035
- type: 'graph';
2036
- /** Node array. Each node must have an id field. */
2037
- nodes: GraphNode[];
2038
- /** Edge array. Each edge connects source and target node ids. */
2039
- edges: GraphEdge[];
2040
- /** Graph-specific encoding mapping visual properties to node/edge fields. */
2041
- encoding?: GraphEncoding;
2042
- /** Layout algorithm configuration. */
2043
- layout?: GraphLayoutConfig;
2044
- /** Per-node visual overrides, keyed by node id. */
2045
- nodeOverrides?: Record<string, NodeOverride>;
2046
- /** Editorial chrome. */
2047
- chrome?: Chrome;
2048
- /** Annotations. */
2049
- annotations?: Annotation[];
2050
- /** Theme configuration overrides. */
2051
- theme?: ThemeConfig;
2052
- /** Dark mode behavior. */
2053
- darkMode?: DarkMode;
2054
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2055
- watermark?: boolean;
2056
- }
2057
-
2058
- // ---------------------------------------------------------------------------
2059
- // Layer spec (multi-layer composition)
2060
- // ---------------------------------------------------------------------------
2061
-
2062
- /**
2063
- * Resolution strategy for shared resources across layers.
2064
- * 'shared' (default): union domains, single axis/legend.
2065
- * 'independent': each layer gets its own scale/axis/legend.
2066
- */
2067
- export type ResolveMode = 'shared' | 'independent';
2068
-
2069
- /**
2070
- * Per-channel resolution config. Controls whether scales, axes, and legends
2071
- * are shared or independent across layers.
2072
- */
2073
- export interface ResolveConfig {
2074
- scale?: Partial<Record<'x' | 'y' | 'color' | 'size', ResolveMode>>;
2075
- axis?: Partial<Record<'x' | 'y', ResolveMode>>;
2076
- legend?: Partial<Record<'color' | 'size', ResolveMode>>;
2077
- }
2078
-
2079
- /**
2080
- * Layer specification: composites multiple chart layers into a single view.
2081
- *
2082
- * Each element in `layer` is either a ChartSpec or another LayerSpec (nested).
2083
- * Shared data, encoding, and transforms at the LayerSpec level are inherited
2084
- * by children that don't define their own.
2085
- *
2086
- * @template TData - The shape of a single data row. When all layers share the
2087
- * same data shape, pass it here to get field autocomplete across the layer.
2088
- * For layers with different data shapes per child, omit TData (defaults to
2089
- * `DataRow`) — children can be independently typed.
2090
- */
2091
- export interface LayerSpec<TData extends DataRow = DataRow> {
2092
- /** Array of child layers (ChartSpec or nested LayerSpec). */
2093
- layer: (ChartSpec<TData> | LayerSpec<TData>)[];
2094
- /** Shared data inherited by children without their own data. */
2095
- data?: TData[];
2096
- /** Shared encoding inherited by children (overridden per-channel by child). */
2097
- encoding?: Encoding<TData>;
2098
- /** Shared transforms. Parent transforms run before child transforms. */
2099
- transform?: Transform[];
2100
- /** Editorial chrome (title, subtitle, source, etc.). */
2101
- chrome?: Chrome;
2102
- /** Top-level title sugar (VL aligned). Expanded into `chrome.title`; `chrome.title` wins. */
2103
- title?: string | { text: string; subtitle?: string };
2104
- /** Top-level subtitle sugar. Expanded into `chrome.subtitle`; `chrome.subtitle` wins. */
2105
- subtitle?: string;
2106
- /** Fixed render width in pixels (VL aligned). Overrides the container-derived width. */
2107
- width?: number;
2108
- /** Fixed render height in pixels (VL aligned). Overrides the container-derived height. */
2109
- height?: number;
2110
- /** Accepted for Vega-Lite compatibility and ignored (with a compile warning). */
2111
- $schema?: string;
2112
- /** Annotations on the layered view. */
2113
- annotations?: Annotation[];
2114
- /** Label display configuration. `false` disables all labels, `true` uses defaults. */
2115
- labels?: LabelSpec;
2116
- /** Legend display configuration. */
2117
- legend?: LegendConfig;
2118
- /** Whether the chart adapts to container width. Defaults to true. */
2119
- responsive?: boolean;
2120
- /** Theme configuration overrides. */
2121
- theme?: ThemeConfig;
2122
- /** Dark mode behavior. Defaults to "off". */
2123
- darkMode?: DarkMode;
2124
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2125
- watermark?: boolean;
2126
- /** Resolution strategy for shared scales/axes/legends. */
2127
- resolve?: ResolveConfig;
2128
- /** Hidden series names. */
2129
- hiddenSeries?: string[];
2130
- /** Endpoint labels column, inherited by child layers that don't set their own. */
2131
- endpointLabels?: boolean | EndpointLabelsConfig;
2132
- /** Animation configuration. */
2133
- animation?: AnimationSpec;
2134
- }
2135
-
2136
- // ---------------------------------------------------------------------------
2137
- // Sankey spec (encoding-centric flow diagram)
2138
- // ---------------------------------------------------------------------------
2139
-
2140
- /** Node alignment strategy for sankey layout. */
2141
- export type SankeyNodeAlign = 'left' | 'right' | 'center' | 'justify';
2142
-
2143
- /** Link coloring strategy for sankey diagrams. */
2144
- export type SankeyLinkColor = 'gradient' | 'source' | 'target' | 'neutral';
2145
-
2146
- /** Encoding channels specific to sankey diagrams. */
2147
- export interface SankeyEncoding {
2148
- /** Source node field (required, nominal). */
2149
- source: EncodingChannel;
2150
- /** Target node field (required, nominal). */
2151
- target: EncodingChannel;
2152
- /** Flow value field (required, quantitative). */
2153
- value: EncodingChannel;
2154
- /** Color encoding for nodes/links (optional, nominal). */
2155
- color?: EncodingChannel;
2156
- /** Tooltip encoding (optional). */
2157
- tooltip?: EncodingChannel | EncodingChannel[];
2158
- }
2159
-
2160
- export interface SankeySpec {
2161
- /** Discriminant: always "sankey". */
2162
- type: 'sankey';
2163
- /** Tabular flow data. Each row is a source-target-value link. */
2164
- data: DataRow[];
2165
- /** Encoding channels mapping data fields to visual properties. */
2166
- encoding: SankeyEncoding;
2167
- /** Width of node rectangles in pixels. Defaults to 12. */
2168
- nodeWidth?: number;
2169
- /** Vertical padding between nodes in pixels. Defaults to 16. */
2170
- nodePadding?: number;
2171
- /** Node alignment algorithm. Defaults to 'justify'. */
2172
- nodeAlign?: SankeyNodeAlign;
2173
- /** Number of layout relaxation iterations. Defaults to 6. */
2174
- iterations?: number;
2175
- /** Link coloring strategy. Defaults to 'gradient'. */
2176
- linkStyle?: SankeyLinkColor;
2177
- /** Link fill opacity (0-1). Defaults to 0.5 in light mode, 0.75 in dark mode. */
2178
- linkOpacity?: number;
2179
- /**
2180
- * Which side of each node to place labels. 'auto' uses the default heuristic
2181
- * (left-column right, right-column left, middle right). 'right' forces all
2182
- * labels to the right of their nodes. 'left' forces all labels to the left.
2183
- * Defaults to 'auto'.
2184
- */
2185
- nodeLabelAlign?: 'auto' | 'left' | 'right';
2186
- /**
2187
- * Explicit vertical ordering of nodes. An array of node IDs listed from
2188
- * top to bottom. Nodes sharing the same column are sorted by their position
2189
- * in this array. Nodes not in the array are placed after those that are.
2190
- */
2191
- nodeSort?: string[];
2192
- /** Editorial chrome (title, subtitle, source, byline, footer). */
2193
- chrome?: Chrome;
2194
- /** Legend display configuration. */
2195
- legend?: LegendConfig;
2196
- /** Theme configuration overrides. */
2197
- theme?: ThemeConfig;
2198
- /** Dark mode behavior. Defaults to "off". */
2199
- darkMode?: DarkMode;
2200
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2201
- watermark?: boolean;
2202
- /** Animation configuration for entrance animations. */
2203
- animation?: AnimationSpec;
2204
- /**
2205
- * d3-format string applied to flow values in tooltips and ARIA labels.
2206
- * Uses the literal suffix extension: ".0f%" appends "%" to the formatted
2207
- * number (data value 28 renders as "28%"). For currency: "$,.0f" or "$~s".
2208
- * For SI suffixes: "~s" (renders 1000 as "1k"). When not set, values use
2209
- * the default number formatter.
2210
- * @deprecated Use the encoding channel's `format` field instead. This
2211
- * property is expanded into the encoding during compilation.
2212
- */
2213
- valueFormat?: string;
2214
- }
2215
-
2216
- // ---------------------------------------------------------------------------
2217
- // TileMap spec (US state tile grid map)
2218
- // ---------------------------------------------------------------------------
2219
-
2220
- /** Encoding channels specific to tile map visualizations. */
2221
- export interface TileMapEncoding {
2222
- /** State code field (required, nominal). Maps to US state abbreviations. */
2223
- state: EncodingChannel;
2224
- /**
2225
- * Value field. For quantitative mode, maps to a sequential color scale.
2226
- * For categorical mode (when color channel is present or values are strings),
2227
- * maps to category labels shown in tooltips.
2228
- */
2229
- value: EncodingChannel;
2230
- /** Color encoding channel (optional, nominal). When present, enables categorical coloring. */
2231
- color?: EncodingChannel;
2232
- /** Tooltip encoding (optional). */
2233
- tooltip?: EncodingChannel | EncodingChannel[];
2234
- }
2235
-
2236
- /** Sequential color palette names available for tile maps. */
2237
- export type TileMapPalette = 'blue' | 'green' | 'orange' | 'purple' | 'teal';
2238
-
2239
- export interface TileMapSpec {
2240
- /** Discriminant: always "tilemap". */
2241
- type: 'tilemap';
2242
- /**
2243
- * Data for the tile map. Accepts either:
2244
- * - A record mapping state codes to numeric values: `{ "CA": 12000, "TX": 8500 }`
2245
- * - A record mapping state codes to string categories: `{ "CA": "high", "TX": "low" }`
2246
- * - Tabular data rows with state and value fields (requires encoding)
2247
- */
2248
- data: Record<string, number | string | null> | DataRow[];
2249
- /**
2250
- * Encoding channels mapping data fields to visual properties.
2251
- * Required when data is DataRow[]. Auto-generated when data is a record map.
2252
- */
2253
- encoding?: TileMapEncoding;
2254
- /** Sequential color palette. Defaults to 'blue'. Only used in quantitative mode. */
2255
- palette?: TileMapPalette;
2256
- /**
2257
- * Custom category-to-color mapping for categorical tilemaps.
2258
- * Keys are category values, values are CSS color strings.
2259
- * When provided, forces categorical mode regardless of encoding.
2260
- * Example: `{ "medical_only": "#ee4a73", "religious": "#e07d00", "philosophical": "#06b6d4" }`
2261
- */
2262
- colors?: Record<string, string>;
2263
- /** Editorial chrome (title, subtitle, source, byline, footer). */
2264
- chrome?: Chrome;
2265
- /** Legend display configuration. */
2266
- legend?: LegendConfig;
2267
- /** Theme configuration overrides. */
2268
- theme?: ThemeConfig;
2269
- /** Dark mode behavior. Defaults to "off". */
2270
- darkMode?: DarkMode;
2271
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2272
- watermark?: boolean;
2273
- /** Animation configuration for entrance animations. */
2274
- animation?: AnimationSpec;
2275
- /**
2276
- * d3-format string applied to tile values, legend labels, and tooltips.
2277
- * Examples: ".1f" for one decimal, "$,.0f" for currency, "~s" for SI.
2278
- * @deprecated Use the encoding channel's `format` field instead. This
2279
- * property is expanded into the encoding during compilation.
2280
- */
2281
- valueFormat?: string;
2282
- }
2283
-
2284
- // ---------------------------------------------------------------------------
2285
- // Map spec (choropleth / symbol maps)
2286
- // ---------------------------------------------------------------------------
2287
-
2288
- /** Supported map projections. */
2289
- export type MapProjection = 'albersUsa' | 'mercator' | 'equalEarth' | 'identity';
2290
-
2291
- /** Focus target for map camera: feature id(s) to zoom/pan to. */
2292
- export type MapFocus =
2293
- | string
2294
- | number
2295
- | Array<string | number>
2296
- | { features: string | number | Array<string | number>; padding?: number };
2297
-
2298
- /** Geo configuration for map specs. */
2299
- export interface MapGeo {
2300
- /** TopoJSON topology object. User imports this from us-atlas, world-atlas, or their own source. */
2301
- features: unknown;
2302
- /** Field in the TopoJSON feature properties to use as the join key. Defaults to 'id'. */
2303
- idField?: string;
2304
- /** Map projection. Defaults to 'albersUsa'. */
2305
- projection?: MapProjection;
2306
- /** Focus on specific feature(s). null clears focus from a prior story step. */
2307
- focus?: MapFocus | null;
2308
- }
2309
-
2310
- /** Encoding channels specific to map visualizations. */
2311
- export interface MapEncoding {
2312
- /** Join key field in the data (required). Maps data rows to geo features via geo.idField. */
2313
- key: EncodingChannel;
2314
- /** Color encoding (required, quantitative or nominal). Drives fill color of features. */
2315
- color: EncodingChannel;
2316
- /** Tooltip encoding (optional). */
2317
- tooltip?: EncodingChannel | EncodingChannel[];
2318
- }
2319
-
2320
- export interface MapSpec {
2321
- /** Discriminant: always "map". */
2322
- type: 'map';
2323
- /** Geo configuration: TopoJSON features, join key, and projection. */
2324
- geo: MapGeo;
2325
- /** Tabular data to join to geo features. */
2326
- data: DataRow[];
2327
- /** Encoding channels mapping data fields to visual properties. */
2328
- encoding: MapEncoding;
2329
- /** Editorial chrome (title, subtitle, source, byline, footer). */
2330
- chrome?: Chrome;
2331
- /** Legend display configuration. */
2332
- legend?: LegendConfig;
2333
- /** Theme configuration overrides. */
2334
- theme?: ThemeConfig;
2335
- /** Dark mode behavior. Defaults to "off". */
2336
- darkMode?: DarkMode;
2337
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2338
- watermark?: boolean;
2339
- /** Animation configuration for entrance animations. */
2340
- animation?: AnimationSpec;
2341
- /**
2342
- * d3-format string applied to color values in tooltips and legend.
2343
- * @deprecated Use the encoding channel's `format` field instead.
2344
- */
2345
- valueFormat?: string;
2346
- }
2347
-
2348
- // ---------------------------------------------------------------------------
2349
- // BarList spec (ranked horizontal bar list)
2350
- // ---------------------------------------------------------------------------
2351
-
2352
- export interface BarListSpec {
2353
- /** Discriminant: always "barlist". */
2354
- type: 'barlist';
2355
- /**
2356
- * Data rows. Each row must have at least a label field and a value field.
2357
- * Rendered as a ranked list of horizontal bars.
2358
- */
2359
- data: DataRow[];
2360
- /** Encoding channels mapping data fields to visual properties. */
2361
- encoding: BarListEncoding;
2362
- /** Bar height in pixels. Defaults to 6. */
2363
- barHeight?: number;
2364
- /** Corner radius: number in px or "pill" for fully rounded ends. Defaults to "pill". */
2365
- cornerRadius?: number | 'pill';
2366
- /** Maximum number of rows to show. Defaults to 20. */
2367
- maxItems?: number;
2368
- /** Editorial chrome (title, subtitle, source, byline, footer). */
2369
- chrome?: Chrome;
2370
- /** Theme configuration overrides. */
2371
- theme?: ThemeConfig;
2372
- /** Dark mode behavior. Defaults to "off". */
2373
- darkMode?: DarkMode;
2374
- /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2375
- watermark?: boolean;
2376
- /** Animation configuration for entrance animations. */
2377
- animation?: AnimationSpec;
2378
- /**
2379
- * d3-format string applied to bar values and tooltips.
2380
- * Examples: ".1f" for one decimal, "$,.0f" for currency, "~s" for SI.
2381
- * @deprecated Use the encoding channel's `format` field instead. This
2382
- * property is expanded into the encoding during compilation.
2383
- */
2384
- valueFormat?: string;
2385
- }
2386
-
2387
- /** Encoding channels for a barlist visualization. */
2388
- export interface BarListEncoding {
2389
- /** Label field (required, nominal). The category name for each row. */
2390
- label: EncodingChannel;
2391
- /** Value field (required, quantitative). The numeric value that drives bar width. */
2392
- value: EncodingChannel;
2393
- /** Subtitle field (optional, nominal). Secondary text shown beside the label in lighter weight. */
2394
- subtitle?: EncodingChannel;
2395
- /** Color field (optional, nominal). Maps categories to colors from the palette. When omitted, colors cycle by row index. */
2396
- color?: EncodingChannel;
2397
- /** Tooltip encoding (optional). */
2398
- tooltip?: EncodingChannel | EncodingChannel[];
2399
- }
2400
-
2401
- /**
2402
- * Top-level visualization spec: union discriminated by structural shape.
2403
- *
2404
- * - ChartSpec: has `mark` field (no `type`, no `layer`)
2405
- * - LayerSpec: has `layer` field
2406
- * - TableSpec: has `type: 'table'`
2407
- * - GraphSpec: has `type: 'graph'`
2408
- * - SankeySpec: has `type: 'sankey'`
2409
- * - TileMapSpec: has `type: 'tilemap'`
2410
- * - MapSpec: has `type: 'map'`
2411
- * - BarListSpec: has `type: 'barlist'`
2412
- *
2413
- * Election parliament (hemicycle) charts are `ChartSpec` with `mark:
2414
- * 'parliament'`, not a separate top-level type: seat data (party + seats)
2415
- * maps naturally onto encoding.color/y like arc and waffle, and riding the
2416
- * existing chart pipeline gets scales, legend, tooltips, a11y, and dark mode
2417
- * for free instead of a second bespoke compile path (see
2418
- * `packages/engine/src/charts/parliament/`).
2419
- */
2420
- export type VizSpec =
2421
- | ChartSpec
2422
- | LayerSpec
2423
- | TableSpec
2424
- | GraphSpec
2425
- | SankeySpec
2426
- | TileMapSpec
2427
- | MapSpec
2428
- | BarListSpec;
2429
-
2430
- /**
2431
- * Chart spec without runtime data, for persistence/storage.
2432
- * Generic: `ChartSpecWithoutData<MyRow>` constrains encoding field names.
2433
- */
2434
- export type ChartSpecWithoutData<TData extends DataRow = DataRow> = Omit<ChartSpec<TData>, 'data'>;
2435
- /** Table spec without runtime data and columns, for persistence/storage. Columns can be auto-generated via dataTable(). */
2436
- export type TableSpecWithoutData = Omit<TableSpec, 'data' | 'columns'>;
2437
- /** Graph spec without runtime data, for persistence/storage. */
2438
- export type GraphSpecWithoutData = Omit<GraphSpec, 'nodes' | 'edges'>;
2439
- /** Sankey spec without runtime data, for persistence/storage. */
2440
- export type SankeySpecWithoutData = Omit<SankeySpec, 'data'>;
2441
- /** TileMap spec without runtime data, for persistence/storage. */
2442
- export type TileMapSpecWithoutData = Omit<TileMapSpec, 'data'>;
2443
- /** Map spec without runtime data, for persistence/storage. */
2444
- export type MapSpecWithoutData = Omit<MapSpec, 'data'>;
2445
- /** BarList spec without runtime data, for persistence/storage. */
2446
- export type BarListSpecWithoutData = Omit<BarListSpec, 'data'>;
2447
- /** Union of data-stripped spec types for persistence/storage. */
2448
- export type StoredVizSpec =
2449
- | ChartSpecWithoutData
2450
- | TableSpecWithoutData
2451
- | GraphSpecWithoutData
2452
- | SankeySpecWithoutData
2453
- | TileMapSpecWithoutData
2454
- | MapSpecWithoutData
2455
- | BarListSpecWithoutData;
2456
-
2457
- // ---------------------------------------------------------------------------
2458
- // Transforms (Vega-Lite aligned)
2459
- // ---------------------------------------------------------------------------
2460
-
2461
- /** Logical AND combinator for filter predicates. */
2462
- export interface LogicalAnd<T> {
2463
- and: T[];
2464
- }
2465
-
2466
- /** Logical OR combinator for filter predicates. */
2467
- export interface LogicalOr<T> {
2468
- or: T[];
2469
- }
2470
-
2471
- /** Logical NOT combinator for filter predicates. */
2472
- export interface LogicalNot<T> {
2473
- not: T;
2474
- }
2475
-
2476
- /** A relative-time reference that resolves against the data extent. */
2477
- export interface RelativeTimeRef {
2478
- anchor: 'max' | 'min';
2479
- offset: number;
2480
- unit: 'day' | 'week' | 'month' | 'quarter' | 'year';
2481
- }
2482
-
2483
- /** A predicate that tests a field value against a condition. */
2484
- export interface FieldPredicate {
2485
- /**
2486
- * Data field to test.
2487
- * Note: FieldPredicate is used in transforms (FilterTransform) which operate
2488
- * on the raw DataRow type — field is typed as string here since transforms
2489
- * can reference computed/derived fields not present in the original TData.
2490
- */
2491
- field: string;
2492
- /** Equals comparison. */
2493
- equal?: unknown;
2494
- /** Less than. */
2495
- lt?: number | RelativeTimeRef;
2496
- /** Less than or equal. */
2497
- lte?: number | RelativeTimeRef;
2498
- /** Greater than. */
2499
- gt?: number | RelativeTimeRef;
2500
- /** Greater than or equal. */
2501
- gte?: number | RelativeTimeRef;
2502
- /** Inclusive range [min, max]. */
2503
- range?: [number | RelativeTimeRef, number | RelativeTimeRef];
2504
- /** Value is one of these. */
2505
- oneOf?: unknown[];
2506
- /** Whether the value is valid (non-null, non-undefined, non-NaN). */
2507
- valid?: boolean;
2508
- }
2509
-
2510
- /**
2511
- * A filter predicate: a field predicate or logical combination of predicates.
2512
- */
2513
- export type FilterPredicate =
2514
- | FieldPredicate
2515
- | LogicalAnd<FilterPredicate>
2516
- | LogicalOr<FilterPredicate>
2517
- | LogicalNot<FilterPredicate>;
2518
-
2519
- /** Parameters for binning a field. */
2520
- export interface BinParams {
2521
- /** Maximum number of bins. */
2522
- maxbins?: number;
2523
- /** Exact step size between bins. */
2524
- step?: number;
2525
- /** Whether to nice-ify bin boundaries. */
2526
- nice?: boolean;
2527
- /** Explicit extent [min, max] for binning. */
2528
- extent?: [number, number];
2529
- }
2530
-
2531
- /** Expression for calculate transforms. */
2532
- export interface CalculateExpression {
2533
- /** Arithmetic operation to apply. */
2534
- op: '+' | '-' | '*' | '/' | 'abs' | 'round' | 'floor' | 'ceil' | 'log' | 'sqrt';
2535
- /** Primary field to operate on. */
2536
- field: string;
2537
- /** Secondary field for binary operations (+, -, *, /). */
2538
- field2?: string;
2539
- /** Constant value for binary operations when field2 is not provided. */
2540
- value?: number;
2541
- }
2542
-
2543
- /**
2544
- * Time unit granularity for temporal field transformations.
2545
- * Follows Vega-Lite conventions.
2546
- */
2547
- export type TimeUnit =
2548
- | 'year'
2549
- | 'quarter'
2550
- | 'month'
2551
- | 'week'
2552
- | 'day'
2553
- | 'dayofyear'
2554
- | 'date'
2555
- | 'hours'
2556
- | 'minutes'
2557
- | 'seconds'
2558
- | 'milliseconds'
2559
- | 'yearmonth'
2560
- | 'yearmonthdate'
2561
- | 'monthdate'
2562
- | 'hoursminutes';
2563
-
2564
- /** Filter transform: removes rows that don't match the predicate. */
2565
- export interface FilterTransform {
2566
- filter: FilterPredicate;
2567
- }
2568
-
2569
- /** Bin transform: adds binned field(s) to each row. */
2570
- export interface BinTransform {
2571
- bin: true | BinParams;
2572
- field: string;
2573
- as: string | [string, string];
2574
- }
2575
-
2576
- /** Calculate transform: adds a computed field to each row. */
2577
- export interface CalculateTransform {
2578
- calculate: CalculateExpression;
2579
- as: string;
2580
- }
2581
-
2582
- /** Time unit transform: extracts a time unit from a temporal field. */
2583
- export interface TimeUnitTransform {
2584
- timeUnit: TimeUnit;
2585
- field: string;
2586
- as: string;
2587
- }
2588
-
2589
- /**
2590
- * Aggregate transform: group rows and compute summary statistics (VL aligned).
2591
- * Produces one row per group with the groupby fields and computed aggregates.
2592
- */
2593
- export interface AggregateTransform {
2594
- aggregate: Array<{ op: AggregateOp; field: string; as: string }>;
2595
- groupby: string[];
2596
- }
2597
-
2598
- /**
2599
- * Fold transform: unpivot wide-format columns into key/value rows (VL aligned).
2600
- * For each input row, produces N output rows (one per fold field) with all
2601
- * non-fold fields copied plus a key column (field name) and value column (field value).
2602
- */
2603
- export interface FoldTransform {
2604
- fold: string[];
2605
- /** Output field names for [key, value]. Defaults to ['key', 'value']. */
2606
- as?: [string, string];
2607
- }
2608
-
2609
- /** Window operation types for computing values relative to other rows. */
2610
- export type WindowOp = 'lag' | 'lead' | 'diff' | 'pct_change' | 'cumsum' | 'rank' | 'first_value';
2611
-
2612
- /** Sort field definition for window transforms. */
2613
- export interface WindowSortField {
2614
- field: string;
2615
- order?: 'ascending' | 'descending';
2616
- }
2617
-
2618
- /** Window field definition specifying which operation to compute. */
2619
- export interface WindowFieldDef {
2620
- op: WindowOp;
2621
- field: string;
2622
- /** Row offset for lag/lead/diff/pct_change. Defaults to 1. */
2623
- offset?: number;
2624
- as: string;
2625
- }
2626
-
2627
- /** Window transform: computes values relative to other rows in sort order within a partition. */
2628
- export interface WindowTransform {
2629
- window: WindowFieldDef[];
2630
- /** Fields to sort by within each partition. */
2631
- sort: WindowSortField[];
2632
- /** Fields to partition (group) by. Each group is windowed independently. */
2633
- groupby?: string[];
2634
- }
2635
-
2636
- /** Discriminated union of all transform types. */
2637
- export type Transform =
2638
- | FilterTransform
2639
- | BinTransform
2640
- | CalculateTransform
2641
- | TimeUnitTransform
2642
- | AggregateTransform
2643
- | FoldTransform
2644
- | WindowTransform;
2645
-
2646
- // ---------------------------------------------------------------------------
2647
- // Conditional encoding (Vega-Lite aligned)
2648
- // ---------------------------------------------------------------------------
2649
-
2650
- /**
2651
- * A single condition with a test predicate and resulting value/field.
2652
- * When the test passes for a datum, the condition's value/field is used.
2653
- *
2654
- * @template TData - Propagated from ChartSpec<TData>. Constrains `field` to
2655
- * `keyof TData & string` when a typed data row is provided.
2656
- */
2657
- export interface Condition<TData extends DataRow = DataRow> {
2658
- /** Predicate to test against each datum. */
2659
- test: FilterPredicate;
2660
- /**
2661
- * Static value to use when the condition is true.
2662
- * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2663
- * or a gradient definition for color channels.
2664
- */
2665
- value?: string | number | boolean | null | GradientDef;
2666
- /**
2667
- * Data field to use when the condition is true.
2668
- * Constrained to column names of `TData` when using `ChartSpec<TData>`.
2669
- */
2670
- field?: keyof TData & string;
2671
- /** Field type for the conditional field. */
2672
- type?: FieldType;
2673
- }
2674
-
2675
- /**
2676
- * A conditional value definition for an encoding channel.
2677
- * Evaluates conditions in order, falling back to the default value.
2678
- *
2679
- * @template TData - Propagated from ChartSpec<TData>.
2680
- */
2681
- export interface ConditionalValueDef<TData extends DataRow = DataRow> {
2682
- /** One or more conditions to evaluate. */
2683
- condition: Condition<TData> | Condition<TData>[];
2684
- /**
2685
- * Default value when no condition matches.
2686
- * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2687
- * or a gradient definition for color channels.
2688
- */
2689
- value?: string | number | boolean | null | GradientDef;
2690
- }
2691
-
2692
- /**
2693
- * Check if a channel definition is a regular EncodingChannel (has 'field' at top level).
2694
- * Use this to narrow `EncodingChannel | ConditionalValueDef | ValueDef` in encoding
2695
- * channels that support conditional encoding (color, size, opacity).
2696
- */
2697
- export function isEncodingChannel<TData extends DataRow = DataRow>(
2698
- def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined,
2699
- ): def is EncodingChannel<TData> {
2700
- if (!def) return false;
2701
- return 'field' in def && !('condition' in def);
2702
- }
2703
-
2704
- /**
2705
- * Check if a channel definition is a ConditionalValueDef.
2706
- */
2707
- export function isConditionalDef<TData extends DataRow = DataRow>(
2708
- def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined,
2709
- ): def is ConditionalValueDef<TData> {
2710
- if (!def) return false;
2711
- return 'condition' in def;
2712
- }
2713
-
2714
- /**
2715
- * Check if a channel definition is a bare ValueDef (constant value, no field
2716
- * or condition). These are expanded to mark-level properties during the
2717
- * pre-validation sugar expansion.
2718
- */
2719
- export function isValueDef<TData extends DataRow = DataRow>(
2720
- def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined,
2721
- ): def is ValueDef {
2722
- if (!def) return false;
2723
- return 'value' in def && !('condition' in def) && !('field' in def);
2724
- }
2725
-
2726
- // ---------------------------------------------------------------------------
2727
- // Mark type helpers
2728
- // ---------------------------------------------------------------------------
2729
-
2730
- /** All valid mark type strings for runtime checking. */
2731
- export const MARK_TYPES: ReadonlySet<string> = new Set<MarkType>([
2732
- 'bar',
2733
- 'line',
2734
- 'area',
2735
- 'point',
2736
- 'circle',
2737
- 'arc',
2738
- 'text',
2739
- 'rule',
2740
- 'tick',
2741
- 'rect',
2742
- 'lollipop',
2743
- 'beeswarm',
2744
- 'range',
2745
- 'waffle',
2746
- 'calendar',
2747
- 'parliament',
2748
- ]);
2749
-
2750
- /**
2751
- * Mark types that lay out inside the chart area without positional axes
2752
- * (no axis or gridline computation; margins shrink to padding).
2753
- */
2754
- export function isAxislessMark(markType: MarkType): boolean {
2755
- return (
2756
- markType === 'arc' ||
2757
- markType === 'waffle' ||
2758
- markType === 'calendar' ||
2759
- markType === 'parliament'
2760
- );
2761
- }
2762
-
2763
- /**
2764
- * Extract the mark type string from a mark field (string or MarkDef).
2765
- */
2766
- export function resolveMarkType(mark: MarkType | MarkDef): MarkType {
2767
- return typeof mark === 'string' ? mark : mark.type;
2768
- }
2769
-
2770
- /**
2771
- * Extract the full MarkDef from a mark field, filling in defaults for string shorthand.
2772
- */
2773
- export function resolveMarkDef(mark: MarkType | MarkDef): MarkDef {
2774
- return typeof mark === 'string' ? { type: mark } : mark;
2775
- }
2776
-
2777
- // ---------------------------------------------------------------------------
2778
- // Type guards
2779
- // ---------------------------------------------------------------------------
2780
-
2781
- /** Check if a spec is a ChartSpec (has `mark` field, not a layer/table/graph). */
2782
- export function isChartSpec(spec: VizSpec | Record<string, unknown>): spec is ChartSpec {
2783
- return 'mark' in spec && !('layer' in spec);
2784
- }
2785
-
2786
- /** Check if a spec is a LayerSpec (has `layer` array). */
2787
- export function isLayerSpec(spec: VizSpec | Record<string, unknown>): spec is LayerSpec {
2788
- return 'layer' in spec && Array.isArray((spec as Record<string, unknown>).layer);
2789
- }
2790
-
2791
- /** Check if a spec is a TableSpec. */
2792
- export function isTableSpec(spec: VizSpec | Record<string, unknown>): spec is TableSpec {
2793
- return 'type' in spec && (spec as Record<string, unknown>).type === 'table';
2794
- }
2795
-
2796
- /** Check if a spec is a GraphSpec. */
2797
- export function isGraphSpec(spec: VizSpec | Record<string, unknown>): spec is GraphSpec {
2798
- return 'type' in spec && (spec as Record<string, unknown>).type === 'graph';
2799
- }
2800
-
2801
- /** Check if a spec is a SankeySpec. */
2802
- export function isSankeySpec(spec: VizSpec | Record<string, unknown>): spec is SankeySpec {
2803
- return 'type' in spec && (spec as Record<string, unknown>).type === 'sankey';
2804
- }
2805
-
2806
- /** Check if a spec is a TileMapSpec. */
2807
- export function isTileMapSpec(spec: VizSpec | Record<string, unknown>): spec is TileMapSpec {
2808
- return 'type' in spec && (spec as Record<string, unknown>).type === 'tilemap';
2809
- }
2810
-
2811
- /** Check if a spec is a BarListSpec. */
2812
- export function isBarListSpec(spec: VizSpec | Record<string, unknown>): spec is BarListSpec {
2813
- return 'type' in spec && (spec as Record<string, unknown>).type === 'barlist';
2814
- }
2815
-
2816
- /** Check if a spec is a MapSpec. */
2817
- export function isMapSpec(spec: VizSpec | Record<string, unknown>): spec is MapSpec {
2818
- return 'type' in spec && (spec as Record<string, unknown>).type === 'map';
2819
- }
2820
-
2821
- // ---------------------------------------------------------------------------
2822
- // Annotation type guards
2823
- // ---------------------------------------------------------------------------
2824
-
2825
- /** Check if an annotation is a TextAnnotation. */
2826
- export function isTextAnnotation(annotation: Annotation): annotation is TextAnnotation {
2827
- return annotation.type === 'text';
2828
- }
2829
-
2830
- /** Check if an annotation is a RangeAnnotation. */
2831
- export function isRangeAnnotation(annotation: Annotation): annotation is RangeAnnotation {
2832
- return annotation.type === 'range';
2833
- }
2834
-
2835
- /**
2836
- * Check if an annotation is a RefLineAnnotation. Also accepts the deprecated
2837
- * `'rule'` type at runtime (pre-sugar-expansion input), since `annotation.type`
2838
- * may not yet be narrowed to the canonical `'refline'` form.
2839
- */
2840
- export function isRefLineAnnotation(annotation: Annotation): annotation is RefLineAnnotation {
2841
- const type: string = annotation.type;
2842
- return type === 'refline' || type === 'rule';
2843
- }
2844
-
2845
- // ---------------------------------------------------------------------------
2846
- // Display name mapping for accessibility
2847
- // ---------------------------------------------------------------------------
2848
-
2849
- /** Human-readable display names for mark types (used in alt text, error messages). */
2850
- export const MARK_DISPLAY_NAMES: Record<MarkType, string> = {
2851
- bar: 'Bar chart',
2852
- line: 'Line chart',
2853
- area: 'Area chart',
2854
- point: 'Scatter plot',
2855
- circle: 'Dot plot',
2856
- arc: 'Pie chart', // overridden to "Donut chart" when innerRadius > 0
2857
- text: 'Text chart',
2858
- rule: 'Rule chart',
2859
- tick: 'Tick plot',
2860
- rect: 'Heatmap',
2861
- lollipop: 'Lollipop chart',
2862
- beeswarm: 'Beeswarm chart',
2863
- range: 'Range plot',
2864
- waffle: 'Waffle chart',
2865
- calendar: 'Calendar heatmap',
2866
- parliament: 'Parliament chart',
2867
- };