@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/dist/index.d.ts CHANGED
@@ -139,8 +139,9 @@ interface ColumnConfig {
139
139
  /** Explicit column width (CSS value like "200px" or "20%"). Auto-sized if omitted. */
140
140
  width?: string;
141
141
  /**
142
- * Number/date format string (d3-format or d3-time-format).
143
- * Applied to the raw value before display. e.g. ",.0f" or "%Y-%m-%d".
142
+ * Number/date format string (d3-format or d3-time-format), or semantic
143
+ * keyword `'percent'` / `'currency'`. e.g. ",.0f", "%Y-%m-%d", "percent".
144
+ * Tables always show full precision (no compact notation).
144
145
  */
145
146
  format?: string;
146
147
  /** Heatmap: color the cell background based on numeric value. */
@@ -361,10 +362,12 @@ interface ResolvedTheme extends Theme {
361
362
  * - 'rule': reference lines as data marks
362
363
  * - 'tick': strip/rug plot marks
363
364
  * - 'rect': heatmaps and 2D binned plots
365
+ * - 'range': dumbbell / arrow / range-bar plots spanning two values per category
366
+ * - 'waffle': unit grids ("x of 100") for part-to-whole counts
367
+ * - 'calendar': GitHub-style calendar heatmap (weeks x weekdays, daily dates)
368
+ * - 'parliament': hemicycle seat-dot chart for election/legislature results
364
369
  */
365
- type MarkType = 'bar' | 'line' | 'area' | 'point' | 'circle' | 'arc' | 'text' | 'rule' | 'tick' | 'rect' | 'lollipop';
366
- /** @deprecated Use MarkType instead. Kept for internal migration references. */
367
- type ChartType = MarkType;
370
+ type MarkType = 'bar' | 'line' | 'area' | 'point' | 'circle' | 'arc' | 'text' | 'rule' | 'tick' | 'rect' | 'lollipop' | 'beeswarm' | 'range' | 'waffle' | 'calendar' | 'parliament';
368
371
  /** A single color stop in a gradient definition. */
369
372
  interface GradientStop {
370
373
  /** Position along the gradient, 0 to 1. */
@@ -456,6 +459,19 @@ interface MarkDef {
456
459
  innerRadius?: number;
457
460
  /** Outer radius for arc marks. */
458
461
  outerRadius?: number;
462
+ /**
463
+ * Start angle in radians for arc marks. Defaults to 0 (full pie/donut).
464
+ * Combined with `endAngle`, restricts the pie/donut to a partial sweep,
465
+ * e.g. `startAngle: -Math.PI / 2, endAngle: Math.PI / 2` for a half-donut
466
+ * (election-style results donut). Angles follow d3's convention: 0 is
467
+ * straight up, increasing clockwise. Only meaningful when `type` is `'arc'`.
468
+ */
469
+ startAngle?: number;
470
+ /**
471
+ * End angle in radians for arc marks. Defaults to 2*Math.PI (full pie/donut).
472
+ * See `startAngle`. Only meaningful when `type` is `'arc'`.
473
+ */
474
+ endAngle?: number;
459
475
  /** Corner radius for rect/bar marks. 'pill' sets rx to half the bar thickness. */
460
476
  cornerRadius?: number | 'pill';
461
477
  /** Fixed bar thickness in pixels for bar/column marks. When set, bars are this height (horizontal) or width (vertical), centered within the band. */
@@ -482,6 +498,102 @@ interface MarkDef {
482
498
  * two competing diagonals.
483
499
  */
484
500
  trendline?: boolean;
501
+ /**
502
+ * Horizontal pixel offset from the data anchor. Only meaningful when `type`
503
+ * is `'text'`. Use it to lift a label clear of the mark it annotates instead
504
+ * of shifting the underlying data value.
505
+ */
506
+ dx?: number;
507
+ /**
508
+ * Vertical pixel offset from the data anchor (negative = up). Only meaningful
509
+ * when `type` is `'text'`. `{ type: 'text', dy: -10 }` floats each label ten
510
+ * pixels above its point.
511
+ */
512
+ dy?: number;
513
+ /** Horizontal alignment of text marks relative to their anchor. Defaults to `'center'`. */
514
+ align?: 'left' | 'center' | 'right';
515
+ /** Vertical alignment of text marks relative to their anchor. Defaults to `'middle'`. */
516
+ baseline?: 'top' | 'middle' | 'bottom';
517
+ /**
518
+ * Font size in pixels for text marks. Ignored when the `size` encoding channel
519
+ * is present (which scales font size from a data field). `size` is already
520
+ * taken by bar thickness, hence the distinct name.
521
+ */
522
+ fontSize?: number;
523
+ /**
524
+ * Pattern-fill reinforcement for filled marks (bar, area, arc).
525
+ *
526
+ * - `'auto'`: assign a per-series SVG pattern (diagonal hatch, dots,
527
+ * crosshatch, vertical lines) layered over each series color so series
528
+ * remain distinguishable without color vision. Marks too small to show
529
+ * a legible pattern keep their solid fill.
530
+ * - `'none'` (default): solid fills only.
531
+ */
532
+ fillPattern?: 'auto' | 'none';
533
+ /**
534
+ * Visual style for range marks. Only meaningful when `type` is `'range'`.
535
+ * - 'dumbbell' (default): a dot at each end joined by a connector line.
536
+ * The start dot is muted, the end dot carries the accent color.
537
+ * - 'arrow': a line with an arrowhead at the x2/y2 end, the strongest
538
+ * "change between two points in time" form.
539
+ * - 'bar': a plain floating range bar spanning start to end.
540
+ */
541
+ style?: 'dumbbell' | 'arrow' | 'bar';
542
+ /**
543
+ * Color range marks by direction of change. Only meaningful when `type` is
544
+ * `'range'`. When true, increases (x2 > x, or y2 > y) use the theme's
545
+ * `positive` semantic color and decreases use `negative`. A field-based
546
+ * `encoding.color` takes precedence over direction coloring.
547
+ */
548
+ colorByDirection?: boolean;
549
+ /**
550
+ * Total number of cells in a waffle grid. Only meaningful when `type` is
551
+ * `'waffle'`. Category values normalize to this many cells via
552
+ * largest-remainder rounding so the grid always sums exactly. Default 100.
553
+ */
554
+ units?: number;
555
+ /**
556
+ * Number of columns in a waffle grid. Only meaningful when `type` is
557
+ * `'waffle'`. Rows derive from `units / columns`. Default 10.
558
+ */
559
+ columns?: number;
560
+ /**
561
+ * First day of the week for calendar marks. Only meaningful when `type` is
562
+ * `'calendar'`. Controls which weekday occupies the top row of each band.
563
+ * Defaults to 'monday' (ISO week convention).
564
+ */
565
+ weekStart?: 'monday' | 'sunday';
566
+ /**
567
+ * Corner radius in pixels for calendar day cells. Only meaningful when
568
+ * `type` is `'calendar'`. Defaults to 1.
569
+ */
570
+ cellRadius?: number;
571
+ /**
572
+ * Seat layout shape for parliament marks. Only meaningful when `type` is
573
+ * `'parliament'`. `'hemicycle'` (default, and currently the only shape)
574
+ * packs seats into concentric semicircular arcs, party-grouped left to
575
+ * right.
576
+ */
577
+ shape?: 'hemicycle';
578
+ /**
579
+ * Seat dot radius in pixels for parliament marks. `'auto'` (default) sizes
580
+ * dots to fill the available hemicycle rings for the given seat count.
581
+ * Only meaningful when `type` is `'parliament'`.
582
+ */
583
+ seatRadius?: number | 'auto';
584
+ /**
585
+ * The majority-threshold line and "N to win" label on parliament marks. Only
586
+ * meaningful when `type` is `'parliament'`. `true` (default) draws the line at
587
+ * a simple majority (half the seats plus one); `false` hides it. The object
588
+ * form overrides the threshold and label: `{ seats }` sets a custom seat count
589
+ * (e.g. a two-thirds supermajority) and `{ label }` overrides the default
590
+ * "N to win" text. A `seats` value outside `1..totalSeats` warns and falls
591
+ * back to the simple-majority default.
592
+ */
593
+ majorityLine?: boolean | {
594
+ seats?: number;
595
+ label?: string;
596
+ };
485
597
  }
486
598
  /** Data field type, following Vega-Lite conventions. */
487
599
  type FieldType = 'quantitative' | 'temporal' | 'nominal' | 'ordinal';
@@ -491,7 +603,11 @@ type AggregateOp = 'count' | 'sum' | 'mean' | 'median' | 'min' | 'max' | 'varian
491
603
  interface AxisConfig {
492
604
  /** Axis title text. If omitted, the field name is used. */
493
605
  title?: string;
494
- /** Number format string (d3-format). e.g. ",.0f" for comma-separated integers. */
606
+ /**
607
+ * Number format: d3-format string (e.g. ",.0f"), or semantic keyword
608
+ * `'percent'` / `'currency'`. When omitted, compact notation (1k, 2.5M)
609
+ * is applied automatically.
610
+ */
495
611
  format?: string;
496
612
  /** Override tick count. Engine picks a sensible default if omitted. */
497
613
  tickCount?: number;
@@ -534,8 +650,12 @@ interface AxisConfig {
534
650
  }
535
651
  /** Scale configuration for an encoding channel. */
536
652
  interface ScaleConfig {
537
- /** Explicit domain override. Auto-derived from data if omitted. */
538
- domain?: [number, number] | string[];
653
+ /**
654
+ * Explicit domain override. Auto-derived from data if omitted.
655
+ * Continuous scales take `[min, max]`; threshold scales take the full
656
+ * list of class break values; categorical scales take the value list.
657
+ */
658
+ domain?: number[] | string[];
539
659
  /** Scale type override. Usually inferred from field type. */
540
660
  type?: ScaleType;
541
661
  /** Whether to nice-ify the domain for clean tick values. Defaults to true. */
@@ -546,6 +666,15 @@ interface ScaleConfig {
546
666
  clip?: boolean;
547
667
  /** Explicit range override. */
548
668
  range?: unknown[];
669
+ /**
670
+ * Named color scheme (Vega-Lite aligned). Resolved against the core palette
671
+ * registry (sequential: blue, green, orange, purple, teal; diverging:
672
+ * redBlue, brownTeal; categorical) during the pre-validation sugar
673
+ * expansion; common VL names (blues, greens, category10, ...) are accepted
674
+ * as aliases. Unknown names fail validation with the supported list.
675
+ * An explicit `range` wins over `scheme`.
676
+ */
677
+ scheme?: string;
549
678
  /** Reverse the range direction. */
550
679
  reverse?: boolean;
551
680
  /** Clamp output to the range. */
@@ -565,6 +694,28 @@ interface ScaleConfig {
565
694
  }
566
695
  /** Scale type, following Vega-Lite conventions. */
567
696
  type ScaleType = 'linear' | 'log' | 'pow' | 'sqrt' | 'symlog' | 'time' | 'utc' | 'ordinal' | 'band' | 'point' | 'quantile' | 'quantize' | 'threshold';
697
+ /**
698
+ * Sort a categorical domain by the aggregated values of another field
699
+ * (Vega-Lite EncodingSortField).
700
+ */
701
+ interface SortByField {
702
+ /** Data field whose values determine the category order. */
703
+ field: string;
704
+ /** Aggregate applied per category before comparing. Defaults to 'sum'. */
705
+ op?: AggregateOp;
706
+ /** Sort direction. Defaults to 'ascending'. */
707
+ order?: 'ascending' | 'descending';
708
+ }
709
+ /**
710
+ * A constant value definition for an encoding channel (Vega-Lite aligned).
711
+ * The VL idiom for a fixed visual property, e.g. `color: { value: '#1b7fa3' }`.
712
+ * Expanded to the matching mark-level property (fill/stroke, size, opacity)
713
+ * during the pre-validation sugar expansion.
714
+ */
715
+ interface ValueDef {
716
+ /** The constant value: CSS color string, size number, opacity (0-1), or gradient. */
717
+ value: string | number | boolean | null | GradientDef;
718
+ }
568
719
  /**
569
720
  * A single encoding channel mapping a data field to a visual property.
570
721
  *
@@ -596,8 +747,11 @@ interface EncodingChannel<TData extends DataRow = DataRow> {
596
747
  type: FieldType;
597
748
  /** Optional aggregate to apply before encoding. */
598
749
  aggregate?: AggregateOp;
599
- /** Axis configuration. Set to `false` to suppress axis entirely (no space reserved). */
600
- axis?: AxisConfig | false;
750
+ /**
751
+ * Axis configuration. Set to `false` to suppress axis entirely (no space
752
+ * reserved). `null` is accepted as a VL-aligned alias for `false`.
753
+ */
754
+ axis?: AxisConfig | false | null;
601
755
  /** Scale configuration. */
602
756
  scale?: ScaleConfig;
603
757
  /**
@@ -609,26 +763,28 @@ interface EncodingChannel<TData extends DataRow = DataRow> {
609
763
  * The string forms are still the canonical input — the boolean shorthand is
610
764
  * normalized to the matching string before reaching layout.
611
765
  *
612
- * - undefined: chart-type default (see below)
766
+ * - undefined: chart-type default (see below) -- stacked for bar/column/area
613
767
  * - true | 'zero': stack from zero baseline
614
768
  * - 'normalize': stack and normalize to fraction of total (0-1 per category)
615
769
  * - 'center': center stacks around zero (streamgraph style)
616
- * - null | false: no stacking -- renders overlap (area) or grouped/dodged (bar)
770
+ * - null | false: no stacking -- renders grouped/dodged (bar) or overlap (area)
617
771
  *
618
772
  * **Defaults differ by chart type:**
619
- * - **Bar/Column**: defaults to grouped (side-by-side). Use `stack: 'zero'` (or `true`) for stacked bars.
620
- * - **Area**: defaults to overlap (v6 breaking change). Use `stack: 'zero'` (or `true`)
621
- * to opt into stacked areas. Each overlapping series renders as a translucent
622
- * gradient band anchored at the y-domain baseline.
773
+ * - **Bar/Column**: defaults to stacked (`'zero'`), Vega-Lite aligned. Use
774
+ * `stack: null` (or `false`) for grouped/side-by-side bars.
775
+ * - **Area**: defaults to stacked (`'zero'`), Vega-Lite aligned. Use
776
+ * `stack: null` (or `false`) to opt into overlap rendering, where each
777
+ * series renders as a translucent gradient band anchored at the
778
+ * y-domain baseline.
623
779
  * - **Line**: stacking is not applied (lines always overlap).
624
780
  *
625
781
  * @example
626
- * // Stacked horizontal bars (opt-in; default is grouped):
627
- * "x": { "field": "revenue", "type": "quantitative", "stack": "zero" }
782
+ * // Grouped horizontal bars (opt-out; default is stacked):
783
+ * "x": { "field": "revenue", "type": "quantitative", "stack": null }
628
784
  *
629
785
  * @example
630
- * // Stacked area (opt-in; default is overlap):
631
- * "y": { "field": "value", "type": "quantitative", "stack": "zero" }
786
+ * // Overlap area (opt-out; default is stacked):
787
+ * "y": { "field": "value", "type": "quantitative", "stack": null }
632
788
  */
633
789
  stack?: boolean | 'zero' | 'normalize' | 'center' | null;
634
790
  /**
@@ -651,8 +807,22 @@ interface EncodingChannel<TData extends DataRow = DataRow> {
651
807
  * - 'descending': sort domain values descending
652
808
  * - null: use data order (no sorting)
653
809
  * - undefined: ascending (VL default)
810
+ * - 'x' | '-x' | 'y' | '-y': sort by another channel's values ('-' prefix
811
+ * for descending). The VL ranked-bar idiom: `sort: '-y'` orders categories
812
+ * by value, largest first. Resolved into an explicit domain during the
813
+ * pre-validation sugar expansion.
814
+ * - string[]: explicit value order. Listed values come first (in the given
815
+ * order); unlisted data values follow in data order.
816
+ * - SortByField: sort by the aggregated values of another field.
654
817
  */
655
- sort?: 'ascending' | 'descending' | null;
818
+ sort?: 'ascending' | 'descending' | null | 'x' | '-x' | 'y' | '-y' | string[] | SortByField;
819
+ /**
820
+ * Legend configuration for this channel (Vega-Lite aligned).
821
+ * Only meaningful on the `color` channel. `null` hides the legend (the VL
822
+ * idiom for `legend.show = false`); a config object merges into the
823
+ * top-level `legend` (top-level keys win on conflict).
824
+ */
825
+ legend?: LegendConfig | null;
656
826
  /**
657
827
  * Display title override (Vega-Lite aligned).
658
828
  * Used as the label in tooltips instead of the raw field name.
@@ -661,7 +831,8 @@ interface EncodingChannel<TData extends DataRow = DataRow> {
661
831
  title?: string;
662
832
  /**
663
833
  * Format string for values (Vega-Lite aligned).
664
- * For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f").
834
+ * For quantitative fields: d3-format string (e.g. ",.0f", "$,.2f"),
835
+ * or semantic keyword `'percent'` / `'currency'`.
665
836
  * For temporal fields: d3-time-format string (e.g. "%Y", "%b %d").
666
837
  * Used in tooltips; `axis.format` takes precedence for axis tick labels.
667
838
  */
@@ -721,14 +892,16 @@ interface Encoding<TData extends DataRow = DataRow> {
721
892
  /**
722
893
  * Color channel. Required for arc marks (determines pie/donut slice coloring).
723
894
  * Optional for all other marks -- used for series differentiation on multi-series charts,
724
- * or heatmap intensity. Accepts a conditional definition to apply colors based on data predicates.
895
+ * or heatmap intensity. Accepts a conditional definition to apply colors based on data predicates,
896
+ * or a bare `{ value }` constant (VL aligned; expanded to the mark-level fill/stroke).
725
897
  */
726
- color?: EncodingChannel<TData> | ConditionalValueDef<TData>;
898
+ color?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
727
899
  /**
728
900
  * Size channel. Used by point/bubble charts to scale dot area by a quantitative field.
729
- * Accepts a conditional definition to vary size based on data predicates.
901
+ * Accepts a conditional definition to vary size based on data predicates,
902
+ * or a bare `{ value }` constant (VL aligned; expanded to `mark.size`).
730
903
  */
731
- size?: EncodingChannel<TData> | ConditionalValueDef<TData>;
904
+ size?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
732
905
  /**
733
906
  * Detail channel. Groups data into multiple series without mapping to a visual property.
734
907
  * Useful when you want separate lines per category but don't need the color to differ.
@@ -752,17 +925,14 @@ interface Encoding<TData extends DataRow = DataRow> {
752
925
  y2?: EncodingChannel<TData>;
753
926
  /**
754
927
  * Data-driven opacity (0-1 range). Accepts a conditional definition to vary opacity
755
- * based on data predicates (e.g., highlight selected points).
756
- */
757
- opacity?: EncodingChannel<TData> | ConditionalValueDef<TData>;
758
- /**
759
- * Point shape encoding. Valid values: 'circle', 'square', 'diamond', 'triangle-up',
760
- * 'triangle-down', 'cross'. Used on point/scatter marks to differentiate series by shape.
928
+ * based on data predicates (e.g., highlight selected points), or a bare `{ value }`
929
+ * constant (VL aligned; expanded to `mark.opacity`).
761
930
  */
762
- shape?: EncodingChannel<TData>;
931
+ opacity?: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef;
763
932
  /**
764
- * Stroke dash pattern encoding. Maps a nominal field to different dash patterns
765
- * on line marks. Useful when color alone doesn't distinguish series well.
933
+ * Stroke dash pattern encoding. Maps a nominal/ordinal field to different
934
+ * dash patterns on line and rule marks. Useful when color alone doesn't
935
+ * distinguish series well.
766
936
  */
767
937
  strokeDash?: EncodingChannel<TData>;
768
938
  /** Rotation angle encoding for point marks. Maps a quantitative field to 0-360 degrees. */
@@ -777,25 +947,15 @@ interface Encoding<TData extends DataRow = DataRow> {
777
947
  * multi-field tooltips. Independent of the x/y/color encoding.
778
948
  */
779
949
  tooltip?: EncodingChannel<TData> | EncodingChannel<TData>[];
780
- /** Hyperlink encoding. Maps a field containing URLs to clickable marks. */
781
- href?: EncodingChannel<TData>;
782
950
  /**
783
- * Drawing order. Controls z-order and stacking sort order for bar/area marks.
784
- * Lower values are drawn first (behind higher values).
785
- */
786
- order?: EncodingChannel<TData>;
787
- /**
788
- * Angular position for arc marks (pie/donut).
789
- * Optional -- defaults to the `y` channel value when omitted.
790
- * Not used by any other mark type.
951
+ * Angular position (slice value) for arc marks (pie/donut), the canonical
952
+ * value channel in v8. Waffle and parliament marks accept it too (the same
953
+ * part-to-whole value channel). `y` is still accepted on these marks as a
954
+ * deprecated alias for backward compatibility (a compile warning is emitted
955
+ * and `y` is populated from `theta` internally). Not used by any other mark
956
+ * type.
791
957
  */
792
958
  theta?: EncodingChannel<TData>;
793
- /**
794
- * Radial distance from center for arc marks.
795
- * Optional -- only meaningful on donut charts (controls inner radius boundary).
796
- * Not used by any other mark type.
797
- */
798
- radius?: EncodingChannel<TData>;
799
959
  /**
800
960
  * Facet channel: partitions data into a grid of small-multiple panels.
801
961
  * Each unique value of the facet field produces one panel. Panels share
@@ -907,15 +1067,24 @@ interface AnnotationOffset {
907
1067
  }
908
1068
  /** Anchor direction for annotation label placement relative to the data point. */
909
1069
  type AnnotationAnchor = 'top' | 'bottom' | 'left' | 'right' | 'auto';
910
- /** Style overrides for the dot marker drawn at the connector's data-point endpoint. */
1070
+ /** Connector type for annotation labels. */
1071
+ type ConnectorType = 'straight' | 'curve' | 'drop-line';
1072
+ /** Object form of the connector config, allowing arrow control. */
1073
+ interface ConnectorConfig {
1074
+ /** Connector style. */
1075
+ type: ConnectorType;
1076
+ /** Whether to draw an arrowhead at the data-point end. Default: true for curve, false for straight. Drop-line never takes a head. */
1077
+ arrow?: boolean;
1078
+ }
1079
+ /** Style overrides for the dot marker drawn at the data point. */
911
1080
  interface AnnotationDot {
912
- /** Circle radius in pixels. Default 5. */
1081
+ /** Circle radius in pixels. Default 4. */
913
1082
  radius?: number;
914
1083
  /** Fill color. Defaults to theme background for an "open ring" look. */
915
1084
  fill?: string;
916
- /** Stroke color. Defaults to theme text color. */
1085
+ /** Stroke color. Defaults to the connector's resolved stroke, so marker and leader read as one system. */
917
1086
  stroke?: string;
918
- /** Stroke width in pixels. Default 2. */
1087
+ /** Stroke width in pixels. Default 1.5. */
919
1088
  strokeWidth?: number;
920
1089
  }
921
1090
  /** Base properties shared by all annotation types. */
@@ -945,7 +1114,13 @@ interface TextAnnotation extends AnnotationBase {
945
1114
  x: string | number;
946
1115
  /** Y-axis data value or position. */
947
1116
  y: string | number;
948
- /** The annotation text. Required for text annotations. */
1117
+ /**
1118
+ * The annotation text. Required for text annotations.
1119
+ *
1120
+ * Supports inline `**bold**` spans, so emphasis lands on the key phrase rather
1121
+ * than the whole block: `'Inflation peaked at **8.5%**'`. Unmatched `**` (and
1122
+ * empty `****`) render literally. `\n` starts a new line.
1123
+ */
949
1124
  text: string;
950
1125
  /** Thinning priority (lower = kept longer at narrow widths). When omitted, spec order is used. */
951
1126
  priority?: number;
@@ -953,12 +1128,23 @@ interface TextAnnotation extends AnnotationBase {
953
1128
  * Optional muted second-tone text rendered below the primary `text`.
954
1129
  * Used for supporting context (e.g. methodology, source). Newlines in
955
1130
  * `text` still produce multi-line primary; subtitle is a separate block.
1131
+ *
1132
+ * Supports the same inline `**bold**` spans as `text`. When a subtitle is
1133
+ * present and no `fontWeight` is set, the primary `text` resolves to weight
1134
+ * 700 — the lede + context stack.
956
1135
  */
957
1136
  subtitle?: string;
958
1137
  /**
959
- * Optional dot marker drawn at the connector's data-point endpoint.
960
- * `true` enables the default open-ring style. Pass an object to override
961
- * radius, fill, stroke, or strokeWidth.
1138
+ * Open-ring marker drawn on the data point itself (not on the pulled-back
1139
+ * connector tip).
1140
+ *
1141
+ * Left unset, a default marker appears whenever a connector is enabled and
1142
+ * carries no arrowhead — the leader points, the ring lands. An arrowed
1143
+ * connector gets no marker unless you ask for one: the head already marks the
1144
+ * spot. `dot: false` is always bare; an explicit `dot` always wins.
1145
+ *
1146
+ * `true` uses the default style; pass an object to override radius, fill,
1147
+ * stroke, or strokeWidth.
962
1148
  */
963
1149
  dot?: boolean | AnnotationDot;
964
1150
  /** Font size override. */
@@ -971,13 +1157,19 @@ interface TextAnnotation extends AnnotationBase {
971
1157
  anchor?: AnnotationAnchor;
972
1158
  /**
973
1159
  * Connector from label to anchor point.
974
- * - `true` (default): straight line
975
- * - `'straight'`: straight line (alias of `true`)
1160
+ * - `true` (default): straight line, no arrow
1161
+ * - `'straight'`: straight line, no arrow
976
1162
  * - `'curve'`: curved arrow with arrowhead
977
- * - `'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
1163
+ * - `'drop-line'`: vertical line through the data point's x
1164
+ * - `{ type, arrow? }`: object form for explicit arrow control
978
1165
  * - `false`: no connector
1166
+ *
1167
+ * The connector leaves the text block on the side facing the data point, with
1168
+ * a small standoff gap. A leader shorter than 8px is dropped as noise (the
1169
+ * marker alone reads better), as is a connector whose target sits inside the
1170
+ * text block. Widen `offset` if you want the line back.
979
1171
  */
980
- connector?: boolean | 'straight' | 'curve' | 'drop-line';
1172
+ connector?: boolean | ConnectorType | ConnectorConfig;
981
1173
  /** Per-endpoint offsets for the connector line. Allows fine-tuning where the connector starts and ends. */
982
1174
  connectorOffset?: {
983
1175
  /** Offset for the label-end of the connector. */
@@ -985,8 +1177,14 @@ interface TextAnnotation extends AnnotationBase {
985
1177
  /** Offset for the data-point-end of the connector. */
986
1178
  to?: AnnotationOffset;
987
1179
  };
988
- /** Background color behind the text. Useful for readability over chart lines. */
989
- background?: string;
1180
+ /**
1181
+ * Background plate behind the text, for readability over chart lines.
1182
+ *
1183
+ * `true` uses the theme surface, so the plate follows light/dark mode. A
1184
+ * color string sets it explicitly. Prefer `true`: a hardcoded '#ffffff' stays
1185
+ * white in dark mode and the text turns light-gray-on-white.
1186
+ */
1187
+ background?: string | true;
990
1188
  /** Whether to show the paint-order stroke halo behind text. Default true. Set false for white text on colored backgrounds. */
991
1189
  halo?: boolean;
992
1190
  }
@@ -1028,7 +1226,14 @@ interface RangeAnnotation extends AnnotationBase {
1028
1226
  * Useful for baselines (zero), targets, or thresholds.
1029
1227
  */
1030
1228
  interface RefLineAnnotation extends AnnotationBase {
1031
- type: 'refline' | 'rule';
1229
+ /**
1230
+ * Discriminant. `'refline'` is canonical and the only value in the type.
1231
+ * `'rule'` (it collides with the `rule` mark type) is still accepted at
1232
+ * runtime for backward compatibility -- the spec-sugar pass rewrites it to
1233
+ * `'refline'` with a deprecation warning before validation -- but it is not
1234
+ * part of this type. Use `'refline'`.
1235
+ */
1236
+ type: 'refline';
1032
1237
  /** X-axis value for a vertical reference line. */
1033
1238
  x?: string | number;
1034
1239
  /** Y-axis value for a horizontal reference line. */
@@ -1212,7 +1417,11 @@ interface LabelConfig {
1212
1417
  suffix?: string;
1213
1418
  /** Fixed CSS color for all labels. Overrides the default fill-derived color. */
1214
1419
  color?: string;
1215
- /** Per-series pixel offsets for fine-tuning label positions, keyed by series name. */
1420
+ /**
1421
+ * Per-series pixel offsets for fine-tuning label positions, keyed by series name.
1422
+ * @deprecated Per-series pixel offsets are a workaround for collision bugs
1423
+ * now fixed by the layout engine. Will be removed in a future version.
1424
+ */
1216
1425
  offsets?: Record<string, AnnotationOffset>;
1217
1426
  /** Font size in pixels for bar/column value labels. */
1218
1427
  fontSize?: number;
@@ -1236,6 +1445,46 @@ interface LegendConfig {
1236
1445
  /** Series names to exclude from the legend. Excluded series still render in the chart. */
1237
1446
  exclude?: string[];
1238
1447
  }
1448
+ /**
1449
+ * Configuration for the searchable series highlight input (`seriesSearch`).
1450
+ * The boolean form (`seriesSearch: true`) uses the defaults.
1451
+ */
1452
+ interface SeriesSearchConfig {
1453
+ /** Placeholder text for the search input. Defaults to "Find a series". */
1454
+ placeholder?: string;
1455
+ }
1456
+ /**
1457
+ * Configuration for the "you draw it" interactive engagement format
1458
+ * (`youDrawIt`). The reader draws their guess of the trend from `from` to the
1459
+ * right edge before the real line reveals. Line marks only, single-series only.
1460
+ */
1461
+ interface YouDrawItConfig {
1462
+ /**
1463
+ * The x value (matching `encoding.x`'s domain) where drawing starts. Data
1464
+ * before this point renders normally; data at or after it is hidden behind
1465
+ * a hatched "draw here" region until reveal.
1466
+ */
1467
+ from: string | number;
1468
+ /** Prompt text shown in the hatched drawing region. Defaults to "Draw your guess". */
1469
+ prompt?: string;
1470
+ /** Label for the skip-to-reveal button. Defaults to "Show me". */
1471
+ revealLabel?: string;
1472
+ /**
1473
+ * Optional extra comparison line (e.g. "what everyone else guessed"),
1474
+ * supplied by the host app. Rendered in a third, visually distinct style
1475
+ * alongside the reader's guess and the real line after reveal.
1476
+ */
1477
+ comparisonLine?: Array<{
1478
+ x: string | number;
1479
+ y: number;
1480
+ }>;
1481
+ }
1482
+ /**
1483
+ * Content composition for an endpoint-label entry: the series name, the
1484
+ * formatted value, or both joined on one line. See
1485
+ * {@link EndpointLabelsConfig.content}.
1486
+ */
1487
+ type EndpointLabelContent = 'label' | 'value' | 'label value';
1239
1488
  /**
1240
1489
  * Configuration for the endpoint labels column rendered at the chart's right edge
1241
1490
  * for multi-series line/area charts. Each entry pairs the series name with its
@@ -1273,6 +1522,21 @@ interface EndpointLabelsConfig {
1273
1522
  * - `'both'`: labels on both left (leading) and right (trailing) edges.
1274
1523
  */
1275
1524
  ends?: 'end' | 'both';
1525
+ /**
1526
+ * What each entry renders.
1527
+ * - `'label'`: series name only.
1528
+ * - `'value'`: formatted value only.
1529
+ * - `'label value'`: series name and value joined on one line.
1530
+ *
1531
+ * When omitted, entries render the series name with the formatted value on
1532
+ * a second line (the classic endpoint column). Pass an object to set the
1533
+ * leading and trailing columns independently, e.g. the classic slope-chart
1534
+ * look is `{ leading: 'label value', trailing: 'value' }`.
1535
+ */
1536
+ content?: EndpointLabelContent | {
1537
+ leading?: EndpointLabelContent;
1538
+ trailing?: EndpointLabelContent;
1539
+ };
1276
1540
  /** Field to read the displayed value from. Defaults to `encoding.y.field`. */
1277
1541
  valueField?: string;
1278
1542
  /** d3-format string for the value. Defaults to `encoding.y.axis.format`. */
@@ -1422,14 +1686,14 @@ interface Metric {
1422
1686
  }
1423
1687
  /**
1424
1688
  * Encoding for arc marks (pie/donut charts).
1425
- * - `y`: required (quantitative — the slice value)
1689
+ * - `theta`: required (quantitative — the canonical slice value channel)
1426
1690
  * - `color`: required (nominal/ordinal — the category)
1427
- * - `theta`: optional (defaults to `y` channel)
1428
- * - `radius`: optional (donut inner radius)
1691
+ * - `y`: deprecated alias for `theta`, accepted for backward compatibility
1429
1692
  */
1430
1693
  interface ArcEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1431
- y: EncodingChannel<TData>;
1432
- color: EncodingChannel<TData>;
1694
+ /** Canonical value channel for arc marks (quantitative — the slice value). */
1695
+ theta: EncodingChannel<TData>;
1696
+ color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1433
1697
  }
1434
1698
  /**
1435
1699
  * Encoding for line marks.
@@ -1485,6 +1749,62 @@ interface LollipopEncoding<TData extends DataRow = DataRow> extends Encoding<TDa
1485
1749
  x: EncodingChannel<TData>;
1486
1750
  y: EncodingChannel<TData>;
1487
1751
  }
1752
+ /**
1753
+ * Encoding for beeswarm marks (dodged distribution dots).
1754
+ * - Exactly one positional channel is quantitative: the value axis
1755
+ * (x = horizontal swarm, y = vertical swarm).
1756
+ * - The other positional channel is optional (nominal/ordinal): grouped
1757
+ * swarms, one lane per category. The cross axis has no scale; the dodge
1758
+ * layout computes pixel offsets around each lane center.
1759
+ */
1760
+ interface BeeswarmEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1761
+ x?: EncodingChannel<TData>;
1762
+ y?: EncodingChannel<TData>;
1763
+ }
1764
+ /**
1765
+ * Encoding for range marks (dumbbell / arrow / range-bar plots).
1766
+ * - Horizontal (the common editorial form): `y` nominal/ordinal category,
1767
+ * `x` quantitative start, `x2` quantitative end.
1768
+ * - Vertical: `x` nominal/ordinal category, `y` quantitative start,
1769
+ * `y2` quantitative end.
1770
+ */
1771
+ interface RangeEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1772
+ x: EncodingChannel<TData>;
1773
+ y: EncodingChannel<TData>;
1774
+ }
1775
+ /**
1776
+ * Encoding for waffle marks (unit grids for part-to-whole counts).
1777
+ * - `color`: required (nominal/ordinal, the category)
1778
+ * - `theta`: the canonical quantitative share, the same part-to-whole value
1779
+ * channel arc marks use. `y` is a deprecated alias, accepted for backward
1780
+ * compatibility.
1781
+ */
1782
+ interface WaffleEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1783
+ color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1784
+ }
1785
+ /**
1786
+ * Encoding for parliament marks (hemicycle seat-dot charts).
1787
+ * - `color`: required (nominal/ordinal, the party)
1788
+ * - `theta`: the canonical quantitative seat count, the same part-to-whole
1789
+ * value channel arc/waffle marks use. `y` is a deprecated alias, accepted
1790
+ * for backward compatibility.
1791
+ */
1792
+ interface ParliamentEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1793
+ color: EncodingChannel<TData> | ConditionalValueDef<TData>;
1794
+ }
1795
+ /**
1796
+ * Encoding for calendar marks (GitHub-style calendar heatmaps).
1797
+ * - `x`: required (temporal, daily dates, one row per day)
1798
+ * - `color`: required (quantitative, the per-day value)
1799
+ *
1800
+ * The calendar computes its own weeks-x-weekdays geometry: no positional
1801
+ * scales or axes. Multi-year data partitions into one stacked band per year,
1802
+ * all bands sharing a single color scale and legend.
1803
+ */
1804
+ interface CalendarEncoding<TData extends DataRow = DataRow> extends Encoding<TData> {
1805
+ x: EncodingChannel<TData>;
1806
+ color: EncodingChannel<TData>;
1807
+ }
1488
1808
  /**
1489
1809
  * Encoding for text marks (data-positioned labels).
1490
1810
  * - `text`: required (the field to render as text)
@@ -1511,6 +1831,28 @@ interface RectEncoding<TData extends DataRow = DataRow> extends Encoding<TData>
1511
1831
  x: EncodingChannel<TData>;
1512
1832
  y: EncodingChannel<TData>;
1513
1833
  }
1834
+ /**
1835
+ * Author-facing accessibility overrides.
1836
+ *
1837
+ * Everything here is optional; when omitted, openchart auto-generates alt
1838
+ * text, ARIA labels, and a screen-reader data table from the spec and data.
1839
+ */
1840
+ interface A11yConfig {
1841
+ /**
1842
+ * Alt text announced by screen readers for the chart as a whole. Replaces
1843
+ * the auto-generated description. Also settable via the top-level
1844
+ * `description` field (Vega-Lite convention); this field wins when both
1845
+ * are present.
1846
+ */
1847
+ description?: string;
1848
+ /**
1849
+ * Hide the chart from assistive technology entirely (`aria-hidden`).
1850
+ * Use only when the chart is decorative or the surrounding content
1851
+ * already conveys the same information. Also suppresses the hidden
1852
+ * screen-reader data table and keyboard mark navigation.
1853
+ */
1854
+ hidden?: boolean;
1855
+ }
1514
1856
  /**
1515
1857
  * Properties shared across all ChartSpec mark variants.
1516
1858
  * Extracted to avoid repeating them in every union member.
@@ -1518,12 +1860,47 @@ interface RectEncoding<TData extends DataRow = DataRow> extends Encoding<TData>
1518
1860
  * @internal
1519
1861
  */
1520
1862
  interface BaseChartSpec<TData extends DataRow = DataRow> {
1521
- /** Data array: each element is a row with field values. */
1863
+ /**
1864
+ * Data array: each element is a row with field values.
1865
+ * The Vega-Lite object form `{ values: [...] }` is also accepted at runtime
1866
+ * and unwrapped during the pre-validation sugar expansion. `{ url }` is not
1867
+ * supported (openchart does not fetch data).
1868
+ */
1522
1869
  data: TData[];
1523
1870
  /** Data transforms applied in order before encoding (filter, bin, calculate, timeUnit). */
1524
1871
  transform?: Transform[];
1525
1872
  /** Editorial chrome (title, subtitle, source, etc.). */
1526
1873
  chrome?: Chrome;
1874
+ /**
1875
+ * Top-level title sugar (Vega-Lite aligned). Expanded into `chrome.title`
1876
+ * (and `chrome.subtitle` for the object form). `chrome.title` wins when
1877
+ * both are set.
1878
+ */
1879
+ title?: string | {
1880
+ text: string;
1881
+ subtitle?: string;
1882
+ };
1883
+ /** Top-level subtitle sugar. Expanded into `chrome.subtitle`; `chrome.subtitle` wins. */
1884
+ subtitle?: string;
1885
+ /**
1886
+ * Alt text for the rendered chart (Vega-Lite aligned). Sugar for
1887
+ * `a11y.description`; `a11y.description` wins when both are set.
1888
+ * When absent, alt text is auto-generated from the spec and data.
1889
+ */
1890
+ description?: string;
1891
+ /** Accessibility overrides. See {@link A11yConfig}. */
1892
+ a11y?: A11yConfig;
1893
+ /**
1894
+ * Fixed render width in pixels (Vega-Lite aligned). Overrides the
1895
+ * container-derived width. When both `width` and `height` are set,
1896
+ * `responsive: false` is implied unless `responsive` is set explicitly.
1897
+ * Omit for the default container-driven sizing.
1898
+ */
1899
+ width?: number;
1900
+ /** Fixed render height in pixels (Vega-Lite aligned). See `width`. */
1901
+ height?: number;
1902
+ /** Accepted for Vega-Lite compatibility and ignored (with a compile warning). */
1903
+ $schema?: string;
1527
1904
  /**
1528
1905
  * KPI/metric cells rendered as a horizontal row between subtitle and chart
1529
1906
  * area. Each cell shows a label/value pair with optional delta and secondary
@@ -1584,6 +1961,22 @@ interface BaseChartSpec<TData extends DataRow = DataRow> {
1584
1961
  * explicitly set.
1585
1962
  */
1586
1963
  crosshair?: boolean;
1964
+ /**
1965
+ * Searchable series highlight ("find your country"). Renders a compact
1966
+ * search input above the chart with typeahead over the color channel's
1967
+ * distinct values. Selecting a value adds it to the highlight set
1968
+ * (multi-select chips, removable); clearing restores the authored
1969
+ * `encoding.color.highlight` baseline. Requires a categorical color
1970
+ * encoding. Mutually exclusive with edit mode (search wins).
1971
+ */
1972
+ seriesSearch?: boolean | SeriesSearchConfig;
1973
+ /**
1974
+ * "You draw it" interactive engagement format: the reader draws their
1975
+ * guess of the trend before the real line reveals. Line marks only,
1976
+ * single-series only. Mutually exclusive with edit mode (validation
1977
+ * warning if both are enabled; youDrawIt is disabled).
1978
+ */
1979
+ youDrawIt?: YouDrawItConfig;
1587
1980
  /**
1588
1981
  * Display mode controlling how much chart chrome is rendered.
1589
1982
  *
@@ -1684,6 +2077,21 @@ type ChartSpec<TData extends DataRow = DataRow> = (BaseChartSpec<TData> & {
1684
2077
  type: 'lollipop';
1685
2078
  });
1686
2079
  encoding: LollipopEncoding<TData>;
2080
+ }) | (BaseChartSpec<TData> & {
2081
+ mark: 'beeswarm' | (MarkDef & {
2082
+ type: 'beeswarm';
2083
+ });
2084
+ encoding: BeeswarmEncoding<TData>;
2085
+ }) | (BaseChartSpec<TData> & {
2086
+ mark: 'range' | (MarkDef & {
2087
+ type: 'range';
2088
+ });
2089
+ encoding: RangeEncoding<TData>;
2090
+ }) | (BaseChartSpec<TData> & {
2091
+ mark: 'waffle' | (MarkDef & {
2092
+ type: 'waffle';
2093
+ });
2094
+ encoding: WaffleEncoding<TData>;
1687
2095
  }) | (BaseChartSpec<TData> & {
1688
2096
  mark: 'text' | (MarkDef & {
1689
2097
  type: 'text';
@@ -1704,6 +2112,16 @@ type ChartSpec<TData extends DataRow = DataRow> = (BaseChartSpec<TData> & {
1704
2112
  type: 'rule';
1705
2113
  });
1706
2114
  encoding: Encoding<TData>;
2115
+ }) | (BaseChartSpec<TData> & {
2116
+ mark: 'calendar' | (MarkDef & {
2117
+ type: 'calendar';
2118
+ });
2119
+ encoding: CalendarEncoding<TData>;
2120
+ }) | (BaseChartSpec<TData> & {
2121
+ mark: 'parliament' | (MarkDef & {
2122
+ type: 'parliament';
2123
+ });
2124
+ encoding: ParliamentEncoding<TData>;
1707
2125
  });
1708
2126
  /**
1709
2127
  * Table specification: input for data table visualizations.
@@ -1834,6 +2252,19 @@ interface LayerSpec<TData extends DataRow = DataRow> {
1834
2252
  transform?: Transform[];
1835
2253
  /** Editorial chrome (title, subtitle, source, etc.). */
1836
2254
  chrome?: Chrome;
2255
+ /** Top-level title sugar (VL aligned). Expanded into `chrome.title`; `chrome.title` wins. */
2256
+ title?: string | {
2257
+ text: string;
2258
+ subtitle?: string;
2259
+ };
2260
+ /** Top-level subtitle sugar. Expanded into `chrome.subtitle`; `chrome.subtitle` wins. */
2261
+ subtitle?: string;
2262
+ /** Fixed render width in pixels (VL aligned). Overrides the container-derived width. */
2263
+ width?: number;
2264
+ /** Fixed render height in pixels (VL aligned). Overrides the container-derived height. */
2265
+ height?: number;
2266
+ /** Accepted for Vega-Lite compatibility and ignored (with a compile warning). */
2267
+ $schema?: string;
1837
2268
  /** Annotations on the layered view. */
1838
2269
  annotations?: Annotation[];
1839
2270
  /** Label display configuration. `false` disables all labels, `true` uses defaults. */
@@ -1924,6 +2355,8 @@ interface SankeySpec {
1924
2355
  * number (data value 28 renders as "28%"). For currency: "$,.0f" or "$~s".
1925
2356
  * For SI suffixes: "~s" (renders 1000 as "1k"). When not set, values use
1926
2357
  * the default number formatter.
2358
+ * @deprecated Use the encoding channel's `format` field instead. This
2359
+ * property is expanded into the encoding during compilation.
1927
2360
  */
1928
2361
  valueFormat?: string;
1929
2362
  }
@@ -1983,6 +2416,62 @@ interface TileMapSpec {
1983
2416
  /**
1984
2417
  * d3-format string applied to tile values, legend labels, and tooltips.
1985
2418
  * Examples: ".1f" for one decimal, "$,.0f" for currency, "~s" for SI.
2419
+ * @deprecated Use the encoding channel's `format` field instead. This
2420
+ * property is expanded into the encoding during compilation.
2421
+ */
2422
+ valueFormat?: string;
2423
+ }
2424
+ /** Supported map projections. */
2425
+ type MapProjection = 'albersUsa' | 'mercator' | 'equalEarth' | 'identity';
2426
+ /** Focus target for map camera: feature id(s) to zoom/pan to. */
2427
+ type MapFocus = string | number | Array<string | number> | {
2428
+ features: string | number | Array<string | number>;
2429
+ padding?: number;
2430
+ };
2431
+ /** Geo configuration for map specs. */
2432
+ interface MapGeo {
2433
+ /** TopoJSON topology object. User imports this from us-atlas, world-atlas, or their own source. */
2434
+ features: unknown;
2435
+ /** Field in the TopoJSON feature properties to use as the join key. Defaults to 'id'. */
2436
+ idField?: string;
2437
+ /** Map projection. Defaults to 'albersUsa'. */
2438
+ projection?: MapProjection;
2439
+ /** Focus on specific feature(s). null clears focus from a prior story step. */
2440
+ focus?: MapFocus | null;
2441
+ }
2442
+ /** Encoding channels specific to map visualizations. */
2443
+ interface MapEncoding {
2444
+ /** Join key field in the data (required). Maps data rows to geo features via geo.idField. */
2445
+ key: EncodingChannel;
2446
+ /** Color encoding (required, quantitative or nominal). Drives fill color of features. */
2447
+ color: EncodingChannel;
2448
+ /** Tooltip encoding (optional). */
2449
+ tooltip?: EncodingChannel | EncodingChannel[];
2450
+ }
2451
+ interface MapSpec {
2452
+ /** Discriminant: always "map". */
2453
+ type: 'map';
2454
+ /** Geo configuration: TopoJSON features, join key, and projection. */
2455
+ geo: MapGeo;
2456
+ /** Tabular data to join to geo features. */
2457
+ data: DataRow[];
2458
+ /** Encoding channels mapping data fields to visual properties. */
2459
+ encoding: MapEncoding;
2460
+ /** Editorial chrome (title, subtitle, source, byline, footer). */
2461
+ chrome?: Chrome;
2462
+ /** Legend display configuration. */
2463
+ legend?: LegendConfig;
2464
+ /** Theme configuration overrides. */
2465
+ theme?: ThemeConfig;
2466
+ /** Dark mode behavior. Defaults to "off". */
2467
+ darkMode?: DarkMode;
2468
+ /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
2469
+ watermark?: boolean;
2470
+ /** Animation configuration for entrance animations. */
2471
+ animation?: AnimationSpec;
2472
+ /**
2473
+ * d3-format string applied to color values in tooltips and legend.
2474
+ * @deprecated Use the encoding channel's `format` field instead.
1986
2475
  */
1987
2476
  valueFormat?: string;
1988
2477
  }
@@ -2015,6 +2504,8 @@ interface BarListSpec {
2015
2504
  /**
2016
2505
  * d3-format string applied to bar values and tooltips.
2017
2506
  * Examples: ".1f" for one decimal, "$,.0f" for currency, "~s" for SI.
2507
+ * @deprecated Use the encoding channel's `format` field instead. This
2508
+ * property is expanded into the encoding during compilation.
2018
2509
  */
2019
2510
  valueFormat?: string;
2020
2511
  }
@@ -2040,9 +2531,17 @@ interface BarListEncoding {
2040
2531
  * - GraphSpec: has `type: 'graph'`
2041
2532
  * - SankeySpec: has `type: 'sankey'`
2042
2533
  * - TileMapSpec: has `type: 'tilemap'`
2534
+ * - MapSpec: has `type: 'map'`
2043
2535
  * - BarListSpec: has `type: 'barlist'`
2536
+ *
2537
+ * Election parliament (hemicycle) charts are `ChartSpec` with `mark:
2538
+ * 'parliament'`, not a separate top-level type: seat data (party + seats)
2539
+ * maps naturally onto encoding.color/y like arc and waffle, and riding the
2540
+ * existing chart pipeline gets scales, legend, tooltips, a11y, and dark mode
2541
+ * for free instead of a second bespoke compile path (see
2542
+ * `packages/engine/src/charts/parliament/`).
2044
2543
  */
2045
- type VizSpec = ChartSpec | LayerSpec | TableSpec | GraphSpec | SankeySpec | TileMapSpec | BarListSpec;
2544
+ type VizSpec = ChartSpec | LayerSpec | TableSpec | GraphSpec | SankeySpec | TileMapSpec | MapSpec | BarListSpec;
2046
2545
  /**
2047
2546
  * Chart spec without runtime data, for persistence/storage.
2048
2547
  * Generic: `ChartSpecWithoutData<MyRow>` constrains encoding field names.
@@ -2056,10 +2555,12 @@ type GraphSpecWithoutData = Omit<GraphSpec, 'nodes' | 'edges'>;
2056
2555
  type SankeySpecWithoutData = Omit<SankeySpec, 'data'>;
2057
2556
  /** TileMap spec without runtime data, for persistence/storage. */
2058
2557
  type TileMapSpecWithoutData = Omit<TileMapSpec, 'data'>;
2558
+ /** Map spec without runtime data, for persistence/storage. */
2559
+ type MapSpecWithoutData = Omit<MapSpec, 'data'>;
2059
2560
  /** BarList spec without runtime data, for persistence/storage. */
2060
2561
  type BarListSpecWithoutData = Omit<BarListSpec, 'data'>;
2061
2562
  /** Union of data-stripped spec types for persistence/storage. */
2062
- type StoredVizSpec = ChartSpecWithoutData | TableSpecWithoutData | GraphSpecWithoutData | SankeySpecWithoutData | TileMapSpecWithoutData | BarListSpecWithoutData;
2563
+ type StoredVizSpec = ChartSpecWithoutData | TableSpecWithoutData | GraphSpecWithoutData | SankeySpecWithoutData | TileMapSpecWithoutData | MapSpecWithoutData | BarListSpecWithoutData;
2063
2564
  /** Logical AND combinator for filter predicates. */
2064
2565
  interface LogicalAnd<T> {
2065
2566
  and: T[];
@@ -2215,9 +2716,10 @@ interface Condition<TData extends DataRow = DataRow> {
2215
2716
  test: FilterPredicate;
2216
2717
  /**
2217
2718
  * Static value to use when the condition is true.
2218
- * Accepted values: CSS color string, opacity (0-1), size number, or boolean flag.
2719
+ * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2720
+ * or a gradient definition for color channels.
2219
2721
  */
2220
- value?: string | number | boolean | null;
2722
+ value?: string | number | boolean | null | GradientDef;
2221
2723
  /**
2222
2724
  * Data field to use when the condition is true.
2223
2725
  * Constrained to column names of `TData` when using `ChartSpec<TData>`.
@@ -2237,24 +2739,34 @@ interface ConditionalValueDef<TData extends DataRow = DataRow> {
2237
2739
  condition: Condition<TData> | Condition<TData>[];
2238
2740
  /**
2239
2741
  * Default value when no condition matches.
2240
- * Accepted values: CSS color string, opacity (0-1), size number, or boolean flag.
2742
+ * Accepted values: CSS color string, opacity (0-1), size number, boolean flag,
2743
+ * or a gradient definition for color channels.
2241
2744
  */
2242
- value?: string | number | boolean | null;
2745
+ value?: string | number | boolean | null | GradientDef;
2243
2746
  }
2244
2747
  /**
2245
2748
  * Check if a channel definition is a regular EncodingChannel (has 'field' at top level).
2246
- * Use this to narrow `EncodingChannel | ConditionalValueDef` in encoding channels
2247
- * that support conditional encoding (color, size, opacity).
2749
+ * Use this to narrow `EncodingChannel | ConditionalValueDef | ValueDef` in encoding
2750
+ * channels that support conditional encoding (color, size, opacity).
2248
2751
  */
2249
- declare function isEncodingChannel<TData extends DataRow = DataRow>(def: EncodingChannel<TData> | ConditionalValueDef<TData> | undefined): def is EncodingChannel<TData>;
2752
+ declare function isEncodingChannel<TData extends DataRow = DataRow>(def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined): def is EncodingChannel<TData>;
2250
2753
  /**
2251
2754
  * Check if a channel definition is a ConditionalValueDef.
2252
2755
  */
2253
- declare function isConditionalDef<TData extends DataRow = DataRow>(def: EncodingChannel<TData> | ConditionalValueDef<TData> | undefined): def is ConditionalValueDef<TData>;
2756
+ declare function isConditionalDef<TData extends DataRow = DataRow>(def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined): def is ConditionalValueDef<TData>;
2757
+ /**
2758
+ * Check if a channel definition is a bare ValueDef (constant value, no field
2759
+ * or condition). These are expanded to mark-level properties during the
2760
+ * pre-validation sugar expansion.
2761
+ */
2762
+ declare function isValueDef<TData extends DataRow = DataRow>(def: EncodingChannel<TData> | ConditionalValueDef<TData> | ValueDef | undefined): def is ValueDef;
2254
2763
  /** All valid mark type strings for runtime checking. */
2255
2764
  declare const MARK_TYPES: ReadonlySet<string>;
2256
- /** @deprecated Use MARK_TYPES instead. */
2257
- declare const CHART_TYPES: ReadonlySet<string>;
2765
+ /**
2766
+ * Mark types that lay out inside the chart area without positional axes
2767
+ * (no axis or gridline computation; margins shrink to padding).
2768
+ */
2769
+ declare function isAxislessMark(markType: MarkType): boolean;
2258
2770
  /**
2259
2771
  * Extract the mark type string from a mark field (string or MarkDef).
2260
2772
  */
@@ -2277,11 +2789,17 @@ declare function isSankeySpec(spec: VizSpec | Record<string, unknown>): spec is
2277
2789
  declare function isTileMapSpec(spec: VizSpec | Record<string, unknown>): spec is TileMapSpec;
2278
2790
  /** Check if a spec is a BarListSpec. */
2279
2791
  declare function isBarListSpec(spec: VizSpec | Record<string, unknown>): spec is BarListSpec;
2792
+ /** Check if a spec is a MapSpec. */
2793
+ declare function isMapSpec(spec: VizSpec | Record<string, unknown>): spec is MapSpec;
2280
2794
  /** Check if an annotation is a TextAnnotation. */
2281
2795
  declare function isTextAnnotation(annotation: Annotation): annotation is TextAnnotation;
2282
2796
  /** Check if an annotation is a RangeAnnotation. */
2283
2797
  declare function isRangeAnnotation(annotation: Annotation): annotation is RangeAnnotation;
2284
- /** Check if an annotation is a RefLineAnnotation. */
2798
+ /**
2799
+ * Check if an annotation is a RefLineAnnotation. Also accepts the deprecated
2800
+ * `'rule'` type at runtime (pre-sugar-expansion input), since `annotation.type`
2801
+ * may not yet be narrowed to the canonical `'refline'` form.
2802
+ */
2285
2803
  declare function isRefLineAnnotation(annotation: Annotation): annotation is RefLineAnnotation;
2286
2804
  /** Human-readable display names for mark types (used in alt text, error messages). */
2287
2805
  declare const MARK_DISPLAY_NAMES: Record<MarkType, string>;
@@ -2309,15 +2827,11 @@ interface EncodingRule {
2309
2827
  y2?: ChannelRule;
2310
2828
  color: ChannelRule;
2311
2829
  size: ChannelRule;
2312
- shape?: ChannelRule;
2313
2830
  opacity?: ChannelRule;
2314
2831
  strokeDash?: ChannelRule;
2315
2832
  text?: ChannelRule;
2316
2833
  tooltip?: ChannelRule;
2317
- href?: ChannelRule;
2318
- order?: ChannelRule;
2319
2834
  theta?: ChannelRule;
2320
- radius?: ChannelRule;
2321
2835
  detail: ChannelRule;
2322
2836
  }
2323
2837
  /**
@@ -2336,10 +2850,17 @@ interface EncodingRule {
2336
2850
  * - point: both axes are quantitative
2337
2851
  * - text/rule: fully optional positioning
2338
2852
  * - tick/rect: both axes required, any type
2853
+ * - beeswarm: both axes optional at the channel level; the compiler enforces
2854
+ * the combination (exactly one quantitative value axis, the other axis an
2855
+ * optional nominal/ordinal lane channel)
2856
+ * - waffle: no positional axes; y is the share value (quantitative, with
2857
+ * theta accepted as an alias like arc), color is the category
2858
+ * - calendar: x is the daily date (temporal), color the per-day value
2859
+ * (quantitative); no y channel (the calendar owns its own geometry)
2860
+ * - parliament: no positional axes; theta (aliased to y) is the seat count
2861
+ * (quantitative), color the party
2339
2862
  */
2340
2863
  declare const MARK_ENCODING_RULES: Record<MarkType, EncodingRule>;
2341
- /** @deprecated Use MARK_ENCODING_RULES instead. */
2342
- declare const CHART_ENCODING_RULES: Record<MarkType, EncodingRule>;
2343
2864
  /** Encoding rule for a single graph visual channel. */
2344
2865
  interface GraphChannelRule {
2345
2866
  /** Whether this channel is required. */
@@ -2487,6 +3008,12 @@ interface ChartEventHandlers {
2487
3008
  onMarkLeave?: () => void;
2488
3009
  /** Called when a legend entry is toggled (clicked to show/hide a series). */
2489
3010
  onLegendToggle?: (series: string, visible: boolean) => void;
3011
+ /**
3012
+ * Called when the series search (`seriesSearch`) changes the highlight set.
3013
+ * Receives the full set of highlighted values after the change, including
3014
+ * the authored `encoding.color.highlight` baseline.
3015
+ */
3016
+ onHighlightChange?: (values: string[]) => void;
2490
3017
  /** Called when an annotation element is clicked. */
2491
3018
  onAnnotationClick?: (annotation: Annotation, event: MouseEvent) => void;
2492
3019
  /** Called when a text annotation label is dragged to a new position. */
@@ -2499,6 +3026,15 @@ interface ChartEventHandlers {
2499
3026
  onDeselect?: (element: ElementRef) => void;
2500
3027
  /** Fired when inline text editing commits. Also flows through onEdit as a 'text-edit' event. */
2501
3028
  onTextEdit?: (element: ElementRef, oldText: string, newText: string) => void;
3029
+ /**
3030
+ * Fired when a "you draw it" (`youDrawIt`) drawing is revealed (button click
3031
+ * or programmatic reveal), with the reader's guess in data coordinates,
3032
+ * ordered by x. For newsrooms aggregating reader guesses.
3033
+ */
3034
+ onReveal?: (guess: Array<{
3035
+ x: string | number;
3036
+ y: number;
3037
+ }>) => void;
2502
3038
  }
2503
3039
 
2504
3040
  /** A rectangle defined by position and dimensions. */
@@ -2593,6 +3129,12 @@ interface AxisTick {
2593
3129
  position: number;
2594
3130
  /** Formatted label string for display. */
2595
3131
  label: string;
3132
+ /**
3133
+ * The untruncated label, set only when `label` was shortened with an ellipsis
3134
+ * to fit the rotated-tick reservation. Renderers should surface this as the
3135
+ * tick's accessible/tooltip text so the full category name is never lost.
3136
+ */
3137
+ fullLabel?: string;
2596
3138
  /** Secondary label text from axis.labelField. */
2597
3139
  subtitle?: string;
2598
3140
  }
@@ -2653,6 +3195,23 @@ interface AxisLayout {
2653
3195
  angle?: number;
2654
3196
  };
2655
3197
  }
3198
+ /** The four built-in fill pattern shapes for series reinforcement. */
3199
+ type FillPatternType = 'diagonal' | 'dot' | 'crosshatch' | 'vertical';
3200
+ /**
3201
+ * A resolved per-mark fill pattern (engine output for `mark.fillPattern: 'auto'`).
3202
+ *
3203
+ * The renderer builds one SVG `<pattern>` per unique (type, base, line)
3204
+ * combination: a tile of `base` color overlaid with `line`-colored pattern
3205
+ * geometry. Marks referencing the same resolved pattern share one def.
3206
+ */
3207
+ interface ResolvedFillPattern {
3208
+ /** Pattern shape assigned to this mark's series. */
3209
+ type: FillPatternType;
3210
+ /** Base tile color: the series' solid color. */
3211
+ base: string;
3212
+ /** Pattern line/dot color, contrast-picked against `base`. */
3213
+ line: string;
3214
+ }
2656
3215
  /** Accessibility attributes for a mark. */
2657
3216
  interface MarkAria {
2658
3217
  /** ARIA label for the mark. Optional when `decorative: true` — decorative
@@ -2738,6 +3297,8 @@ interface AreaMark {
2738
3297
  interpolate?: string;
2739
3298
  /** Fill color or gradient. */
2740
3299
  fill: string | GradientDef;
3300
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
3301
+ pattern?: ResolvedFillPattern;
2741
3302
  /** Fill opacity. */
2742
3303
  fillOpacity: number;
2743
3304
  /** Optional stroke for the top boundary. */
@@ -2783,6 +3344,8 @@ interface RectMark {
2783
3344
  height: number;
2784
3345
  /** Fill color or gradient. */
2785
3346
  fill: string | GradientDef;
3347
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
3348
+ pattern?: ResolvedFillPattern;
2786
3349
  /** Stroke color. */
2787
3350
  stroke?: string;
2788
3351
  /** Stroke width. */
@@ -2840,6 +3403,8 @@ interface ArcMark {
2840
3403
  endAngle: number;
2841
3404
  /** Fill color or gradient. */
2842
3405
  fill: string | GradientDef;
3406
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
3407
+ pattern?: ResolvedFillPattern;
2843
3408
  /** Stroke color (usually white for slice separation). */
2844
3409
  stroke: string;
2845
3410
  /** Stroke width. */
@@ -2896,10 +3461,14 @@ interface TextMarkLayout {
2896
3461
  type: 'textMark';
2897
3462
  /** Stable identity key for data-update transitions. */
2898
3463
  key?: string;
2899
- /** X position. */
3464
+ /** X position, including any `dx` offset. */
2900
3465
  x: number;
2901
- /** Y position. */
3466
+ /** Y position, including any `dy` offset. */
2902
3467
  y: number;
3468
+ /** Data-space anchor before `dx` was applied. Set only when the mark is offset. */
3469
+ anchorX?: number;
3470
+ /** Data-space anchor before `dy` was applied. Set only when the mark is offset. */
3471
+ anchorY?: number;
2903
3472
  /** Text content to display. */
2904
3473
  text: string;
2905
3474
  /** Fill color. */
@@ -2912,6 +3481,11 @@ interface TextMarkLayout {
2912
3481
  fontFamily?: string;
2913
3482
  /** Horizontal text alignment. */
2914
3483
  textAnchor: 'start' | 'middle' | 'end';
3484
+ /**
3485
+ * Vertical baseline. Omit for the SVG default (alphabetic), which the calendar
3486
+ * and parliament marks rely on — they hand-compute baseline-relative offsets.
3487
+ */
3488
+ dominantBaseline?: 'auto' | 'hanging' | 'central' | 'text-after-edge';
2915
3489
  /** Rotation angle in degrees. */
2916
3490
  angle?: number;
2917
3491
  /** Original data row. */
@@ -3020,6 +3594,13 @@ interface ResolvedLabel {
3020
3594
  stroke: string;
3021
3595
  /** Connector style: straight line, curved arrow, or vertical drop-line through the data point. */
3022
3596
  style: 'straight' | 'curve' | 'drop-line';
3597
+ /** Whether to draw an arrowhead at the data-point end. */
3598
+ arrow: boolean;
3599
+ /**
3600
+ * Which edge of the label block the connector leaves from. Renderers use it
3601
+ * to shape the curve's control point. Internal; not part of the spec.
3602
+ */
3603
+ exit?: 'horizontal' | 'vertical';
3023
3604
  };
3024
3605
  /** Background color behind the label text. */
3025
3606
  background?: string;
@@ -3036,6 +3617,16 @@ interface ResolvedAnnotation {
3036
3617
  type: 'text' | 'range' | 'refline';
3037
3618
  /** Stable identifier from the spec annotation, for selection/edit callbacks. */
3038
3619
  id?: string;
3620
+ /**
3621
+ * Index of the originating annotation in `spec.annotations`.
3622
+ *
3623
+ * The resolved array is a *filtered* view of the spec array -- an annotation
3624
+ * whose position falls outside the scale domain resolves to nothing and is
3625
+ * dropped -- so a resolved array index is NOT a spec index. Anything that has
3626
+ * to reach back to the authored annotation (thinning's priority/responsive
3627
+ * lookup, faceted footnote numbering) must key on this.
3628
+ */
3629
+ specIndex?: number;
3039
3630
  /** Label text (if any). */
3040
3631
  label?: ResolvedLabel;
3041
3632
  /** For range: the highlighted rectangle in pixel coordinates. */
@@ -3058,8 +3649,9 @@ interface ResolvedAnnotation {
3058
3649
  /** Z-index for render ordering. Higher values render on top. */
3059
3650
  zIndex?: number;
3060
3651
  /**
3061
- * For text annotations: optional dot marker drawn at the connector's
3062
- * data-point endpoint. Coordinates match `label.connector.to` exactly.
3652
+ * For text annotations: the endpoint marker drawn at the data point.
3653
+ * Coordinates sit on the data point itself (plus any `connectorOffset.to`),
3654
+ * not on the pulled-back `label.connector.to`.
3063
3655
  */
3064
3656
  dot?: {
3065
3657
  x: number;
@@ -3108,8 +3700,21 @@ interface LegendEntry {
3108
3700
  /** True for overflow indicator entries ("+N more"). Not interactive. */
3109
3701
  overflow?: boolean;
3110
3702
  }
3703
+ /** The encoding channel a legend keys. */
3704
+ type LegendChannel = 'color' | 'size';
3111
3705
  /** Base legend layout fields shared by all legend types. */
3112
3706
  interface BaseLegendLayout {
3707
+ /**
3708
+ * Which encoding channel this legend keys. Omitted means `'color'` (every
3709
+ * legend was a color legend before size legends existed).
3710
+ *
3711
+ * This is the identity that makes plural legends safe: click-to-toggle-series
3712
+ * must fire for color entries and never for size circles, and selection/drag
3713
+ * must be able to tell two legends apart. Without it, `.oc-legend` and
3714
+ * `data-legend-index` are ambiguous across legends and the DOM queries that
3715
+ * read them silently pick whichever comes first.
3716
+ */
3717
+ channel?: LegendChannel;
3113
3718
  /** Where the legend is positioned relative to the chart area. */
3114
3719
  position: 'top' | 'right' | 'bottom' | 'bottom-right' | 'inline';
3115
3720
  /** Bounding box for the legend (pixel coordinates). */
@@ -3165,8 +3770,94 @@ interface GradientLegendLayout extends BaseLegendLayout {
3165
3770
  /** Formatted maximum value label. */
3166
3771
  maxLabel: string;
3167
3772
  }
3168
- /** Resolved legend layout — either categorical (swatches) or gradient (continuous bar). */
3169
- type LegendLayout = CategoricalLegendLayout | GradientLegendLayout;
3773
+ /** A positioned value label on a continuous/binned color legend. */
3774
+ interface ContinuousLegendTick {
3775
+ /** The data value this label marks. */
3776
+ value: number;
3777
+ /** Formatted label text (channel format applied). */
3778
+ label: string;
3779
+ /** Label x position (pixel coordinates). */
3780
+ x: number;
3781
+ /** Horizontal text anchor for the label. */
3782
+ anchor: 'start' | 'middle' | 'end';
3783
+ }
3784
+ /** A discrete class swatch in a binned continuous legend. */
3785
+ interface ContinuousLegendBin {
3786
+ /** Swatch x position (pixel coordinates). */
3787
+ x: number;
3788
+ /** Swatch width in pixels. */
3789
+ width: number;
3790
+ /** Fill color. */
3791
+ color: string;
3792
+ }
3793
+ /**
3794
+ * Continuous color legend for quantitative color scales on charts.
3795
+ *
3796
+ * Two rendering modes, resolved from the color channel's scale type:
3797
+ * - `gradient`: a single gradient-filled bar with min/max labels (plus a
3798
+ * midpoint label at the neutral value for diverging ramps).
3799
+ * - `binned`: a contiguous swatch row (quantile/quantize/threshold scales)
3800
+ * with boundary value labels between swatches at the class breaks.
3801
+ */
3802
+ interface ContinuousLegendLayout extends BaseLegendLayout {
3803
+ /** Discriminant for legend type. */
3804
+ type: 'continuous';
3805
+ /** Rendering mode: gradient bar or binned swatch row. */
3806
+ mode: 'gradient' | 'binned';
3807
+ /** The color bar rectangle (pixel coordinates). */
3808
+ bar: Rect;
3809
+ /** Gradient color stops (empty in binned mode). */
3810
+ colorStops: GradientColorStop[];
3811
+ /** Class swatches, left to right (empty in gradient mode). */
3812
+ bins: ContinuousLegendBin[];
3813
+ /** Value labels rendered below the bar. */
3814
+ ticks: ContinuousLegendTick[];
3815
+ /** Text baseline y for the value labels (pixel coordinates). */
3816
+ labelY: number;
3817
+ }
3818
+ /**
3819
+ * One graduated circle in a size legend: a representative value from the size
3820
+ * scale's domain, drawn at the radius that value actually resolves to.
3821
+ */
3822
+ interface SizeLegendCircle {
3823
+ /** The data value this circle stands for. */
3824
+ value: number;
3825
+ /** Formatted label text. */
3826
+ label: string;
3827
+ /** Radius in pixels -- the *same* scale the marks use, never a re-derivation. */
3828
+ radius: number;
3829
+ /** Center x, relative to the legend bounds. */
3830
+ cx: number;
3831
+ /** Center y, relative to the legend bounds. Circles sit on a common baseline. */
3832
+ cy: number;
3833
+ /** Label baseline y, relative to the legend bounds. */
3834
+ labelY: number;
3835
+ }
3836
+ /**
3837
+ * Size legend: graduated circles keying a quantitative `size` encoding.
3838
+ *
3839
+ * The circles are drawn nested (concentric, sharing a bottom edge) rather than
3840
+ * side by side -- the newsroom convention, and it makes the *ratio* between
3841
+ * magnitudes legible instead of just their order.
3842
+ *
3843
+ * The keyed values come from the scale's DOMAIN, not the data extent. The size
3844
+ * scale clamps, so with an explicit `scale.domain` every datum past `domain[1]`
3845
+ * renders at max radius; keying the data extent would imply the biggest circle
3846
+ * equals the biggest datum when it may not.
3847
+ */
3848
+ interface SizeLegendLayout extends BaseLegendLayout {
3849
+ /** Discriminant for legend type. */
3850
+ type: 'size';
3851
+ /** Graduated circles, smallest first. */
3852
+ circles: SizeLegendCircle[];
3853
+ /** Stroke color for the circle outlines. */
3854
+ stroke: string;
3855
+ }
3856
+ /**
3857
+ * Resolved legend layout: categorical (swatches), gradient (tilemap bar),
3858
+ * continuous (chart color bar), or size (graduated circles).
3859
+ */
3860
+ type LegendLayout = CategoricalLegendLayout | GradientLegendLayout | ContinuousLegendLayout | SizeLegendLayout;
3170
3861
  /** A single resolved endpoint-label entry, fully positioned. */
3171
3862
  interface EndpointLabelEntry {
3172
3863
  /** Series identifier (matches mark.seriesKey). */
@@ -3241,7 +3932,7 @@ interface TooltipContent {
3241
3932
  }
3242
3933
  /** Accessibility metadata for the entire visualization. */
3243
3934
  interface A11yMetadata {
3244
- /** Generated alt text describing the visualization. */
3935
+ /** Alt text describing the visualization: the author's `a11y.description` / `description` when set, auto-generated otherwise. */
3245
3936
  altText: string;
3246
3937
  /** Tabular data fallback for screen readers. Each inner array is a row. */
3247
3938
  dataTableFallback: unknown[][];
@@ -3249,6 +3940,13 @@ interface A11yMetadata {
3249
3940
  role: string;
3250
3941
  /** Whether the visualization is keyboard-navigable. */
3251
3942
  keyboardNavigable: boolean;
3943
+ /**
3944
+ * Hide the visualization from assistive technology (`aria-hidden="true"`,
3945
+ * no screen-reader table, no keyboard mark navigation). Optional because
3946
+ * only chart compilation sets it, and only on author opt-out via
3947
+ * `a11y.hidden`.
3948
+ */
3949
+ hidden?: boolean;
3252
3950
  }
3253
3951
  /** One fully-resolved animation phase. */
3254
3952
  interface ResolvedAnimationPhase {
@@ -3302,6 +4000,78 @@ interface ResolvedMetricBar {
3302
4000
  /** Cells laid out evenly across the chart width. */
3303
4001
  cells: ResolvedMetricCell[];
3304
4002
  }
4003
+ /**
4004
+ * Reserved band for the series search input (`seriesSearch`). The band is
4005
+ * empty SVG space between the chrome/metrics block and the top legend; the
4006
+ * vanilla adapter overlays an absolutely-positioned DOM combobox aligned to
4007
+ * this rect, so mounting it never changes the container's size.
4008
+ */
4009
+ interface ResolvedSeriesSearch {
4010
+ /** Band left x in layout coordinates. */
4011
+ x: number;
4012
+ /** Band top y in layout coordinates. */
4013
+ y: number;
4014
+ /** Band width. */
4015
+ width: number;
4016
+ /** Input row height (the reservation adds a gap below this). */
4017
+ height: number;
4018
+ /** Resolved input placeholder text. */
4019
+ placeholder: string;
4020
+ /** Distinct color-channel values available for search, in data order. */
4021
+ values: string[];
4022
+ }
4023
+ /**
4024
+ * One x-sample the reader's drawing snaps to: a pixel x position paired with
4025
+ * its data-x value. `xValue` is the value reported by `onReveal`; it falls
4026
+ * back to the pixel x when the data value can't be recovered.
4027
+ */
4028
+ interface YouDrawItSample {
4029
+ /** Pixel x position within the chart area. */
4030
+ px: number;
4031
+ /** Data-coordinate x value at this sample (from the target line's data). */
4032
+ xValue: string | number;
4033
+ }
4034
+ /**
4035
+ * Linear y pixel-to-data anchors, so the vanilla layer can map a drawn pixel
4036
+ * y back to a data value for `onReveal` without holding the scale object.
4037
+ * Present only when the y scale is invertible (continuous scales).
4038
+ */
4039
+ interface YouDrawItYInvert {
4040
+ /** Top pixel of the drawing area. */
4041
+ topPixel: number;
4042
+ /** Bottom pixel of the drawing area. */
4043
+ bottomPixel: number;
4044
+ /** Data value at the top pixel. */
4045
+ topData: number;
4046
+ /** Data value at the bottom pixel. */
4047
+ bottomData: number;
4048
+ }
4049
+ /**
4050
+ * Resolved geometry and config for the "you draw it" interactive format
4051
+ * (`youDrawIt`). The vanilla adapter uses this to render the hatched drawing
4052
+ * region, capture pointer input, and mask/reveal the real line. Present only
4053
+ * when the spec enables `youDrawIt` on a valid single-series line chart.
4054
+ */
4055
+ interface ResolvedYouDrawIt {
4056
+ /** Pixel x position of `from` within the chart area (drawing starts here). */
4057
+ fromX: number;
4058
+ /** The chart drawing area, for clamping pointer input and sizing the hatch region. */
4059
+ area: Rect;
4060
+ /** X samples (pixel + data value) at or after `from`, ascending by pixel x. Drawing snaps to these. */
4061
+ samples: YouDrawItSample[];
4062
+ /** Resolved prompt text for the hatched region. */
4063
+ prompt: string;
4064
+ /** Resolved skip-to-reveal button label. */
4065
+ revealLabel: string;
4066
+ /** Stroke color of the target line (the reader's guess uses a distinct pen style, but shares the palette for continuity). */
4067
+ lineColor: string;
4068
+ /** seriesKey of the target line mark, so the vanilla layer can find its DOM element. Undefined for a single-series line with no color encoding. */
4069
+ targetSeriesKey?: string;
4070
+ /** Pixel-to-data y anchors for reporting the guess in data coordinates. Absent when the y scale isn't invertible. */
4071
+ yInvert?: YouDrawItYInvert;
4072
+ /** Optional comparison line, resolved to pixel points parallel to the target line's points. */
4073
+ comparisonPoints?: Point[];
4074
+ }
3305
4075
  /**
3306
4076
  * A single panel in a faceted (small-multiples) layout.
3307
4077
  * Each panel has its own marks, axes, and drawing area but typically
@@ -3356,6 +4126,10 @@ interface ChartLayout {
3356
4126
  chrome: ResolvedChrome;
3357
4127
  /** Resolved KPI metric bar. Present only when spec.metrics is supplied and fits. */
3358
4128
  metrics?: ResolvedMetricBar;
4129
+ /** Reserved series-search band. Present only when spec.seriesSearch is enabled. */
4130
+ seriesSearch?: ResolvedSeriesSearch;
4131
+ /** Resolved "you draw it" config. Present only when spec.youDrawIt is enabled and valid. */
4132
+ youDrawIt?: ResolvedYouDrawIt;
3359
4133
  /** Resolved axis layouts. */
3360
4134
  axes: {
3361
4135
  x?: AxisLayout;
@@ -3367,8 +4141,22 @@ interface ChartLayout {
3367
4141
  marks: Mark[];
3368
4142
  /** Resolved annotations with pixel positions. */
3369
4143
  annotations: ResolvedAnnotation[];
3370
- /** Legend layout (position, entries, bounds). */
4144
+ /**
4145
+ * Primary legend (the color legend). Kept as a singular slot: it is what every
4146
+ * existing consumer reads, and a chart's legend is a color legend unless it
4147
+ * says otherwise.
4148
+ */
3371
4149
  legend: LegendLayout;
4150
+ /**
4151
+ * Every legend on the chart, in render order -- the color legend plus, when a
4152
+ * quantitative `size` channel is encoded, a size legend.
4153
+ *
4154
+ * `legend` is `legends[0]` when a color legend exists. Consumers that reserve
4155
+ * space or hit-test **must** iterate this array: a bubble chart keys both
4156
+ * continent (color) and population (size), and reserving margin for only the
4157
+ * first leaves the second drawing on top of the plot.
4158
+ */
4159
+ legends: LegendLayout[];
3372
4160
  /**
3373
4161
  * Right-side endpoint labels column for multi-series line/area charts.
3374
4162
  * Empty `entries` means the column is suppressed (single-series, opt-out, or
@@ -3806,6 +4594,111 @@ interface TileMapLayout {
3806
4594
  /** Text measurement function for the rendering adapter. */
3807
4595
  measureText: MeasureTextFn;
3808
4596
  }
4597
+ /** A structured compile warning with a code and context. */
4598
+ interface CompileWarning {
4599
+ code: string;
4600
+ message: string;
4601
+ context?: Record<string, unknown>;
4602
+ }
4603
+ /** Resolved focus target for map camera framing. */
4604
+ interface MapFocusLayout {
4605
+ target: {
4606
+ x: number;
4607
+ y: number;
4608
+ width: number;
4609
+ height: number;
4610
+ padding: number;
4611
+ };
4612
+ ids: Array<string | number>;
4613
+ }
4614
+ /** A resolved map feature mark with computed SVG path and fill. */
4615
+ interface MapFeatureMark {
4616
+ type: 'map-feature';
4617
+ /** SVG path `d` attribute for this feature's shape. */
4618
+ path: string;
4619
+ /** CSS fill color. */
4620
+ fill: string;
4621
+ /** CSS stroke color (border). */
4622
+ stroke: string;
4623
+ /** Stroke width in px. */
4624
+ strokeWidth: number;
4625
+ /** Feature ID (from TopoJSON). */
4626
+ id: string | number;
4627
+ /** Feature name (from properties, if available). */
4628
+ name?: string;
4629
+ /** Original joined data row (null if no data matched this feature). */
4630
+ data: Record<string, unknown> | null;
4631
+ /** Accessibility attributes. */
4632
+ aria: MarkAria;
4633
+ /** Index for stagger animation ordering. */
4634
+ animationIndex: number;
4635
+ /** Bounding box in map-local coordinates. */
4636
+ bounds: {
4637
+ x: number;
4638
+ y: number;
4639
+ width: number;
4640
+ height: number;
4641
+ };
4642
+ /** Centroid in map-local coordinates. */
4643
+ centroid: [number, number];
4644
+ }
4645
+ /** Border paths for the map (interior borders and coastline/outline). */
4646
+ interface MapBorders {
4647
+ /** SVG path `d` for interior borders between features. */
4648
+ interiorPath: string;
4649
+ /** SVG path `d` for the outer boundary / coastline. */
4650
+ outlinePath: string;
4651
+ /** Stroke color for interior borders. */
4652
+ interiorStroke: string;
4653
+ /** Stroke color for outer boundary. */
4654
+ outlineStroke: string;
4655
+ }
4656
+ /**
4657
+ * MapLayout: the complete engine output for map visualizations.
4658
+ *
4659
+ * Contains everything an adapter needs to render the map: dimensions,
4660
+ * chrome, feature paths, borders, legend, tooltip descriptors, and
4661
+ * accessibility metadata.
4662
+ */
4663
+ interface MapLayout {
4664
+ /** The map drawing area (after chrome and legend are subtracted). */
4665
+ area: Rect;
4666
+ /** Resolved chrome text elements with positions and styles. */
4667
+ chrome: ResolvedChrome;
4668
+ /** Resolved feature marks with paths, fills, and joined data. */
4669
+ features: MapFeatureMark[];
4670
+ /** Border paths (interior mesh and outline). */
4671
+ borders: MapBorders;
4672
+ /** Continuous color legend for quantitative mode (null if hidden or categorical). */
4673
+ continuousLegend: ContinuousLegendLayout | null;
4674
+ /** Categorical swatch legend for nominal mode (null if hidden or quantitative). */
4675
+ categoricalLegend: CategoricalLegendLayout | null;
4676
+ /** Tooltip content descriptors keyed by feature ID. */
4677
+ tooltipDescriptors: Map<string, TooltipContent>;
4678
+ /** Accessibility metadata for the map. */
4679
+ a11y: A11yMetadata;
4680
+ /** Resolved theme. */
4681
+ theme: ResolvedTheme;
4682
+ /** Total SVG width in pixels. */
4683
+ width: number;
4684
+ /** Total SVG height in pixels. */
4685
+ height: number;
4686
+ /** Resolved animation config (undefined if animation is disabled). */
4687
+ animation: ResolvedAnimation | undefined;
4688
+ /** Whether to render the watermark. */
4689
+ watermark: boolean;
4690
+ /** Text measurement function for the rendering adapter. */
4691
+ measureText: MeasureTextFn;
4692
+ /** Structured compile warnings (unmatched keys, missing data, winding issues). */
4693
+ warnings: CompileWarning[];
4694
+ /** The (width, height) passed to the projection's fitSize -- camera fits against this, not the SVG viewBox. */
4695
+ mapSize: {
4696
+ width: number;
4697
+ height: number;
4698
+ };
4699
+ /** Resolved focus target for camera framing (null if no focus). */
4700
+ focus: MapFocusLayout | null;
4701
+ }
3809
4702
  /** A resolved bar list row with computed positions and visual properties. */
3810
4703
  interface BarListRowMark {
3811
4704
  type: 'barlist-row';
@@ -3907,6 +4800,24 @@ interface CompileOptions {
3907
4800
  darkMode?: boolean;
3908
4801
  /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
3909
4802
  watermark?: boolean;
4803
+ /**
4804
+ * Enable development-time diagnostics (WCAG contrast warnings via
4805
+ * `console.warn`). The engine is isomorphic, so it never sniffs
4806
+ * `process.env`; the host decides what "dev" means and passes this flag.
4807
+ * Warnings are advisory only and never throw. Defaults to false.
4808
+ */
4809
+ dev?: boolean;
4810
+ /**
4811
+ * Sink for advisory spec warnings (deprecations, did-you-mean suggestions,
4812
+ * unknown scheme names, overplotting/beeswarm budget notes, etc.). The engine
4813
+ * is isomorphic and side-effect-free by default: rather than writing to the
4814
+ * global `console`, it routes every advisory warning through this callback so
4815
+ * a host can collect, reroute, or silence them (e.g. suppress under SSR, fail
4816
+ * a test on any warning, surface them in a dev overlay). When omitted, the
4817
+ * engine defaults to `console.warn`. Never called with fatal errors, which are
4818
+ * thrown instead.
4819
+ */
4820
+ onWarn?: (message: string) => void;
3910
4821
  /**
3911
4822
  * Real text measurement function provided by the adapter.
3912
4823
  * Uses a hidden canvas or DOM element for accurate text dimensions.
@@ -3915,6 +4826,18 @@ interface CompileOptions {
3915
4826
  measureText?: MeasureTextFn;
3916
4827
  /** Extra pixels to reserve on the right margin for a secondary y-axis. Set by compileLayer when resolve.scale.y is 'independent'. */
3917
4828
  rightAxisReserve?: number;
4829
+ /**
4830
+ * Extra pixels to reserve below the plot for the auto-thinning footnote list.
4831
+ * Chicken-and-egg: thinning needs the chart area to decide what to demote, but
4832
+ * the footnotes it produces need space carved out of that same area. compileChart
4833
+ * resolves it by compiling once to learn the footnote count, then recompiling with
4834
+ * this set. Callers should not set it; it is internal to that second pass.
4835
+ */
4836
+ footnoteReserve?: number;
4837
+ /** Recursion depth of the footnote-reserve convergence pass. Internal to compileChart. */
4838
+ footnotePass?: number;
4839
+ /** Set after facet height growth to prevent unbounded recursion. Internal to compileFaceted. */
4840
+ facetHeightGrown?: boolean;
3918
4841
  /**
3919
4842
  * Use this chart drawing area instead of computing one from chrome, axes,
3920
4843
  * and legend reservations. Set by compileLayer when compiling leaf layers so
@@ -3945,8 +4868,8 @@ interface CompileTableOptions extends CompileOptions {
3945
4868
  * These are approximations suitable for checking palette accessibility,
3946
4869
  * not medical-grade simulations.
3947
4870
  */
3948
- /** The three common types of color vision deficiency. */
3949
- type ColorBlindnessType = 'protanopia' | 'deuteranopia' | 'tritanopia';
4871
+ /** The four common types of color vision deficiency. */
4872
+ type ColorBlindnessType = 'protanopia' | 'deuteranopia' | 'tritanopia' | 'achromatopsia';
3950
4873
  /**
3951
4874
  * Simulate how a color appears under a given color blindness type.
3952
4875
  * Returns a hex color string.
@@ -3961,6 +4884,13 @@ declare function simulateColorBlindness(color: string, type: ColorBlindnessType)
3961
4884
  */
3962
4885
  declare function checkPaletteDistinguishability(colors: string[], type: ColorBlindnessType, minDistance?: number): boolean;
3963
4886
 
4887
+ /**
4888
+ * Check whether a color string parses to a fully opaque color.
4889
+ * Contrast ratios are only meaningful against opaque backgrounds; the
4890
+ * default chart background ('transparent') defers to the host page and
4891
+ * can't be checked at compile time.
4892
+ */
4893
+ declare function isOpaqueColor(color: string): boolean;
3964
4894
  /**
3965
4895
  * Compute the WCAG contrast ratio between two colors.
3966
4896
  * Returns a value between 1 (identical) and 21 (black on white).
@@ -4062,6 +4992,15 @@ interface DivergingPalette {
4062
4992
  }
4063
4993
  /** All diverging palettes keyed by name. */
4064
4994
  declare const DIVERGING_PALETTES: Record<string, string[]>;
4995
+ /**
4996
+ * Resolve a scheme name to its palette stops. Accepts openchart palette names
4997
+ * (blue, green, orange, purple, teal, redBlue, brownTeal, categorical) and
4998
+ * common Vega-Lite aliases (blues, greens, ..., redblue, category10).
4999
+ * Case-insensitive. Returns undefined for unknown names.
5000
+ */
5001
+ declare function resolveSchemeName(name: string): string[] | undefined;
5002
+ /** Scheme names accepted by `scale: { scheme }`, for validation messages. */
5003
+ declare const SUPPORTED_SCHEME_NAMES: readonly string[];
4065
5004
 
4066
5005
  /**
4067
5006
  * Dark mode theme adaptation.
@@ -4175,6 +5114,23 @@ declare function resolveTheme(userTheme?: ThemeConfig, base?: Theme): ResolvedTh
4175
5114
  * chrome proportional to the container.
4176
5115
  */
4177
5116
 
5117
+ /**
5118
+ * Line-height multiplier for the auto-thinning footnote list.
5119
+ *
5120
+ * Exported because the chrome renderer steps each footnote line down by it
5121
+ * while the band height below is computed from it -- the two have to agree, or
5122
+ * the last line drifts outside the band that was reserved for it.
5123
+ */
5124
+ declare const FOOTNOTE_LINE_HEIGHT = 1.3;
5125
+ /**
5126
+ * Vertical space a footnote list of `count` lines occupies below the plot.
5127
+ *
5128
+ * Single source of truth, deliberately: the engine reserves this band in the
5129
+ * bottom margin, the chrome renderer shifts the source row down by it, and the
5130
+ * brand watermark shifts by it too. When those three disagree the footnotes
5131
+ * land on top of the watermark, which is exactly the bug this centralizes away.
5132
+ */
5133
+ declare function footnoteBandHeight(count: number, theme: ResolvedTheme): number;
4178
5134
  /**
4179
5135
  * Compute resolved chrome layout from a Chrome spec and resolved theme.
4180
5136
  *
@@ -4369,10 +5325,13 @@ declare const X_AXIS_BAND_HEIGHT = 26;
4369
5325
  declare const X_AXIS_TITLE_BAND = 22;
4370
5326
  declare const X_AXIS_TITLE_BAND_ROTATED = 20;
4371
5327
  /**
4372
- * Ceiling on the vertical extent reserved for rotated x tick labels. Labels
4373
- * whose rotated projection exceeds this clip at the reservation edge, so the
4374
- * angle ladder (engine layout/axes/rotation) avoids -90° for labels longer
4375
- * than this.
5328
+ * Ceiling on the vertical extent reserved for rotated x tick labels. The angle
5329
+ * ladder (engine layout/axes/rotation) avoids -90° for labels longer than this.
5330
+ *
5331
+ * The cap bounds the RESERVATION, so a label whose rotated projection exceeds it
5332
+ * must be truncated to fit — see `truncateRotatedLabel`. Reserving a capped band
5333
+ * while drawing the full string is what let long ticks spill past the axis and
5334
+ * collide with the source line.
4376
5335
  */
4377
5336
  declare const X_AXIS_ROTATED_EXTENT_CAP = 120;
4378
5337
  /**
@@ -4392,6 +5351,28 @@ interface XAxisExtentInput {
4392
5351
  measure?: (text: string, fontSize: number, fontWeight: number) => number;
4393
5352
  }
4394
5353
  declare function computeXAxisExtentFromLabels(input: XAxisExtentInput): number;
5354
+ /**
5355
+ * The widest a rotated tick label may be before its vertical projection exceeds
5356
+ * `X_AXIS_ROTATED_EXTENT_CAP`.
5357
+ *
5358
+ * Inverts the rotated-extent formula in `computeXAxisExtentFromLabels`:
5359
+ *
5360
+ * extent = width*sin(θ) + lineHeight*cos(θ) + gap
5361
+ * width = (cap - lineHeight*cos(θ) - gap) / sin(θ)
5362
+ *
5363
+ * Deriving it from the same terms (rather than hardcoding a second number) keeps
5364
+ * the truncation budget and the space reservation locked together — if one
5365
+ * changes, the other follows.
5366
+ */
5367
+ declare function maxRotatedLabelWidth(tickAngle: number, tickFontSize: number): number;
5368
+ /**
5369
+ * Truncate a rotated tick label with an ellipsis so it fits inside the capped
5370
+ * reservation. Returns the label unchanged when it already fits.
5371
+ *
5372
+ * Binary-search on character count against the real measure function, so it
5373
+ * respects proportional fonts rather than assuming a fixed character width.
5374
+ */
5375
+ declare function truncateRotatedLabel(label: string, tickAngle: number, tickFontSize: number, tickFontWeight: number, measure?: (text: string, fontSize: number, fontWeight?: number) => number): string;
4395
5376
 
4396
5377
  /**
4397
5378
  * Label collision detection and resolution.
@@ -4491,23 +5472,42 @@ declare function computeLabelBounds(label: ResolvedLabel): Rect;
4491
5472
  * @param locale - Locale string (currently unused, reserved for i18n).
4492
5473
  */
4493
5474
  declare function formatNumber(value: number, _locale?: string): string;
5475
+ declare function abbreviateNumber(value: number): string;
4494
5476
  /**
4495
- * Abbreviate a large number with a suffix.
4496
- *
4497
- * Examples:
4498
- * - 1500000 -> "1.5M"
4499
- * - 2300 -> "2.3K"
4500
- * - 42 -> "42"
4501
- * - 1000000000 -> "1B"
5477
+ * Format a number as an English ordinal: 1 -> "1st", 2 -> "2nd", 3 -> "3rd",
5478
+ * 11 -> "11th", 22 -> "22nd". Values are rounded to the nearest integer first;
5479
+ * intended for rank axes where the domain is integer positions.
4502
5480
  */
4503
- declare function abbreviateNumber(value: number): string;
5481
+ declare function formatOrdinal(value: number): string;
4504
5482
  /**
4505
5483
  * Build a number formatter from a d3-format string, with support for a
4506
5484
  * trailing literal suffix that d3-format itself would reject.
4507
5485
  *
5486
+ * Also accepts the OpenChart extension `'ordinal'`, which renders English
5487
+ * ordinals ("1st", "2nd", "3rd") for rank axes.
5488
+ *
4508
5489
  * Returns null if the format string is falsy or unparseable.
4509
5490
  */
4510
5491
  declare function buildD3Formatter(formatStr: string | undefined): ((v: number) => string) | null;
5492
+ type NumberFormatter = (value: number) => string;
5493
+ declare function formatPercent(value: number, options?: {
5494
+ fraction?: boolean;
5495
+ }): string;
5496
+ declare function formatCurrency(value: number, options?: {
5497
+ compact?: boolean;
5498
+ }): string;
5499
+ interface FieldFormatContext {
5500
+ extent?: [number, number];
5501
+ allIntegers?: boolean;
5502
+ surface?: 'chart' | 'table';
5503
+ step?: number;
5504
+ stepReference?: number;
5505
+ }
5506
+ declare function computeFieldFormatContext(values: Iterable<unknown>, surface?: 'chart' | 'table'): FieldFormatContext;
5507
+ declare function isYearContext(ctx: FieldFormatContext | undefined): boolean;
5508
+ declare function resolveNumberFormatter(formatStr: string | undefined, ctx?: FieldFormatContext): NumberFormatter | null;
5509
+ declare function defaultNumberFormatter(ctx?: FieldFormatContext): NumberFormatter;
5510
+ declare function buildCompactStepFormatter(step: number): NumberFormatter;
4511
5511
  /** Granularity levels for date formatting. */
4512
5512
  type DateGranularity = 'year' | 'quarter' | 'month' | 'week' | 'day' | 'hour' | 'minute';
4513
5513
  /**
@@ -4673,12 +5673,12 @@ declare function columnChart(data: DataRow[], x: FieldRef, y: FieldRef, options?
4673
5673
  /**
4674
5674
  * Create a pie chart spec.
4675
5675
  *
4676
- * Convention: category maps to the color channel, value maps to y.
5676
+ * Convention: category maps to the color channel, value maps to theta.
4677
5677
  * Pie charts have no x-axis.
4678
5678
  *
4679
5679
  * @param data - Array of data rows.
4680
5680
  * @param category - Category field (color channel, nominal).
4681
- * @param value - Value field (y channel, quantitative).
5681
+ * @param value - Value field (theta channel, quantitative).
4682
5682
  * @param options - Optional chrome, annotations, theme, etc.
4683
5683
  * Note: color option is ignored since category is used for color.
4684
5684
  */
@@ -4695,12 +5695,12 @@ declare function areaChart(data: DataRow[], x: FieldRef, y: FieldRef, options?:
4695
5695
  /**
4696
5696
  * Create a donut chart spec.
4697
5697
  *
4698
- * Convention: category maps to the color channel, value maps to y.
5698
+ * Convention: category maps to the color channel, value maps to theta.
4699
5699
  * Donut charts have no x-axis.
4700
5700
  *
4701
5701
  * @param data - Array of data rows.
4702
5702
  * @param category - Category field (color channel, nominal).
4703
- * @param value - Value field (y channel, quantitative).
5703
+ * @param value - Value field (theta channel, quantitative).
4704
5704
  * @param options - Optional chrome, annotations, theme, etc.
4705
5705
  * Note: color option is ignored since category is used for color.
4706
5706
  */
@@ -4771,4 +5771,39 @@ interface TileMapBuilderOptions {
4771
5771
  */
4772
5772
  declare function tileMap(data: Record<string, number | string | null> | DataRow[], options?: TileMapBuilderOptions): TileMapSpec;
4773
5773
 
4774
- export { type A11yMetadata, ACHROMATIC_RAMP, AXIS_TITLE_GAP, AXIS_TITLE_OFFSET_COMPACT, AXIS_TITLE_OFFSET_DEFAULT, AXIS_TITLE_TRAILING_PAD, type AggregateOp, type AggregateTransform, type AnimationConfig, type AnimationEase, type AnimationPhaseConfig, type AnimationSpec, type AnimationStagger, type Annotation, type AnnotationAnchor, type AnnotationFootnote, type AnnotationOffset, type AnnotationPosition, type ArcEncoding, type ArcMark, type AreaEncoding, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, BRAND_FONT_SIZE, BRAND_MIN_WIDTH, BRAND_RESERVE_WIDTH, BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX, type BarColumnConfig, type BarEncoding, type BarListEncoding, type BarListLayout, type BarListRowMark, type BarListSpec, type BarListSpecWithoutData, type BarTableCell, type BaseLegendLayout, type BinParams, type BinTransform, type Breakpoint, CATEGORICAL_PALETTE, CHART_ENCODING_RULES, CHART_TYPES, COMPACT_WIDTH, type CalculateExpression, type CalculateTransform, type CategoricalLegendLayout, type CategoricalPalette, type CategoryColorsConfig, type CategoryTableCell, type CellStyle, type ChannelRule, type ChartBuilderOptions, type ChartEventHandlers, type ChartLayout, type ChartSpec, type ChartSpecOverride, type ChartSpecWithoutData, type ChartType, type Chrome, type ChromeDefaults, type ChromeKey, type ChromeMode, type ChromeText, type ChromeTextStyle, type ChromeThemeOverride, type CircleEncoding, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, type Condition, type ConditionalValueDef, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type Display, type DivergingPalette, EXTENDED_OFFSET_STRATEGIES, type ElementEdit, type ElementRef, type Encoding, type EncodingChannel, type EncodingRule, type EndpointLabelEntry, type EndpointLabelsConfig, type EndpointLabelsLayout, type FacetChannel, type FacetLayout, type FacetPanelLayout, type FieldPredicate, type FieldRef, type FieldType, type FilterPredicate, type FilterTransform, type FlagTableCell, type FoldTransform, GRAPH_ENCODING_RULES, type GradientColorStop, type GradientDef, type GradientLegendLayout, type GradientStop, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, HEIGHT_CRAMPED_MAX, HEIGHT_SHORT_MAX, HPAD_COMPACT_FRACTION, HPAD_COMPACT_MIN, type HeatmapColumnConfig, type HeatmapTableCell, type HeightClass, type ImageColumnConfig, type ImageTableCell, LABEL_GAP_COMPACT, LABEL_GAP_DEFAULT, LABEL_GAP_NARROW_MAX, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LabelSpec, type LayerSpec, type LayoutStrategy, type LegendConfig, type LegendEntry, type LegendEntryPosition, type LegendLayout, type LegendPosition, type LineEncoding, type LineMark, type LinearGradient, type LogicalAnd, type LogicalNot, type LogicalOr, type LollipopEncoding, MARK_DISPLAY_NAMES, MARK_ENCODING_RULES, MARK_TYPES, MAX_LEFT_LABEL_FRACTION_COMPACT, MAX_LEFT_LABEL_FRACTION_DEFAULT, MAX_LEFT_LABEL_FRACTION_MEDIUM, MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX, type Margins, type Mark, type MarkAria, type MarkDef, type MarkEvent, type MarkType, type MeasureTextFn, type Metric, NARROW_VIEWPORT_MAX, type NodeOverride, OFFSET_STRATEGIES, type OffsetStrategy, type PaginationState, type Point, type PointEncoding, type PointMark, type RadialGradient, type RangeAnnotation, type Rect, type RectEncoding, type RectMark, type RefLineAnnotation, type RelativeTimeRef, type ResolveConfig, type ResolveMode, type ResolvedAnimation, type ResolvedAnimationPhase, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedLabel, type ResolvedMetricBar, type ResolvedMetricCell, type ResolvedTheme, type ResponsiveConfig, type RuleMarkLayout, SEQUENTIAL_PALETTES, type SankeyEncoding, type SankeyLayout, type SankeyLinkColor, type SankeyLinkMark, type SankeyNodeAlign, type SankeyNodeMark, type SankeySpec, type SankeySpecWithoutData, type ScaleConfig, type ScaleType, type SequentialPalette, type SeriesStrategy, type SeriesStyle, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, TICK_LABEL_OFFSET, TICK_LINE_HEIGHT_FACTOR, TOP_PAD_EXTRA_NARROW, TOP_PAD_NARROW_MAX, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextEncoding, type TextMarkLayout, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TickEncoding, type TickMarkLayout, type TileMapBuilderOptions, type TileMapEncoding, type TileMapLayout, type TileMapPalette, type TileMapSpec, type TileMapSpecWithoutData, type TileMapTileMark, type TimeUnit, type TimeUnitTransform, type TokenValue, type TooltipContent, type TooltipField, type Transform, type VizSpec, type WindowFieldDef, type WindowOp, type WindowSortField, type WindowTransform, type XAxisExtentInput, X_AXIS_BAND_HEIGHT, X_AXIS_ROTATED_EXTENT_CAP, X_AXIS_TITLE_BAND, X_AXIS_TITLE_BAND_ROTATED, abbreviateNumber, adaptColorForDarkMode, adaptForLightLineStroke, adaptTheme, areaChart, axisTitleOffset, barChart, buildD3Formatter, buildTemporalFormatter, checkPaletteDistinguishability, columnChart, computeChrome, computeLabelBounds, computeXAxisExtentFromLabels, contrastRatio, dataTable, detectCollision, donutChart, dotChart, editorial, elementRef, essay, estimateTextWidth, findAccessibleColor, formatDate, formatNumber, generateAltText, generateAriaLabels, generateDataTable, getAxisTitleOffset, getBreakpoint, getHeightClass, getLayoutStrategy, getRepresentativeColor, heuristicMeasure, inferFieldType, isBarListSpec, isChartSpec, isConditionalDef, isEncodingChannel, isGradientDef, isGraphSpec, isLayerSpec, isRangeAnnotation, isRefLineAnnotation, isSankeySpec, isTableSpec, isTextAnnotation, isTileMapSpec, lineChart, meetsAA, overlapArea, pickLabelColor, pieChart, resolveCollisions, resolveMarkDef, resolveMarkType, resolveMeasurer, resolveTheme, scatterChart, simulateColorBlindness, textAscent, tileMap, wire, wrapText };
5774
+ /**
5775
+ * Deep merge for applying partial patches onto a spec-shaped object.
5776
+ *
5777
+ * Used by the scrollytelling story layer (`vanilla/story`) to apply
5778
+ * cumulative deep-partial spec patches onto a base spec. Arrays REPLACE
5779
+ * rather than merge element-by-element: predictable behavior for things
5780
+ * like `annotations` or `data`, where index-wise merging would silently
5781
+ * produce nonsense. Plain objects merge key-by-key, recursively.
5782
+ */
5783
+ declare function deepMergeSpec(target: any, source: any): any;
5784
+
5785
+ interface CssTokenDef {
5786
+ name: string;
5787
+ light: string;
5788
+ dark?: string;
5789
+ comment?: string;
5790
+ consumers?: string;
5791
+ section?: string;
5792
+ }
5793
+ declare const CSS_TOKEN_ROOT_SELECTORS: readonly string[];
5794
+ /**
5795
+ * Text levels. Names invert across modes: in light mode "muted" sits at
5796
+ * a lighter zinc step than "subtle" because both are picked relative to
5797
+ * the active background, not from a fixed lightness ladder. The intent
5798
+ * is "muted = first step away from primary text"; "subtle = next step
5799
+ * down"; etc.
5800
+ *
5801
+ * Light mode: text=zinc-950, secondary=zinc-700, muted=zinc-500,
5802
+ * subtle=zinc-400, faint=zinc-300
5803
+ * Dark mode (dark.css): inverts the surface tokens but keeps the same
5804
+ * muted -> subtle -> faint progression away from primary.
5805
+ */
5806
+ declare const CSS_TOKENS: readonly CssTokenDef[];
5807
+ declare function cssTokenDefault(name: string, mode: 'light' | 'dark'): string;
5808
+
5809
+ export { type A11yConfig, type A11yMetadata, ACHROMATIC_RAMP, AXIS_TITLE_GAP, AXIS_TITLE_OFFSET_COMPACT, AXIS_TITLE_OFFSET_DEFAULT, AXIS_TITLE_TRAILING_PAD, type AggregateOp, type AggregateTransform, type AnimationConfig, type AnimationEase, type AnimationPhaseConfig, type AnimationSpec, type AnimationStagger, type Annotation, type AnnotationAnchor, type AnnotationFootnote, type AnnotationOffset, type AnnotationPosition, type ArcEncoding, type ArcMark, type AreaEncoding, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, BRAND_FONT_SIZE, BRAND_MIN_WIDTH, BRAND_RESERVE_WIDTH, BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX, type BarColumnConfig, type BarEncoding, type BarListEncoding, type BarListLayout, type BarListRowMark, type BarListSpec, type BarListSpecWithoutData, type BarTableCell, type BaseLegendLayout, type BeeswarmEncoding, type BinParams, type BinTransform, type Breakpoint, CATEGORICAL_PALETTE, COMPACT_WIDTH, CSS_TOKENS, CSS_TOKEN_ROOT_SELECTORS, type CalculateExpression, type CalculateTransform, type CalendarEncoding, type CategoricalLegendLayout, type CategoricalPalette, type CategoryColorsConfig, type CategoryTableCell, type CellStyle, type ChannelRule, type ChartBuilderOptions, type ChartEventHandlers, type ChartLayout, type ChartSpec, type ChartSpecOverride, type ChartSpecWithoutData, type Chrome, type ChromeDefaults, type ChromeKey, type ChromeMode, type ChromeText, type ChromeTextStyle, type ChromeThemeOverride, type CircleEncoding, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, type CompileWarning, type Condition, type ConditionalValueDef, type ConnectorConfig, type ConnectorType, type ContinuousLegendBin, type ContinuousLegendLayout, type ContinuousLegendTick, type CssTokenDef, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type Display, type DivergingPalette, EXTENDED_OFFSET_STRATEGIES, type ElementEdit, type ElementRef, type Encoding, type EncodingChannel, type EncodingRule, type EndpointLabelContent, type EndpointLabelEntry, type EndpointLabelsConfig, type EndpointLabelsLayout, FOOTNOTE_LINE_HEIGHT, type FacetChannel, type FacetLayout, type FacetPanelLayout, type FieldFormatContext, type FieldPredicate, type FieldRef, type FieldType, type FillPatternType, type FilterPredicate, type FilterTransform, type FlagTableCell, type FoldTransform, GRAPH_ENCODING_RULES, type GradientColorStop, type GradientDef, type GradientLegendLayout, type GradientStop, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, HEIGHT_CRAMPED_MAX, HEIGHT_SHORT_MAX, HPAD_COMPACT_FRACTION, HPAD_COMPACT_MIN, type HeatmapColumnConfig, type HeatmapTableCell, type HeightClass, type ImageColumnConfig, type ImageTableCell, LABEL_GAP_COMPACT, LABEL_GAP_DEFAULT, LABEL_GAP_NARROW_MAX, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LabelSpec, type LayerSpec, type LayoutStrategy, type LegendChannel, type LegendConfig, type LegendEntry, type LegendEntryPosition, type LegendLayout, type LegendPosition, type LineEncoding, type LineMark, type LinearGradient, type LogicalAnd, type LogicalNot, type LogicalOr, type LollipopEncoding, MARK_DISPLAY_NAMES, MARK_ENCODING_RULES, MARK_TYPES, MAX_LEFT_LABEL_FRACTION_COMPACT, MAX_LEFT_LABEL_FRACTION_DEFAULT, MAX_LEFT_LABEL_FRACTION_MEDIUM, MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX, type MapBorders, type MapEncoding, type MapFeatureMark, type MapFocus, type MapFocusLayout, type MapGeo, type MapLayout, type MapProjection, type MapSpec, type MapSpecWithoutData, type Margins, type Mark, type MarkAria, type MarkDef, type MarkEvent, type MarkType, type MeasureTextFn, type Metric, NARROW_VIEWPORT_MAX, type NodeOverride, type NumberFormatter, OFFSET_STRATEGIES, type OffsetStrategy, type PaginationState, type ParliamentEncoding, type Point, type PointEncoding, type PointMark, type RadialGradient, type RangeAnnotation, type RangeEncoding, type Rect, type RectEncoding, type RectMark, type RefLineAnnotation, type RelativeTimeRef, type ResolveConfig, type ResolveMode, type ResolvedAnimation, type ResolvedAnimationPhase, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedFillPattern, type ResolvedLabel, type ResolvedMetricBar, type ResolvedMetricCell, type ResolvedSeriesSearch, type ResolvedTheme, type ResolvedYouDrawIt, type ResponsiveConfig, type RuleMarkLayout, SEQUENTIAL_PALETTES, SUPPORTED_SCHEME_NAMES, type SankeyEncoding, type SankeyLayout, type SankeyLinkColor, type SankeyLinkMark, type SankeyNodeAlign, type SankeyNodeMark, type SankeySpec, type SankeySpecWithoutData, type ScaleConfig, type ScaleType, type SequentialPalette, type SeriesSearchConfig, type SeriesStrategy, type SeriesStyle, type SizeLegendCircle, type SizeLegendLayout, type SortByField, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, TICK_LABEL_OFFSET, TICK_LINE_HEIGHT_FACTOR, TOP_PAD_EXTRA_NARROW, TOP_PAD_NARROW_MAX, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextEncoding, type TextMarkLayout, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TickEncoding, type TickMarkLayout, type TileMapBuilderOptions, type TileMapEncoding, type TileMapLayout, type TileMapPalette, type TileMapSpec, type TileMapSpecWithoutData, type TileMapTileMark, type TimeUnit, type TimeUnitTransform, type TokenValue, type TooltipContent, type TooltipField, type Transform, type ValueDef, type VizSpec, type WaffleEncoding, type WindowFieldDef, type WindowOp, type WindowSortField, type WindowTransform, type XAxisExtentInput, X_AXIS_BAND_HEIGHT, X_AXIS_ROTATED_EXTENT_CAP, X_AXIS_TITLE_BAND, X_AXIS_TITLE_BAND_ROTATED, type YouDrawItConfig, type YouDrawItSample, type YouDrawItYInvert, abbreviateNumber, adaptColorForDarkMode, adaptForLightLineStroke, adaptTheme, areaChart, axisTitleOffset, barChart, buildCompactStepFormatter, buildD3Formatter, buildTemporalFormatter, checkPaletteDistinguishability, columnChart, computeChrome, computeFieldFormatContext, computeLabelBounds, computeXAxisExtentFromLabels, contrastRatio, cssTokenDefault, dataTable, deepMergeSpec, defaultNumberFormatter, detectCollision, donutChart, dotChart, editorial, elementRef, essay, estimateTextWidth, findAccessibleColor, footnoteBandHeight, formatCurrency, formatDate, formatNumber, formatOrdinal, formatPercent, generateAltText, generateAriaLabels, generateDataTable, getAxisTitleOffset, getBreakpoint, getHeightClass, getLayoutStrategy, getRepresentativeColor, heuristicMeasure, inferFieldType, isAxislessMark, isBarListSpec, isChartSpec, isConditionalDef, isEncodingChannel, isGradientDef, isGraphSpec, isLayerSpec, isMapSpec, isOpaqueColor, isRangeAnnotation, isRefLineAnnotation, isSankeySpec, isTableSpec, isTextAnnotation, isTileMapSpec, isValueDef, isYearContext, lineChart, maxRotatedLabelWidth, meetsAA, overlapArea, pickLabelColor, pieChart, resolveCollisions, resolveMarkDef, resolveMarkType, resolveMeasurer, resolveNumberFormatter, resolveSchemeName, resolveTheme, scatterChart, simulateColorBlindness, textAscent, tileMap, truncateRotatedLabel, wire, wrapText };