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

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 +533 -42
  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 +77 -10
  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
package/src/types/spec.ts CHANGED
@@ -32,6 +32,10 @@ import type { SeriesStrategy, TokenValue } from './theme';
32
32
  * - 'rule': reference lines as data marks
33
33
  * - 'tick': strip/rug plot marks
34
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
35
39
  */
36
40
  export type MarkType =
37
41
  | 'bar'
@@ -44,10 +48,12 @@ export type MarkType =
44
48
  | 'rule'
45
49
  | 'tick'
46
50
  | 'rect'
47
- | 'lollipop';
48
-
49
- /** @deprecated Use MarkType instead. Kept for internal migration references. */
50
- export type ChartType = MarkType;
51
+ | 'lollipop'
52
+ | 'beeswarm'
53
+ | 'range'
54
+ | 'waffle'
55
+ | 'calendar'
56
+ | 'parliament';
51
57
 
52
58
  // ---------------------------------------------------------------------------
53
59
  // Gradient definitions (Vega-aligned)
@@ -174,6 +180,19 @@ export interface MarkDef {
174
180
  innerRadius?: number;
175
181
  /** Outer radius for arc marks. */
176
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;
177
196
  /** Corner radius for rect/bar marks. 'pill' sets rx to half the bar thickness. */
178
197
  cornerRadius?: number | 'pill';
179
198
  /** Fixed bar thickness in pixels for bar/column marks. When set, bars are this height (horizontal) or width (vertical), centered within the band. */
@@ -200,6 +219,99 @@ export interface MarkDef {
200
219
  * two competing diagonals.
201
220
  */
202
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 };
203
315
  }
204
316
 
205
317
  // ---------------------------------------------------------------------------
@@ -227,7 +339,11 @@ export type AggregateOp =
227
339
  export interface AxisConfig {
228
340
  /** Axis title text. If omitted, the field name is used. */
229
341
  title?: string;
230
- /** Number format string (d3-format). e.g. ",.0f" for comma-separated integers. */
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
+ */
231
347
  format?: string;
232
348
  /** Override tick count. Engine picks a sensible default if omitted. */
233
349
  tickCount?: number;
@@ -271,8 +387,12 @@ export interface AxisConfig {
271
387
 
272
388
  /** Scale configuration for an encoding channel. */
273
389
  export interface ScaleConfig {
274
- /** Explicit domain override. Auto-derived from data if omitted. */
275
- domain?: [number, number] | string[];
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[];
276
396
  /** Scale type override. Usually inferred from field type. */
277
397
  type?: ScaleType;
278
398
  /** Whether to nice-ify the domain for clean tick values. Defaults to true. */
@@ -283,6 +403,15 @@ export interface ScaleConfig {
283
403
  clip?: boolean;
284
404
  /** Explicit range override. */
285
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;
286
415
  /** Reverse the range direction. */
287
416
  reverse?: boolean;
288
417
  /** Clamp output to the range. */
@@ -317,6 +446,30 @@ export type ScaleType =
317
446
  | 'quantize'
318
447
  | 'threshold';
319
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
+
320
473
  /**
321
474
  * A single encoding channel mapping a data field to a visual property.
322
475
  *
@@ -348,8 +501,11 @@ export interface EncodingChannel<TData extends DataRow = DataRow> {
348
501
  type: FieldType;
349
502
  /** Optional aggregate to apply before encoding. */
350
503
  aggregate?: AggregateOp;
351
- /** Axis configuration. Set to `false` to suppress axis entirely (no space reserved). */
352
- axis?: AxisConfig | false;
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;
353
509
  /** Scale configuration. */
354
510
  scale?: ScaleConfig;
355
511
  /**
@@ -361,26 +517,28 @@ export interface EncodingChannel<TData extends DataRow = DataRow> {
361
517
  * The string forms are still the canonical input — the boolean shorthand is
362
518
  * normalized to the matching string before reaching layout.
363
519
  *
364
- * - undefined: chart-type default (see below)
520
+ * - undefined: chart-type default (see below) -- stacked for bar/column/area
365
521
  * - true | 'zero': stack from zero baseline
366
522
  * - 'normalize': stack and normalize to fraction of total (0-1 per category)
367
523
  * - 'center': center stacks around zero (streamgraph style)
368
- * - null | false: no stacking -- renders overlap (area) or grouped/dodged (bar)
524
+ * - null | false: no stacking -- renders grouped/dodged (bar) or overlap (area)
369
525
  *
370
526
  * **Defaults differ by chart type:**
371
- * - **Bar/Column**: defaults to grouped (side-by-side). Use `stack: 'zero'` (or `true`) for stacked bars.
372
- * - **Area**: defaults to overlap (v6 breaking change). Use `stack: 'zero'` (or `true`)
373
- * to opt into stacked areas. Each overlapping series renders as a translucent
374
- * gradient band anchored at the y-domain baseline.
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.
375
533
  * - **Line**: stacking is not applied (lines always overlap).
376
534
  *
377
535
  * @example
378
- * // Stacked horizontal bars (opt-in; default is grouped):
379
- * "x": { "field": "revenue", "type": "quantitative", "stack": "zero" }
536
+ * // Grouped horizontal bars (opt-out; default is stacked):
537
+ * "x": { "field": "revenue", "type": "quantitative", "stack": null }
380
538
  *
381
539
  * @example
382
- * // Stacked area (opt-in; default is overlap):
383
- * "y": { "field": "value", "type": "quantitative", "stack": "zero" }
540
+ * // Overlap area (opt-out; default is stacked):
541
+ * "y": { "field": "value", "type": "quantitative", "stack": null }
384
542
  */
385
543
  stack?: boolean | 'zero' | 'normalize' | 'center' | null;
386
544
  /**
@@ -403,8 +561,22 @@ export interface EncodingChannel<TData extends DataRow = DataRow> {
403
561
  * - 'descending': sort domain values descending
404
562
  * - null: use data order (no sorting)
405
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.
406
571
  */
407
- sort?: 'ascending' | 'descending' | null;
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;
408
580
  /**
409
581
  * Display title override (Vega-Lite aligned).
410
582
  * Used as the label in tooltips instead of the raw field name.
@@ -413,7 +585,8 @@ export interface EncodingChannel<TData extends DataRow = DataRow> {
413
585
  title?: string;
414
586
  /**
415
587
  * Format string for values (Vega-Lite aligned).
416
- * For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f").
588
+ * For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f"),
589
+ * or semantic keyword `'percent'` / `'currency'`.
417
590
  * For temporal fields: d3-time-format string (e.g. "%Y", "%b %d").
418
591
  * Used in tooltips; `axis.format` takes precedence for axis tick labels.
419
592
  */
@@ -475,14 +648,16 @@ export interface Encoding<TData extends DataRow = DataRow> {
475
648
  /**
476
649
  * Color channel. Required for arc marks (determines pie/donut slice coloring).
477
650
  * Optional for all other marks -- used for series differentiation on multi-series charts,
478
- * or heatmap intensity. Accepts a conditional definition to apply colors based on data predicates.
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).
479
653
  */
480
- color?: EncodingChannel<TData> | ConditionalValueDef<TData>;
654
+ color?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
481
655
  /**
482
656
  * Size channel. Used by point/bubble charts to scale dot area by a quantitative field.
483
- * Accepts a conditional definition to vary size based on data predicates.
657
+ * Accepts a conditional definition to vary size based on data predicates,
658
+ * or a bare `{ value }` constant (VL aligned; expanded to `mark.size`).
484
659
  */
485
- size?: EncodingChannel<TData> | ConditionalValueDef<TData>;
660
+ size?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
486
661
  /**
487
662
  * Detail channel. Groups data into multiple series without mapping to a visual property.
488
663
  * Useful when you want separate lines per category but don't need the color to differ.
@@ -506,17 +681,14 @@ export interface Encoding<TData extends DataRow = DataRow> {
506
681
  y2?: EncodingChannel<TData>;
507
682
  /**
508
683
  * Data-driven opacity (0-1 range). Accepts a conditional definition to vary opacity
509
- * based on data predicates (e.g., highlight selected points).
510
- */
511
- opacity?: EncodingChannel<TData> | ConditionalValueDef<TData>;
512
- /**
513
- * Point shape encoding. Valid values: 'circle', 'square', 'diamond', 'triangle-up',
514
- * 'triangle-down', 'cross'. Used on point/scatter marks to differentiate series by shape.
684
+ * based on data predicates (e.g., highlight selected points), or a bare `{ value }`
685
+ * constant (VL aligned; expanded to `mark.opacity`).
515
686
  */
516
- shape?: EncodingChannel<TData>;
687
+ opacity?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
517
688
  /**
518
- * Stroke dash pattern encoding. Maps a nominal field to different dash patterns
519
- * on line marks. Useful when color alone doesn't distinguish series well.
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.
520
692
  */
521
693
  strokeDash?: EncodingChannel<TData>;
522
694
  /** Rotation angle encoding for point marks. Maps a quantitative field to 0-360 degrees. */
@@ -531,25 +703,15 @@ export interface Encoding<TData extends DataRow = DataRow> {
531
703
  * multi-field tooltips. Independent of the x/y/color encoding.
532
704
  */
533
705
  tooltip?: EncodingChannel<TData> | EncodingChannel<TData>[];
534
- /** Hyperlink encoding. Maps a field containing URLs to clickable marks. */
535
- href?: EncodingChannel<TData>;
536
706
  /**
537
- * Drawing order. Controls z-order and stacking sort order for bar/area marks.
538
- * Lower values are drawn first (behind higher values).
539
- */
540
- order?: EncodingChannel<TData>;
541
- /**
542
- * Angular position for arc marks (pie/donut).
543
- * Optional -- defaults to the `y` channel value when omitted.
544
- * Not used by any other mark type.
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.
545
713
  */
546
714
  theta?: EncodingChannel<TData>;
547
- /**
548
- * Radial distance from center for arc marks.
549
- * Optional -- only meaningful on donut charts (controls inner radius boundary).
550
- * Not used by any other mark type.
551
- */
552
- radius?: EncodingChannel<TData>;
553
715
  /**
554
716
  * Facet channel: partitions data into a grid of small-multiple panels.
555
717
  * Each unique value of the facet field produces one panel. Panels share
@@ -682,15 +844,26 @@ export interface AnnotationOffset {
682
844
  /** Anchor direction for annotation label placement relative to the data point. */
683
845
  export type AnnotationAnchor = 'top' | 'bottom' | 'left' | 'right' | 'auto';
684
846
 
685
- /** Style overrides for the dot marker drawn at the connector's data-point endpoint. */
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. */
686
859
  export interface AnnotationDot {
687
- /** Circle radius in pixels. Default 5. */
860
+ /** Circle radius in pixels. Default 4. */
688
861
  radius?: number;
689
862
  /** Fill color. Defaults to theme background for an "open ring" look. */
690
863
  fill?: string;
691
- /** Stroke color. Defaults to theme text color. */
864
+ /** Stroke color. Defaults to the connector's resolved stroke, so marker and leader read as one system. */
692
865
  stroke?: string;
693
- /** Stroke width in pixels. Default 2. */
866
+ /** Stroke width in pixels. Default 1.5. */
694
867
  strokeWidth?: number;
695
868
  }
696
869
 
@@ -722,7 +895,13 @@ export interface TextAnnotation extends AnnotationBase {
722
895
  x: string | number;
723
896
  /** Y-axis data value or position. */
724
897
  y: string | number;
725
- /** The annotation text. Required for text annotations. */
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
+ */
726
905
  text: string;
727
906
  /** Thinning priority (lower = kept longer at narrow widths). When omitted, spec order is used. */
728
907
  priority?: number;
@@ -730,12 +909,23 @@ export interface TextAnnotation extends AnnotationBase {
730
909
  * Optional muted second-tone text rendered below the primary `text`.
731
910
  * Used for supporting context (e.g. methodology, source). Newlines in
732
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.
733
916
  */
734
917
  subtitle?: string;
735
918
  /**
736
- * Optional dot marker drawn at the connector's data-point endpoint.
737
- * `true` enables the default open-ring style. Pass an object to override
738
- * radius, fill, stroke, or strokeWidth.
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.
739
929
  */
740
930
  dot?: boolean | AnnotationDot;
741
931
  /** Font size override. */
@@ -748,13 +938,19 @@ export interface TextAnnotation extends AnnotationBase {
748
938
  anchor?: AnnotationAnchor;
749
939
  /**
750
940
  * Connector from label to anchor point.
751
- * - `true` (default): straight line
752
- * - `'straight'`: straight line (alias of `true`)
941
+ * - `true` (default): straight line, no arrow
942
+ * - `'straight'`: straight line, no arrow
753
943
  * - `'curve'`: curved arrow with arrowhead
754
- * - `'drop-line'`: vertical line through the data point's x; label sits beside the line and auto-flips to the opposite side if it would overflow the chart area
944
+ * - `'drop-line'`: vertical line through the data point's x
945
+ * - `{ type, arrow? }`: object form for explicit arrow control
755
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.
756
952
  */
757
- connector?: boolean | 'straight' | 'curve' | 'drop-line';
953
+ connector?: boolean | ConnectorType | ConnectorConfig;
758
954
  /** Per-endpoint offsets for the connector line. Allows fine-tuning where the connector starts and ends. */
759
955
  connectorOffset?: {
760
956
  /** Offset for the label-end of the connector. */
@@ -762,8 +958,14 @@ export interface TextAnnotation extends AnnotationBase {
762
958
  /** Offset for the data-point-end of the connector. */
763
959
  to?: AnnotationOffset;
764
960
  };
765
- /** Background color behind the text. Useful for readability over chart lines. */
766
- background?: string;
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;
767
969
  /** Whether to show the paint-order stroke halo behind text. Default true. Set false for white text on colored backgrounds. */
768
970
  halo?: boolean;
769
971
  }
@@ -807,7 +1009,14 @@ export interface RangeAnnotation extends AnnotationBase {
807
1009
  * Useful for baselines (zero), targets, or thresholds.
808
1010
  */
809
1011
  export interface RefLineAnnotation extends AnnotationBase {
810
- type: 'refline' | 'rule';
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';
811
1020
  /** X-axis value for a vertical reference line. */
812
1021
  x?: string | number;
813
1022
  /** Y-axis value for a horizontal reference line. */
@@ -1007,7 +1216,11 @@ export interface LabelConfig {
1007
1216
  suffix?: string;
1008
1217
  /** Fixed CSS color for all labels. Overrides the default fill-derived color. */
1009
1218
  color?: string;
1010
- /** Per-series pixel offsets for fine-tuning label positions, keyed by series name. */
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
+ */
1011
1224
  offsets?: Record<string, AnnotationOffset>;
1012
1225
  /** Font size in pixels for bar/column value labels. */
1013
1226
  fontSize?: number;
@@ -1038,6 +1251,46 @@ export interface LegendConfig {
1038
1251
  exclude?: string[];
1039
1252
  }
1040
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
+
1041
1294
  /**
1042
1295
  * Configuration for the endpoint labels column rendered at the chart's right edge
1043
1296
  * for multi-series line/area charts. Each entry pairs the series name with its
@@ -1075,6 +1328,20 @@ export interface EndpointLabelsConfig {
1075
1328
  * - `'both'`: labels on both left (leading) and right (trailing) edges.
1076
1329
  */
1077
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 };
1078
1345
  /** Field to read the displayed value from. Defaults to `encoding.y.field`. */
1079
1346
  valueField?: string;
1080
1347
  /** d3-format string for the value. Defaults to `encoding.y.axis.format`. */
@@ -1250,14 +1517,14 @@ export interface Metric {
1250
1517
 
1251
1518
  /**
1252
1519
  * Encoding for arc marks (pie/donut charts).
1253
- * - `y`: required (quantitative — the slice value)
1520
+ * - `theta`: required (quantitative — the canonical slice value channel)
1254
1521
  * - `color`: required (nominal/ordinal — the category)
1255
- * - `theta`: optional (defaults to `y` channel)
1256
- * - `radius`: optional (donut inner radius)
1522
+ * - `y`: deprecated alias for `theta`, accepted for backward compatibility
1257
1523
  */
1258
1524
  export interface ArcEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1259
- y: EncodingChannel<TData>;
1260
- color: EncodingChannel<TData>;
1525
+ /** Canonical value channel for arc marks (quantitative — the slice value). */
1526
+ theta: EncodingChannel<TData>;
1527
+ color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1261
1528
  }
1262
1529
 
1263
1530
  /**
@@ -1320,6 +1587,67 @@ export interface LollipopEncoding<TData extends DataRow = DataRow> extends Encod
1320
1587
  y: EncodingChannel<TData>;
1321
1588
  }
1322
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
+
1323
1651
  /**
1324
1652
  * Encoding for text marks (data-positioned labels).
1325
1653
  * - `text`: required (the field to render as text)
@@ -1349,6 +1677,33 @@ export interface RectEncoding<TData extends DataRow = DataRow> extends Encoding<
1349
1677
  y: EncodingChannel<TData>;
1350
1678
  }
1351
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
+
1352
1707
  // ---------------------------------------------------------------------------
1353
1708
  // Shared (non-mark-specific) ChartSpec properties
1354
1709
  // ---------------------------------------------------------------------------
@@ -1360,12 +1715,44 @@ export interface RectEncoding<TData extends DataRow = DataRow> extends Encoding<
1360
1715
  * @internal
1361
1716
  */
1362
1717
  interface BaseChartSpec<TData extends DataRow = DataRow> {
1363
- /** Data array: each element is a row with field values. */
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
+ */
1364
1724
  data: TData[];
1365
1725
  /** Data transforms applied in order before encoding (filter, bin, calculate, timeUnit). */
1366
1726
  transform?: Transform[];
1367
1727
  /** Editorial chrome (title, subtitle, source, etc.). */
1368
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;
1369
1756
  /**
1370
1757
  * KPI/metric cells rendered as a horizontal row between subtitle and chart
1371
1758
  * area. Each cell shows a label/value pair with optional delta and secondary
@@ -1426,6 +1813,22 @@ interface BaseChartSpec<TData extends DataRow = DataRow> {
1426
1813
  * explicitly set.
1427
1814
  */
1428
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;
1429
1832
  /**
1430
1833
  * Display mode controlling how much chart chrome is rendered.
1431
1834
  *
@@ -1521,6 +1924,18 @@ export type ChartSpec<TData extends DataRow = DataRow> =
1521
1924
  mark: 'lollipop' | (MarkDef & { type: 'lollipop' });
1522
1925
  encoding: LollipopEncoding<TData>;
1523
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
+ })
1524
1939
  | (BaseChartSpec<TData> & {
1525
1940
  mark: 'text' | (MarkDef & { type: 'text' });
1526
1941
  encoding: TextEncoding<TData>;
@@ -1536,6 +1951,14 @@ export type ChartSpec<TData extends DataRow = DataRow> =
1536
1951
  | (BaseChartSpec<TData> & {
1537
1952
  mark: 'rule' | (MarkDef & { type: 'rule' });
1538
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>;
1539
1962
  });
1540
1963
 
1541
1964
  /**
@@ -1676,6 +2099,16 @@ export interface LayerSpec<TData extends DataRow = DataRow> {
1676
2099
  transform?: Transform[];
1677
2100
  /** Editorial chrome (title, subtitle, source, etc.). */
1678
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;
1679
2112
  /** Annotations on the layered view. */
1680
2113
  annotations?: Annotation[];
1681
2114
  /** Label display configuration. `false` disables all labels, `true` uses defaults. */
@@ -1774,6 +2207,8 @@ export interface SankeySpec {
1774
2207
  * number (data value 28 renders as "28%"). For currency: "$,.0f" or "$~s".
1775
2208
  * For SI suffixes: "~s" (renders 1000 as "1k"). When not set, values use
1776
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.
1777
2212
  */
1778
2213
  valueFormat?: string;
1779
2214
  }
@@ -1840,6 +2275,72 @@ export interface TileMapSpec {
1840
2275
  /**
1841
2276
  * d3-format string applied to tile values, legend labels, and tooltips.
1842
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.
1843
2344
  */
1844
2345
  valueFormat?: string;
1845
2346
  }
@@ -1877,6 +2378,8 @@ export interface BarListSpec {
1877
2378
  /**
1878
2379
  * d3-format string applied to bar values and tooltips.
1879
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.
1880
2383
  */
1881
2384
  valueFormat?: string;
1882
2385
  }
@@ -1904,7 +2407,15 @@ export interface BarListEncoding {
1904
2407
  * - GraphSpec: has `type: 'graph'`
1905
2408
  * - SankeySpec: has `type: 'sankey'`
1906
2409
  * - TileMapSpec: has `type: 'tilemap'`
2410
+ * - MapSpec: has `type: 'map'`
1907
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/`).
1908
2419
  */
1909
2420
  export type VizSpec =
1910
2421
  | ChartSpec
@@ -1913,6 +2424,7 @@ export type VizSpec =
1913
2424
  | GraphSpec
1914
2425
  | SankeySpec
1915
2426
  | TileMapSpec
2427
+ | MapSpec
1916
2428
  | BarListSpec;
1917
2429
 
1918
2430
  /**
@@ -1928,6 +2440,8 @@ export type GraphSpecWithoutData = Omit<GraphSpec, 'nodes' | 'edges'>;
1928
2440
  export type SankeySpecWithoutData = Omit<SankeySpec, 'data'>;
1929
2441
  /** TileMap spec without runtime data, for persistence/storage. */
1930
2442
  export type TileMapSpecWithoutData = Omit<TileMapSpec, 'data'>;
2443
+ /** Map spec without runtime data, for persistence/storage. */
2444
+ export type MapSpecWithoutData = Omit<MapSpec, 'data'>;
1931
2445
  /** BarList spec without runtime data, for persistence/storage. */
1932
2446
  export type BarListSpecWithoutData = Omit<BarListSpec, 'data'>;
1933
2447
  /** Union of data-stripped spec types for persistence/storage. */
@@ -1937,6 +2451,7 @@ export type StoredVizSpec =
1937
2451
  | GraphSpecWithoutData
1938
2452
  | SankeySpecWithoutData
1939
2453
  | TileMapSpecWithoutData
2454
+ | MapSpecWithoutData
1940
2455
  | BarListSpecWithoutData;
1941
2456
 
1942
2457
  // ---------------------------------------------------------------------------
@@ -2144,9 +2659,10 @@ export interface Condition<TData extends DataRow = DataRow> {
2144
2659
  test: FilterPredicate;
2145
2660
  /**
2146
2661
  * Static value to use when the condition is true.
2147
- * Accepted values: CSS color string, opacity (0-1), size number, or boolean flag.
2662
+ * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2663
+ * or a gradient definition for color channels.
2148
2664
  */
2149
- value?: string | number | boolean | null;
2665
+ value?: string | number | boolean | null | GradientDef;
2150
2666
  /**
2151
2667
  * Data field to use when the condition is true.
2152
2668
  * Constrained to column names of `TData` when using `ChartSpec<TData>`.
@@ -2167,18 +2683,19 @@ export interface ConditionalValueDef<TData extends DataRow = DataRow> {
2167
2683
  condition: Condition<TData> | Condition<TData>[];
2168
2684
  /**
2169
2685
  * Default value when no condition matches.
2170
- * Accepted values: CSS color string, opacity (0-1), size number, or boolean flag.
2686
+ * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2687
+ * or a gradient definition for color channels.
2171
2688
  */
2172
- value?: string | number | boolean | null;
2689
+ value?: string | number | boolean | null | GradientDef;
2173
2690
  }
2174
2691
 
2175
2692
  /**
2176
2693
  * Check if a channel definition is a regular EncodingChannel (has 'field' at top level).
2177
- * Use this to narrow `EncodingChannel | ConditionalValueDef` in encoding channels
2178
- * that support conditional encoding (color, size, opacity).
2694
+ * Use this to narrow `EncodingChannel | ConditionalValueDef | ValueDef` in encoding
2695
+ * channels that support conditional encoding (color, size, opacity).
2179
2696
  */
2180
2697
  export function isEncodingChannel<TData extends DataRow = DataRow>(
2181
- def: EncodingChannel<TData> | ConditionalValueDef<TData> | undefined,
2698
+ def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined,
2182
2699
  ): def is EncodingChannel<TData> {
2183
2700
  if (!def) return false;
2184
2701
  return 'field' in def && !('condition' in def);
@@ -2188,12 +2705,24 @@ export function isEncodingChannel<TData extends DataRow = DataRow>(
2188
2705
  * Check if a channel definition is a ConditionalValueDef.
2189
2706
  */
2190
2707
  export function isConditionalDef<TData extends DataRow = DataRow>(
2191
- def: EncodingChannel<TData> | ConditionalValueDef<TData> | undefined,
2708
+ def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined,
2192
2709
  ): def is ConditionalValueDef<TData> {
2193
2710
  if (!def) return false;
2194
2711
  return 'condition' in def;
2195
2712
  }
2196
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
+
2197
2726
  // ---------------------------------------------------------------------------
2198
2727
  // Mark type helpers
2199
2728
  // ---------------------------------------------------------------------------
@@ -2211,10 +2740,25 @@ export const MARK_TYPES: ReadonlySet<string> = new Set<MarkType>([
2211
2740
  'tick',
2212
2741
  'rect',
2213
2742
  'lollipop',
2743
+ 'beeswarm',
2744
+ 'range',
2745
+ 'waffle',
2746
+ 'calendar',
2747
+ 'parliament',
2214
2748
  ]);
2215
2749
 
2216
- /** @deprecated Use MARK_TYPES instead. */
2217
- export const CHART_TYPES = MARK_TYPES;
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
+ }
2218
2762
 
2219
2763
  /**
2220
2764
  * Extract the mark type string from a mark field (string or MarkDef).
@@ -2269,6 +2813,11 @@ export function isBarListSpec(spec: VizSpec | Record<string, unknown>): spec is
2269
2813
  return 'type' in spec && (spec as Record<string, unknown>).type === 'barlist';
2270
2814
  }
2271
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
+
2272
2821
  // ---------------------------------------------------------------------------
2273
2822
  // Annotation type guards
2274
2823
  // ---------------------------------------------------------------------------
@@ -2283,9 +2832,14 @@ export function isRangeAnnotation(annotation: Annotation): annotation is RangeAn
2283
2832
  return annotation.type === 'range';
2284
2833
  }
2285
2834
 
2286
- /** Check if an annotation is a RefLineAnnotation. */
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
+ */
2287
2840
  export function isRefLineAnnotation(annotation: Annotation): annotation is RefLineAnnotation {
2288
- return annotation.type === 'refline' || annotation.type === 'rule';
2841
+ const type: string = annotation.type;
2842
+ return type === 'refline' || type === 'rule';
2289
2843
  }
2290
2844
 
2291
2845
  // ---------------------------------------------------------------------------
@@ -2305,4 +2859,9 @@ export const MARK_DISPLAY_NAMES: Record<MarkType, string> = {
2305
2859
  tick: 'Tick plot',
2306
2860
  rect: 'Heatmap',
2307
2861
  lollipop: 'Lollipop chart',
2862
+ beeswarm: 'Beeswarm chart',
2863
+ range: 'Range plot',
2864
+ waffle: 'Waffle chart',
2865
+ calendar: 'Calendar heatmap',
2866
+ parliament: 'Parliament chart',
2308
2867
  };