@opendata-ai/openchart-core 7.10.0 → 8.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/index.d.ts +1154 -119
  2. package/dist/index.js +530 -41
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +2 -1
  5. package/dist/styles.css.map +1 -0
  6. package/package.json +9 -3
  7. package/schema/chart.schema.json +8957 -0
  8. package/schema/table.schema.json +861 -0
  9. package/schema/vizspec.schema.json +17694 -0
  10. package/src/accessibility/alt-text.ts +39 -1
  11. package/src/accessibility/aria.ts +4 -2
  12. package/src/colors/__tests__/colorblind.test.ts +12 -0
  13. package/src/colors/colorblind.ts +11 -2
  14. package/src/colors/contrast.ts +11 -0
  15. package/src/colors/index.ts +9 -1
  16. package/src/colors/palettes.ts +47 -0
  17. package/src/helpers/__tests__/deep-merge.test.ts +54 -0
  18. package/src/helpers/__tests__/spec-builders.test.ts +3 -3
  19. package/src/helpers/deep-merge.ts +29 -0
  20. package/src/helpers/spec-builders.ts +7 -7
  21. package/src/index.ts +29 -1
  22. package/src/labels/collision.ts +1 -0
  23. package/src/layout/chrome.ts +24 -0
  24. package/src/layout/index.ts +1 -1
  25. package/src/locale/__tests__/format.test.ts +347 -11
  26. package/src/locale/format.ts +243 -18
  27. package/src/locale/index.ts +10 -1
  28. package/src/responsive/index.ts +2 -0
  29. package/src/responsive/metrics.ts +74 -9
  30. package/src/schema/__tests__/generated-artifacts.test.ts +42 -0
  31. package/src/schema/__tests__/schema.test.ts +179 -0
  32. package/src/styles/__tests__/reduced-motion-selectors.test.ts +28 -0
  33. package/src/styles/animation.css +197 -168
  34. package/src/styles/base.css +89 -62
  35. package/src/styles/chrome.css +173 -179
  36. package/src/styles/dark.css +38 -35
  37. package/src/styles/graph.css +116 -111
  38. package/src/styles/index.css +5 -0
  39. package/src/styles/keyframes.css +115 -100
  40. package/src/styles/legend.css +14 -12
  41. package/src/styles/reduced-motion.css +61 -40
  42. package/src/styles/series-search.css +153 -0
  43. package/src/styles/sparkline.css +14 -12
  44. package/src/styles/table-animation.css +82 -80
  45. package/src/styles/table.css +314 -302
  46. package/src/styles/token-definitions.ts +199 -0
  47. package/src/styles/tokens.css +132 -128
  48. package/src/styles/tooltip.css +109 -115
  49. package/src/styles/you-draw-it.css +126 -0
  50. package/src/types/__tests__/encoding.test.ts +86 -17
  51. package/src/types/__tests__/spec.test.ts +16 -12
  52. package/src/types/encoding.ts +69 -25
  53. package/src/types/events.ts +12 -0
  54. package/src/types/index.ts +39 -3
  55. package/src/types/layout.ts +414 -8
  56. package/src/types/schema-roots.ts +32 -0
  57. package/src/types/spec.ts +643 -84
  58. package/src/types/table.ts +3 -2
@@ -0,0 +1,861 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$ref": "#/definitions/TableSpecSchema",
4
+ "definitions": {
5
+ "TableSpecSchema": {
6
+ "$ref": "#/definitions/TableSpec",
7
+ "description": "Root for the TableSpec subset schema (`table.schema.json`). The data-table visualization spec."
8
+ },
9
+ "TableSpec": {
10
+ "type": "object",
11
+ "properties": {
12
+ "type": {
13
+ "type": "string",
14
+ "const": "table",
15
+ "description": "Discriminant: always \"table\"."
16
+ },
17
+ "data": {
18
+ "type": "array",
19
+ "items": {
20
+ "$ref": "#/definitions/DataRow"
21
+ },
22
+ "description": "Data array: each element is a row."
23
+ },
24
+ "columns": {
25
+ "type": "array",
26
+ "items": {
27
+ "$ref": "#/definitions/ColumnConfig"
28
+ },
29
+ "description": "Column definitions controlling display, sorting, formatting, and mini-charts."
30
+ },
31
+ "rowKey": {
32
+ "type": "string",
33
+ "description": "Optional field to use as a unique row identifier."
34
+ },
35
+ "chrome": {
36
+ "$ref": "#/definitions/Chrome",
37
+ "description": "Editorial chrome."
38
+ },
39
+ "theme": {
40
+ "$ref": "#/definitions/ThemeConfig",
41
+ "description": "Theme configuration overrides."
42
+ },
43
+ "darkMode": {
44
+ "$ref": "#/definitions/DarkMode",
45
+ "description": "Dark mode behavior."
46
+ },
47
+ "watermark": {
48
+ "type": "boolean",
49
+ "description": "Whether to show the tryOpenData.ai watermark. Defaults to true."
50
+ },
51
+ "search": {
52
+ "type": "boolean",
53
+ "description": "Enable client-side search/filter."
54
+ },
55
+ "pagination": {
56
+ "anyOf": [
57
+ {
58
+ "type": "boolean"
59
+ },
60
+ {
61
+ "type": "object",
62
+ "properties": {
63
+ "pageSize": {
64
+ "type": "number"
65
+ }
66
+ },
67
+ "required": [
68
+ "pageSize"
69
+ ],
70
+ "additionalProperties": false
71
+ }
72
+ ],
73
+ "description": "Pagination configuration. True for defaults, or an object with pageSize."
74
+ },
75
+ "stickyFirstColumn": {
76
+ "type": "boolean",
77
+ "description": "Whether to stick the first column during horizontal scroll."
78
+ },
79
+ "compact": {
80
+ "type": "boolean",
81
+ "description": "Compact mode: reduced padding and font sizes."
82
+ },
83
+ "responsive": {
84
+ "type": "boolean",
85
+ "description": "Whether the table adapts to container width. Defaults to true."
86
+ },
87
+ "animation": {
88
+ "$ref": "#/definitions/AnimationSpec",
89
+ "description": "Animation configuration for entrance animations."
90
+ }
91
+ },
92
+ "required": [
93
+ "type",
94
+ "data",
95
+ "columns"
96
+ ],
97
+ "additionalProperties": false,
98
+ "description": "Table specification: input for data table visualizations.\n\nTables are a visualization type, not just an HTML grid. They support heatmap coloring, inline sparklines, sorted columns, search, and pagination."
99
+ },
100
+ "DataRow": {
101
+ "type": "object",
102
+ "additionalProperties": {},
103
+ "description": "Data row: a plain object with string keys."
104
+ },
105
+ "ColumnConfig": {
106
+ "type": "object",
107
+ "properties": {
108
+ "key": {
109
+ "type": "string",
110
+ "description": "Data field key (must match a key in the data rows)."
111
+ },
112
+ "label": {
113
+ "type": "string",
114
+ "description": "Display label for the column header. Defaults to the key if omitted."
115
+ },
116
+ "sortable": {
117
+ "type": "boolean",
118
+ "description": "Whether this column is sortable. Defaults to true."
119
+ },
120
+ "align": {
121
+ "type": "string",
122
+ "enum": [
123
+ "left",
124
+ "center",
125
+ "right"
126
+ ],
127
+ "description": "Text alignment in the column. Defaults to \"left\" for text, \"right\" for numbers."
128
+ },
129
+ "width": {
130
+ "type": "string",
131
+ "description": "Explicit column width (CSS value like \"200px\" or \"20%\"). Auto-sized if omitted."
132
+ },
133
+ "format": {
134
+ "type": "string",
135
+ "description": "Number/date format string (d3-format or d3-time-format), or semantic keyword `'percent'` / `'currency'`. e.g. \",.0f\", \"%Y-%m-%d\", \"percent\". Tables always show full precision (no compact notation)."
136
+ },
137
+ "heatmap": {
138
+ "$ref": "#/definitions/HeatmapColumnConfig",
139
+ "description": "Heatmap: color the cell background based on numeric value."
140
+ },
141
+ "bar": {
142
+ "$ref": "#/definitions/BarColumnConfig",
143
+ "description": "Inline bar: render a proportional bar in the cell."
144
+ },
145
+ "sparkline": {
146
+ "$ref": "#/definitions/SparklineColumnConfig",
147
+ "description": "Sparkline: render a mini line/bar chart in the cell."
148
+ },
149
+ "image": {
150
+ "$ref": "#/definitions/ImageColumnConfig",
151
+ "description": "Image: render the cell value as an image URL."
152
+ },
153
+ "flag": {
154
+ "type": "boolean",
155
+ "description": "Flag: render the cell value as a country flag emoji or image."
156
+ },
157
+ "categoryColors": {
158
+ "$ref": "#/definitions/CategoryColorsConfig",
159
+ "description": "Category colors: color-code the cell based on its categorical value."
160
+ },
161
+ "autoAssign": {
162
+ "type": "boolean",
163
+ "description": "When true, auto-assign palette colors to values not in categoryColors. Default: false."
164
+ }
165
+ },
166
+ "required": [
167
+ "key"
168
+ ],
169
+ "additionalProperties": false,
170
+ "description": "Configuration for a single table column.\n\nAt minimum, `key` identifies the data field. All other properties control display, sorting, formatting, and visual features.\n\nOnly one visual feature (heatmap, bar, sparkline, image, flag, categoryColors) should be active per column. If multiple are provided, the engine picks the first one in this precedence order: sparkline > bar > heatmap > image > flag > categoryColors."
171
+ },
172
+ "HeatmapColumnConfig": {
173
+ "type": "object",
174
+ "properties": {
175
+ "palette": {
176
+ "anyOf": [
177
+ {
178
+ "type": "string"
179
+ },
180
+ {
181
+ "type": "array",
182
+ "items": {
183
+ "type": "string"
184
+ }
185
+ }
186
+ ],
187
+ "description": "Color palette name or array of color stops. Uses sequential palette from theme if a string name is provided."
188
+ },
189
+ "domain": {
190
+ "type": "array",
191
+ "items": {
192
+ "type": "number"
193
+ },
194
+ "minItems": 2,
195
+ "maxItems": 2,
196
+ "description": "Explicit domain [min, max] for the color scale. Auto-derived from data if omitted."
197
+ },
198
+ "colorByField": {
199
+ "type": "string",
200
+ "description": "Use a different field's values for coloring while displaying this column's values. Useful for coloring a label column based on a numeric column."
201
+ }
202
+ },
203
+ "additionalProperties": false,
204
+ "description": "Heatmap coloring configuration for a table column."
205
+ },
206
+ "BarColumnConfig": {
207
+ "type": "object",
208
+ "properties": {
209
+ "maxValue": {
210
+ "type": "number",
211
+ "description": "Maximum value for the bar scale. Auto-derived from data if omitted."
212
+ },
213
+ "color": {
214
+ "type": "string",
215
+ "description": "Bar fill color. Uses the first categorical palette color if omitted."
216
+ }
217
+ },
218
+ "additionalProperties": false,
219
+ "description": "Inline bar configuration for a table column."
220
+ },
221
+ "SparklineColumnConfig": {
222
+ "type": "object",
223
+ "properties": {
224
+ "type": {
225
+ "type": "string",
226
+ "enum": [
227
+ "line",
228
+ "bar",
229
+ "column"
230
+ ],
231
+ "description": "Sparkline chart type. Defaults to \"line\"."
232
+ },
233
+ "valuesField": {
234
+ "type": "string",
235
+ "description": "Field containing the array of values to plot."
236
+ },
237
+ "color": {
238
+ "type": "string",
239
+ "description": "Sparkline color. Uses the first categorical palette color if omitted."
240
+ }
241
+ },
242
+ "additionalProperties": false,
243
+ "description": "Inline sparkline configuration for a table column."
244
+ },
245
+ "ImageColumnConfig": {
246
+ "type": "object",
247
+ "properties": {
248
+ "width": {
249
+ "type": "number",
250
+ "description": "Image width in pixels. Defaults to 24."
251
+ },
252
+ "height": {
253
+ "type": "number",
254
+ "description": "Image height in pixels. Defaults to 24."
255
+ },
256
+ "rounded": {
257
+ "type": "boolean",
258
+ "description": "Whether to apply border-radius for rounded/circular images."
259
+ }
260
+ },
261
+ "additionalProperties": false,
262
+ "description": "Image cell configuration for a table column."
263
+ },
264
+ "CategoryColorsConfig": {
265
+ "type": "object",
266
+ "additionalProperties": {
267
+ "type": "string"
268
+ },
269
+ "description": "Category color mapping for a table column. Maps category values to CSS colors."
270
+ },
271
+ "Chrome": {
272
+ "type": "object",
273
+ "properties": {
274
+ "eyebrow": {
275
+ "anyOf": [
276
+ {
277
+ "type": "string"
278
+ },
279
+ {
280
+ "$ref": "#/definitions/ChromeText"
281
+ }
282
+ ],
283
+ "description": "Editorial kicker/category label rendered above the title. Typically uppercase, tracked, and tinted with the accent color (e.g. \"Equities · Single Ticker\"). Term follows IBM Carbon and Atlassian Design System conventions; not part of Vega-Lite's title model."
284
+ },
285
+ "title": {
286
+ "anyOf": [
287
+ {
288
+ "type": "string"
289
+ },
290
+ {
291
+ "$ref": "#/definitions/ChromeText"
292
+ }
293
+ ],
294
+ "description": "Main title displayed above the visualization."
295
+ },
296
+ "subtitle": {
297
+ "anyOf": [
298
+ {
299
+ "type": "string"
300
+ },
301
+ {
302
+ "$ref": "#/definitions/ChromeText"
303
+ }
304
+ ],
305
+ "description": "Subtitle displayed below the title, typically providing context."
306
+ },
307
+ "source": {
308
+ "anyOf": [
309
+ {
310
+ "type": "string"
311
+ },
312
+ {
313
+ "$ref": "#/definitions/ChromeText"
314
+ }
315
+ ],
316
+ "description": "Data source attribution, displayed below the chart area."
317
+ },
318
+ "byline": {
319
+ "anyOf": [
320
+ {
321
+ "type": "string"
322
+ },
323
+ {
324
+ "$ref": "#/definitions/ChromeText"
325
+ }
326
+ ],
327
+ "description": "Author or organization byline."
328
+ },
329
+ "footer": {
330
+ "anyOf": [
331
+ {
332
+ "type": "string"
333
+ },
334
+ {
335
+ "$ref": "#/definitions/ChromeText"
336
+ }
337
+ ],
338
+ "description": "Footer text, displayed at the very bottom."
339
+ },
340
+ "brand": {
341
+ "anyOf": [
342
+ {
343
+ "type": "string"
344
+ },
345
+ {
346
+ "$ref": "#/definitions/ChromeText"
347
+ }
348
+ ],
349
+ "description": "Right-anchored brand block on the footer row, paired with a small accent dot to its left. Visually balances the source/byline left-anchored text. When set, suppresses the default `tryOpenData.ai` watermark for this chart."
350
+ }
351
+ },
352
+ "additionalProperties": false,
353
+ "description": "Editorial chrome elements: eyebrow, title, subtitle, source attribution, byline, footer. These are first-class structural elements, not string-only afterthoughts. Each element can be a simple string or a ChromeText object with style overrides."
354
+ },
355
+ "ChromeText": {
356
+ "type": "object",
357
+ "properties": {
358
+ "text": {
359
+ "type": "string",
360
+ "description": "The text content to display."
361
+ },
362
+ "style": {
363
+ "$ref": "#/definitions/ChromeTextStyle",
364
+ "description": "Optional style overrides. Theme defaults are used for any omitted property."
365
+ },
366
+ "offset": {
367
+ "$ref": "#/definitions/AnnotationOffset",
368
+ "description": "Pixel offset for fine-tuning position."
369
+ }
370
+ },
371
+ "required": [
372
+ "text"
373
+ ],
374
+ "additionalProperties": false,
375
+ "description": "A chrome text element with optional style overrides."
376
+ },
377
+ "ChromeTextStyle": {
378
+ "type": "object",
379
+ "properties": {
380
+ "fontSize": {
381
+ "type": "number",
382
+ "description": "Font size in pixels."
383
+ },
384
+ "fontWeight": {
385
+ "type": "number",
386
+ "description": "Font weight (400 = normal, 600 = semibold, 700 = bold)."
387
+ },
388
+ "fontFamily": {
389
+ "type": "string",
390
+ "description": "Font family override."
391
+ },
392
+ "color": {
393
+ "type": "string",
394
+ "description": "Text color (CSS color string)."
395
+ }
396
+ },
397
+ "additionalProperties": false,
398
+ "description": "Style overrides for a chrome text element."
399
+ },
400
+ "AnnotationOffset": {
401
+ "type": "object",
402
+ "properties": {
403
+ "dx": {
404
+ "type": "number",
405
+ "description": "Horizontal pixel offset."
406
+ },
407
+ "dy": {
408
+ "type": "number",
409
+ "description": "Vertical pixel offset."
410
+ }
411
+ },
412
+ "additionalProperties": false,
413
+ "description": "Pixel offset for fine-grained annotation positioning."
414
+ },
415
+ "ThemeConfig": {
416
+ "type": "object",
417
+ "properties": {
418
+ "colors": {
419
+ "anyOf": [
420
+ {
421
+ "type": "array",
422
+ "items": {
423
+ "type": "string"
424
+ }
425
+ },
426
+ {
427
+ "type": "object",
428
+ "properties": {
429
+ "categorical": {
430
+ "type": "array",
431
+ "items": {
432
+ "type": "string"
433
+ },
434
+ "description": "Categorical palette for nominal data (array of CSS color strings)."
435
+ },
436
+ "sequential": {
437
+ "type": "object",
438
+ "additionalProperties": {
439
+ "type": "array",
440
+ "items": {
441
+ "type": "string"
442
+ }
443
+ },
444
+ "description": "Sequential palettes keyed by name. Each is an array of color stops."
445
+ },
446
+ "diverging": {
447
+ "type": "object",
448
+ "additionalProperties": {
449
+ "type": "array",
450
+ "items": {
451
+ "type": "string"
452
+ }
453
+ },
454
+ "description": "Diverging palettes keyed by name. Each is an array of color stops with a neutral midpoint."
455
+ },
456
+ "background": {
457
+ "$ref": "#/definitions/TokenValue",
458
+ "description": "Background color. Accepts a TokenValue for light/dark pairs."
459
+ },
460
+ "text": {
461
+ "$ref": "#/definitions/TokenValue",
462
+ "description": "Default text color. Accepts a TokenValue for light/dark pairs."
463
+ },
464
+ "gridline": {
465
+ "$ref": "#/definitions/TokenValue",
466
+ "description": "Gridline color. Accepts a TokenValue for light/dark pairs."
467
+ },
468
+ "axis": {
469
+ "$ref": "#/definitions/TokenValue",
470
+ "description": "Axis line and tick color. Accepts a TokenValue for light/dark pairs."
471
+ },
472
+ "annotationFill": {
473
+ "$ref": "#/definitions/TokenValue",
474
+ "description": "Annotation fill color. Accepts a TokenValue for light/dark pairs."
475
+ },
476
+ "annotationText": {
477
+ "$ref": "#/definitions/TokenValue",
478
+ "description": "Annotation text color. Accepts a TokenValue for light/dark pairs."
479
+ },
480
+ "positive": {
481
+ "$ref": "#/definitions/TokenValue",
482
+ "description": "Semantic color for positive/up-trend values. Accepts a TokenValue for light/dark pairs."
483
+ },
484
+ "negative": {
485
+ "$ref": "#/definitions/TokenValue",
486
+ "description": "Semantic color for negative/down-trend values. Accepts a TokenValue for light/dark pairs."
487
+ }
488
+ },
489
+ "additionalProperties": false
490
+ }
491
+ ],
492
+ "description": "Color palette overrides. Pass a flat string[] as shorthand for categorical colors, or an object for full control over categorical, sequential, diverging, etc."
493
+ },
494
+ "fonts": {
495
+ "type": "object",
496
+ "properties": {
497
+ "family": {
498
+ "type": "string",
499
+ "description": "Primary font family."
500
+ },
501
+ "mono": {
502
+ "type": "string",
503
+ "description": "Monospace font family (for tabular numbers)."
504
+ },
505
+ "sizes": {
506
+ "type": "object",
507
+ "properties": {
508
+ "title": {
509
+ "type": "number",
510
+ "description": "Chart title. Default: 26."
511
+ },
512
+ "subtitle": {
513
+ "type": "number",
514
+ "description": "Subtitle below the title. Default: 14."
515
+ },
516
+ "body": {
517
+ "type": "number",
518
+ "description": "Body text (tooltips, legend labels). Default: 13."
519
+ },
520
+ "small": {
521
+ "type": "number",
522
+ "description": "Small text (source line, footer). Default: 11."
523
+ },
524
+ "axisTick": {
525
+ "type": "number",
526
+ "description": "Axis tick labels. Default: 11."
527
+ },
528
+ "metricLabel": {
529
+ "type": "number",
530
+ "description": "KPI metric uppercase label. Default: 10."
531
+ },
532
+ "metricValue": {
533
+ "type": "number",
534
+ "description": "KPI metric primary value. Default: 22. Delta/secondary derive from this."
535
+ }
536
+ },
537
+ "additionalProperties": false,
538
+ "description": "Font size overrides in pixels. Partial — only specified keys are overridden."
539
+ },
540
+ "weights": {
541
+ "type": "object",
542
+ "properties": {
543
+ "normal": {
544
+ "type": "number",
545
+ "description": "Normal text weight. Default: 450."
546
+ },
547
+ "medium": {
548
+ "type": "number",
549
+ "description": "Medium text weight. Default: 550."
550
+ },
551
+ "semibold": {
552
+ "type": "number",
553
+ "description": "Semibold text weight. Default: 590."
554
+ },
555
+ "bold": {
556
+ "type": "number",
557
+ "description": "Bold text weight. Default: 700."
558
+ }
559
+ },
560
+ "additionalProperties": false,
561
+ "description": "Font weight overrides. Partial — only specified keys are overridden."
562
+ }
563
+ },
564
+ "additionalProperties": false,
565
+ "description": "Font overrides."
566
+ },
567
+ "spacing": {
568
+ "type": "object",
569
+ "properties": {
570
+ "padding": {
571
+ "type": "number",
572
+ "description": "Padding inside the chart container."
573
+ },
574
+ "chromeGap": {
575
+ "type": "number",
576
+ "description": "Gap between chrome elements (title to subtitle, etc.)."
577
+ },
578
+ "chromeToChart": {
579
+ "type": "number",
580
+ "description": "Gap between the last chrome element and the chart area."
581
+ },
582
+ "chartToFooter": {
583
+ "type": "number",
584
+ "description": "Gap between chart area and source/footer below."
585
+ },
586
+ "axisMargin": {
587
+ "type": "number",
588
+ "description": "Internal padding within the chart area (axes margins)."
589
+ },
590
+ "xAxisHeight": {
591
+ "type": "number",
592
+ "description": "Height reserved below chart area for x-axis tick labels. Increase when large axisTick font sizes cause label clipping."
593
+ },
594
+ "xAxisLabelPadding": {
595
+ "type": "number",
596
+ "description": "Gap in pixels between the x-axis line and tick label text. Increase when larger axisTick fonts sit too close to the axis line."
597
+ }
598
+ },
599
+ "additionalProperties": false,
600
+ "description": "Spacing overrides in pixels."
601
+ },
602
+ "borderRadius": {
603
+ "type": "number",
604
+ "description": "Border radius for chart container and tooltips."
605
+ },
606
+ "chrome": {
607
+ "type": "object",
608
+ "properties": {
609
+ "eyebrow": {
610
+ "anyOf": [
611
+ {
612
+ "type": "string"
613
+ },
614
+ {
615
+ "$ref": "#/definitions/ChromeThemeOverride"
616
+ }
617
+ ],
618
+ "description": "Eyebrow (kicker) style override."
619
+ },
620
+ "title": {
621
+ "anyOf": [
622
+ {
623
+ "type": "string"
624
+ },
625
+ {
626
+ "$ref": "#/definitions/ChromeThemeOverride"
627
+ }
628
+ ],
629
+ "description": "Title style override."
630
+ },
631
+ "subtitle": {
632
+ "anyOf": [
633
+ {
634
+ "type": "string"
635
+ },
636
+ {
637
+ "$ref": "#/definitions/ChromeThemeOverride"
638
+ }
639
+ ],
640
+ "description": "Subtitle style override."
641
+ },
642
+ "source": {
643
+ "anyOf": [
644
+ {
645
+ "type": "string"
646
+ },
647
+ {
648
+ "$ref": "#/definitions/ChromeThemeOverride"
649
+ }
650
+ ],
651
+ "description": "Source/attribution style override."
652
+ },
653
+ "byline": {
654
+ "anyOf": [
655
+ {
656
+ "type": "string"
657
+ },
658
+ {
659
+ "$ref": "#/definitions/ChromeThemeOverride"
660
+ }
661
+ ],
662
+ "description": "Byline style override."
663
+ },
664
+ "footer": {
665
+ "anyOf": [
666
+ {
667
+ "type": "string"
668
+ },
669
+ {
670
+ "$ref": "#/definitions/ChromeThemeOverride"
671
+ }
672
+ ],
673
+ "description": "Footer style override."
674
+ }
675
+ },
676
+ "additionalProperties": false,
677
+ "description": "Per-element chrome style overrides. Pass a plain string for color-only (backward-compatible), or a ChromeThemeOverride object for full typography control (color, fontSize, fontWeight, lineHeight)."
678
+ },
679
+ "seriesStrategy": {
680
+ "$ref": "#/definitions/SeriesStrategy",
681
+ "description": "Series color assignment strategy. Default: 'palette' (full categorical palette always, zero visual drift)."
682
+ }
683
+ },
684
+ "additionalProperties": false,
685
+ "description": "User-facing theme configuration for overriding defaults. All fields are optional. The engine deep-merges these onto the default theme."
686
+ },
687
+ "TokenValue": {
688
+ "anyOf": [
689
+ {
690
+ "type": "string"
691
+ },
692
+ {
693
+ "type": "object",
694
+ "properties": {
695
+ "light": {
696
+ "type": "string"
697
+ },
698
+ "dark": {
699
+ "type": "string"
700
+ }
701
+ },
702
+ "required": [
703
+ "light",
704
+ "dark"
705
+ ],
706
+ "additionalProperties": false
707
+ }
708
+ ],
709
+ "description": "A color token value: either a plain CSS color string, or a light/dark pair. When a pair is given, the resolver picks the appropriate value based on dark mode state. When a plain string is given, dark-mode adaptation derives the dark value algorithmically (existing behavior)."
710
+ },
711
+ "ChromeThemeOverride": {
712
+ "type": "object",
713
+ "properties": {
714
+ "color": {
715
+ "$ref": "#/definitions/TokenValue",
716
+ "description": "Text color. Accepts a TokenValue for light/dark pairs."
717
+ },
718
+ "fontSize": {
719
+ "type": "number",
720
+ "description": "Font size in pixels."
721
+ },
722
+ "fontWeight": {
723
+ "type": "number",
724
+ "description": "Font weight."
725
+ },
726
+ "lineHeight": {
727
+ "type": "number",
728
+ "description": "Line height multiplier."
729
+ }
730
+ },
731
+ "additionalProperties": false,
732
+ "description": "Per-element chrome theme override. Pass a plain string (color only, backward-compatible) or an object with full typography control."
733
+ },
734
+ "SeriesStrategy": {
735
+ "type": "string",
736
+ "enum": [
737
+ "palette",
738
+ "accent-neutral"
739
+ ],
740
+ "description": "Series color assignment strategy.\n\n- `'palette'` — always assign from the full categorical palette (current default, zero visual drift).\n- `'accent-neutral'` — assign by series count: a single series gets the accent (first categorical color) only; 2-4 series get accent for the first and neutral grays for the rest; 5+ fall back to the full palette."
741
+ },
742
+ "DarkMode": {
743
+ "type": "string",
744
+ "enum": [
745
+ "auto",
746
+ "force",
747
+ "off"
748
+ ],
749
+ "description": "Dark mode behavior.\n\n- `\"auto\"` - Checks the `prefers-color-scheme` media query to detect the user's system-level preference. This does NOT detect class-based dark mode toggles (e.g. `document.documentElement.classList.toggle('dark')`). If your app uses class-based dark mode, use VizThemeProvider with `\"force\"` or `\"off\"` instead of `\"auto\"` and toggle based on your app's state.\n- `\"force\"` - Always render in dark mode regardless of system preference.\n- `\"off\"` - Always render in light mode (default).\n\nAll components (Chart, Sankey, Graph) inherit darkMode from VizThemeProvider when no explicit prop is passed."
750
+ },
751
+ "AnimationSpec": {
752
+ "anyOf": [
753
+ {
754
+ "type": "boolean"
755
+ },
756
+ {
757
+ "$ref": "#/definitions/AnimationConfig"
758
+ }
759
+ ],
760
+ "description": "Animation spec property.\n- true: enable entrance animation with sensible defaults\n- false/omitted: no animation (current behavior)\n- AnimationConfig: full control via enter/update/exit phases"
761
+ },
762
+ "AnimationConfig": {
763
+ "type": "object",
764
+ "properties": {
765
+ "enter": {
766
+ "anyOf": [
767
+ {
768
+ "$ref": "#/definitions/AnimationPhaseConfig"
769
+ },
770
+ {
771
+ "type": "boolean"
772
+ }
773
+ ],
774
+ "description": "Entrance animation when chart first renders."
775
+ },
776
+ "update": {
777
+ "anyOf": [
778
+ {
779
+ "$ref": "#/definitions/AnimationPhaseConfig"
780
+ },
781
+ {
782
+ "type": "boolean"
783
+ }
784
+ ],
785
+ "description": "Transition animation when data updates."
786
+ },
787
+ "exit": {
788
+ "anyOf": [
789
+ {
790
+ "$ref": "#/definitions/AnimationPhaseConfig"
791
+ },
792
+ {
793
+ "type": "boolean"
794
+ }
795
+ ],
796
+ "description": "Exit animation when marks are removed."
797
+ },
798
+ "annotationDelay": {
799
+ "type": "number",
800
+ "description": "Delay before annotations animate in (ms after marks). Default: 200."
801
+ }
802
+ },
803
+ "additionalProperties": false,
804
+ "description": "Full animation config object. Structured as enter/update/exit phases following Vega's encoding set model."
805
+ },
806
+ "AnimationPhaseConfig": {
807
+ "type": "object",
808
+ "properties": {
809
+ "duration": {
810
+ "type": "number",
811
+ "description": "Duration in ms. Default: 500 for enter."
812
+ },
813
+ "ease": {
814
+ "$ref": "#/definitions/AnimationEase",
815
+ "description": "Easing preset. Default: 'smooth'."
816
+ },
817
+ "stagger": {
818
+ "anyOf": [
819
+ {
820
+ "$ref": "#/definitions/AnimationStagger"
821
+ },
822
+ {
823
+ "type": "boolean"
824
+ }
825
+ ],
826
+ "description": "Stagger config. true = defaults, false = no stagger. Default: true for enter."
827
+ }
828
+ },
829
+ "additionalProperties": false,
830
+ "description": "Animation phase config. Follows Vega's enter/update/exit model. Each phase can be true (use defaults) or a config object."
831
+ },
832
+ "AnimationEase": {
833
+ "type": "string",
834
+ "enum": [
835
+ "smooth",
836
+ "snappy"
837
+ ],
838
+ "description": "Named easing presets for entrance animations. Uses CSS linear() curves. Named 'ease' (not 'easing') to match Vega convention."
839
+ },
840
+ "AnimationStagger": {
841
+ "type": "object",
842
+ "properties": {
843
+ "delay": {
844
+ "type": "number",
845
+ "description": "Delay between each element in ms. Default: 80"
846
+ },
847
+ "order": {
848
+ "type": "string",
849
+ "enum": [
850
+ "index",
851
+ "value",
852
+ "reverse"
853
+ ],
854
+ "description": "Ordering strategy. Default: 'index' (DOM order)"
855
+ }
856
+ },
857
+ "additionalProperties": false,
858
+ "description": "Stagger configuration for sequential element reveal."
859
+ }
860
+ }
861
+ }