@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
@@ -127,6 +127,12 @@ export interface AxisTick {
127
127
  position: number;
128
128
  /** Formatted label string for display. */
129
129
  label: string;
130
+ /**
131
+ * The untruncated label, set only when `label` was shortened with an ellipsis
132
+ * to fit the rotated-tick reservation. Renderers should surface this as the
133
+ * tick's accessible/tooltip text so the full category name is never lost.
134
+ */
135
+ fullLabel?: string;
130
136
  /** Secondary label text from axis.labelField. */
131
137
  subtitle?: string;
132
138
  }
@@ -190,6 +196,25 @@ export interface AxisLayout {
190
196
  // Marks (data visual elements)
191
197
  // ---------------------------------------------------------------------------
192
198
 
199
+ /** The four built-in fill pattern shapes for series reinforcement. */
200
+ export type FillPatternType = 'diagonal' | 'dot' | 'crosshatch' | 'vertical';
201
+
202
+ /**
203
+ * A resolved per-mark fill pattern (engine output for `mark.fillPattern: 'auto'`).
204
+ *
205
+ * The renderer builds one SVG `<pattern>` per unique (type, base, line)
206
+ * combination: a tile of `base` color overlaid with `line`-colored pattern
207
+ * geometry. Marks referencing the same resolved pattern share one def.
208
+ */
209
+ export interface ResolvedFillPattern {
210
+ /** Pattern shape assigned to this mark's series. */
211
+ type: FillPatternType;
212
+ /** Base tile color: the series' solid color. */
213
+ base: string;
214
+ /** Pattern line/dot color, contrast-picked against `base`. */
215
+ line: string;
216
+ }
217
+
193
218
  /** Accessibility attributes for a mark. */
194
219
  export interface MarkAria {
195
220
  /** ARIA label for the mark. Optional when `decorative: true` — decorative
@@ -277,6 +302,8 @@ export interface AreaMark {
277
302
  interpolate?: string;
278
303
  /** Fill color or gradient. */
279
304
  fill: string | GradientDef;
305
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
306
+ pattern?: ResolvedFillPattern;
280
307
  /** Fill opacity. */
281
308
  fillOpacity: number;
282
309
  /** Optional stroke for the top boundary. */
@@ -323,6 +350,8 @@ export interface RectMark {
323
350
  height: number;
324
351
  /** Fill color or gradient. */
325
352
  fill: string | GradientDef;
353
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
354
+ pattern?: ResolvedFillPattern;
326
355
  /** Stroke color. */
327
356
  stroke?: string;
328
357
  /** Stroke width. */
@@ -376,6 +405,8 @@ export interface ArcMark {
376
405
  endAngle: number;
377
406
  /** Fill color or gradient. */
378
407
  fill: string | GradientDef;
408
+ /** Resolved fill pattern. Present only when `mark.fillPattern: 'auto'` assigned one. */
409
+ pattern?: ResolvedFillPattern;
379
410
  /** Stroke color (usually white for slice separation). */
380
411
  stroke: string;
381
412
  /** Stroke width. */
@@ -434,10 +465,14 @@ export interface TextMarkLayout {
434
465
  type: 'textMark';
435
466
  /** Stable identity key for data-update transitions. */
436
467
  key?: string;
437
- /** X position. */
468
+ /** X position, including any `dx` offset. */
438
469
  x: number;
439
- /** Y position. */
470
+ /** Y position, including any `dy` offset. */
440
471
  y: number;
472
+ /** Data-space anchor before `dx` was applied. Set only when the mark is offset. */
473
+ anchorX?: number;
474
+ /** Data-space anchor before `dy` was applied. Set only when the mark is offset. */
475
+ anchorY?: number;
441
476
  /** Text content to display. */
442
477
  text: string;
443
478
  /** Fill color. */
@@ -450,6 +485,11 @@ export interface TextMarkLayout {
450
485
  fontFamily?: string;
451
486
  /** Horizontal text alignment. */
452
487
  textAnchor: 'start' | 'middle' | 'end';
488
+ /**
489
+ * Vertical baseline. Omit for the SVG default (alphabetic), which the calendar
490
+ * and parliament marks rely on — they hand-compute baseline-relative offsets.
491
+ */
492
+ dominantBaseline?: 'auto' | 'hanging' | 'central' | 'text-after-edge';
453
493
  /** Rotation angle in degrees. */
454
494
  angle?: number;
455
495
  /** Original data row. */
@@ -574,6 +614,13 @@ export interface ResolvedLabel {
574
614
  stroke: string;
575
615
  /** Connector style: straight line, curved arrow, or vertical drop-line through the data point. */
576
616
  style: 'straight' | 'curve' | 'drop-line';
617
+ /** Whether to draw an arrowhead at the data-point end. */
618
+ arrow: boolean;
619
+ /**
620
+ * Which edge of the label block the connector leaves from. Renderers use it
621
+ * to shape the curve's control point. Internal; not part of the spec.
622
+ */
623
+ exit?: 'horizontal' | 'vertical';
577
624
  };
578
625
  /** Background color behind the label text. */
579
626
  background?: string;
@@ -595,6 +642,16 @@ export interface ResolvedAnnotation {
595
642
  type: 'text' | 'range' | 'refline';
596
643
  /** Stable identifier from the spec annotation, for selection/edit callbacks. */
597
644
  id?: string;
645
+ /**
646
+ * Index of the originating annotation in `spec.annotations`.
647
+ *
648
+ * The resolved array is a *filtered* view of the spec array -- an annotation
649
+ * whose position falls outside the scale domain resolves to nothing and is
650
+ * dropped -- so a resolved array index is NOT a spec index. Anything that has
651
+ * to reach back to the authored annotation (thinning's priority/responsive
652
+ * lookup, faceted footnote numbering) must key on this.
653
+ */
654
+ specIndex?: number;
598
655
  /** Label text (if any). */
599
656
  label?: ResolvedLabel;
600
657
  /** For range: the highlighted rectangle in pixel coordinates. */
@@ -614,8 +671,9 @@ export interface ResolvedAnnotation {
614
671
  /** Z-index for render ordering. Higher values render on top. */
615
672
  zIndex?: number;
616
673
  /**
617
- * For text annotations: optional dot marker drawn at the connector's
618
- * data-point endpoint. Coordinates match `label.connector.to` exactly.
674
+ * For text annotations: the endpoint marker drawn at the data point.
675
+ * Coordinates sit on the data point itself (plus any `connectorOffset.to`),
676
+ * not on the pulled-back `label.connector.to`.
619
677
  */
620
678
  dot?: {
621
679
  x: number;
@@ -671,8 +729,22 @@ export interface LegendEntry {
671
729
  overflow?: boolean;
672
730
  }
673
731
 
732
+ /** The encoding channel a legend keys. */
733
+ export type LegendChannel = 'color' | 'size';
734
+
674
735
  /** Base legend layout fields shared by all legend types. */
675
736
  export interface BaseLegendLayout {
737
+ /**
738
+ * Which encoding channel this legend keys. Omitted means `'color'` (every
739
+ * legend was a color legend before size legends existed).
740
+ *
741
+ * This is the identity that makes plural legends safe: click-to-toggle-series
742
+ * must fire for color entries and never for size circles, and selection/drag
743
+ * must be able to tell two legends apart. Without it, `.oc-legend` and
744
+ * `data-legend-index` are ambiguous across legends and the DOM queries that
745
+ * read them silently pick whichever comes first.
746
+ */
747
+ channel?: LegendChannel;
676
748
  /** Where the legend is positioned relative to the chart area. */
677
749
  position: 'top' | 'right' | 'bottom' | 'bottom-right' | 'inline';
678
750
  /** Bounding box for the legend (pixel coordinates). */
@@ -733,8 +805,103 @@ export interface GradientLegendLayout extends BaseLegendLayout {
733
805
  maxLabel: string;
734
806
  }
735
807
 
736
- /** Resolved legend layout — either categorical (swatches) or gradient (continuous bar). */
737
- export type LegendLayout = CategoricalLegendLayout | GradientLegendLayout;
808
+ /** A positioned value label on a continuous/binned color legend. */
809
+ export interface ContinuousLegendTick {
810
+ /** The data value this label marks. */
811
+ value: number;
812
+ /** Formatted label text (channel format applied). */
813
+ label: string;
814
+ /** Label x position (pixel coordinates). */
815
+ x: number;
816
+ /** Horizontal text anchor for the label. */
817
+ anchor: 'start' | 'middle' | 'end';
818
+ }
819
+
820
+ /** A discrete class swatch in a binned continuous legend. */
821
+ export interface ContinuousLegendBin {
822
+ /** Swatch x position (pixel coordinates). */
823
+ x: number;
824
+ /** Swatch width in pixels. */
825
+ width: number;
826
+ /** Fill color. */
827
+ color: string;
828
+ }
829
+
830
+ /**
831
+ * Continuous color legend for quantitative color scales on charts.
832
+ *
833
+ * Two rendering modes, resolved from the color channel's scale type:
834
+ * - `gradient`: a single gradient-filled bar with min/max labels (plus a
835
+ * midpoint label at the neutral value for diverging ramps).
836
+ * - `binned`: a contiguous swatch row (quantile/quantize/threshold scales)
837
+ * with boundary value labels between swatches at the class breaks.
838
+ */
839
+ export interface ContinuousLegendLayout extends BaseLegendLayout {
840
+ /** Discriminant for legend type. */
841
+ type: 'continuous';
842
+ /** Rendering mode: gradient bar or binned swatch row. */
843
+ mode: 'gradient' | 'binned';
844
+ /** The color bar rectangle (pixel coordinates). */
845
+ bar: Rect;
846
+ /** Gradient color stops (empty in binned mode). */
847
+ colorStops: GradientColorStop[];
848
+ /** Class swatches, left to right (empty in gradient mode). */
849
+ bins: ContinuousLegendBin[];
850
+ /** Value labels rendered below the bar. */
851
+ ticks: ContinuousLegendTick[];
852
+ /** Text baseline y for the value labels (pixel coordinates). */
853
+ labelY: number;
854
+ }
855
+
856
+ /**
857
+ * One graduated circle in a size legend: a representative value from the size
858
+ * scale's domain, drawn at the radius that value actually resolves to.
859
+ */
860
+ export interface SizeLegendCircle {
861
+ /** The data value this circle stands for. */
862
+ value: number;
863
+ /** Formatted label text. */
864
+ label: string;
865
+ /** Radius in pixels -- the *same* scale the marks use, never a re-derivation. */
866
+ radius: number;
867
+ /** Center x, relative to the legend bounds. */
868
+ cx: number;
869
+ /** Center y, relative to the legend bounds. Circles sit on a common baseline. */
870
+ cy: number;
871
+ /** Label baseline y, relative to the legend bounds. */
872
+ labelY: number;
873
+ }
874
+
875
+ /**
876
+ * Size legend: graduated circles keying a quantitative `size` encoding.
877
+ *
878
+ * The circles are drawn nested (concentric, sharing a bottom edge) rather than
879
+ * side by side -- the newsroom convention, and it makes the *ratio* between
880
+ * magnitudes legible instead of just their order.
881
+ *
882
+ * The keyed values come from the scale's DOMAIN, not the data extent. The size
883
+ * scale clamps, so with an explicit `scale.domain` every datum past `domain[1]`
884
+ * renders at max radius; keying the data extent would imply the biggest circle
885
+ * equals the biggest datum when it may not.
886
+ */
887
+ export interface SizeLegendLayout extends BaseLegendLayout {
888
+ /** Discriminant for legend type. */
889
+ type: 'size';
890
+ /** Graduated circles, smallest first. */
891
+ circles: SizeLegendCircle[];
892
+ /** Stroke color for the circle outlines. */
893
+ stroke: string;
894
+ }
895
+
896
+ /**
897
+ * Resolved legend layout: categorical (swatches), gradient (tilemap bar),
898
+ * continuous (chart color bar), or size (graduated circles).
899
+ */
900
+ export type LegendLayout =
901
+ | CategoricalLegendLayout
902
+ | GradientLegendLayout
903
+ | ContinuousLegendLayout
904
+ | SizeLegendLayout;
738
905
 
739
906
  // ---------------------------------------------------------------------------
740
907
  // Endpoint labels (right-side per-series column for line/area charts)
@@ -826,7 +993,7 @@ export interface TooltipContent {
826
993
 
827
994
  /** Accessibility metadata for the entire visualization. */
828
995
  export interface A11yMetadata {
829
- /** Generated alt text describing the visualization. */
996
+ /** Alt text describing the visualization: the author's `a11y.description` / `description` when set, auto-generated otherwise. */
830
997
  altText: string;
831
998
  /** Tabular data fallback for screen readers. Each inner array is a row. */
832
999
  dataTableFallback: unknown[][];
@@ -834,6 +1001,13 @@ export interface A11yMetadata {
834
1001
  role: string;
835
1002
  /** Whether the visualization is keyboard-navigable. */
836
1003
  keyboardNavigable: boolean;
1004
+ /**
1005
+ * Hide the visualization from assistive technology (`aria-hidden="true"`,
1006
+ * no screen-reader table, no keyboard mark navigation). Optional because
1007
+ * only chart compilation sets it, and only on author opt-out via
1008
+ * `a11y.hidden`.
1009
+ */
1010
+ hidden?: boolean;
837
1011
  }
838
1012
 
839
1013
  // ---------------------------------------------------------------------------
@@ -900,6 +1074,90 @@ export interface ResolvedMetricBar {
900
1074
  cells: ResolvedMetricCell[];
901
1075
  }
902
1076
 
1077
+ // ---------------------------------------------------------------------------
1078
+ // Series search (resolved)
1079
+ // ---------------------------------------------------------------------------
1080
+
1081
+ /**
1082
+ * Reserved band for the series search input (`seriesSearch`). The band is
1083
+ * empty SVG space between the chrome/metrics block and the top legend; the
1084
+ * vanilla adapter overlays an absolutely-positioned DOM combobox aligned to
1085
+ * this rect, so mounting it never changes the container's size.
1086
+ */
1087
+ export interface ResolvedSeriesSearch {
1088
+ /** Band left x in layout coordinates. */
1089
+ x: number;
1090
+ /** Band top y in layout coordinates. */
1091
+ y: number;
1092
+ /** Band width. */
1093
+ width: number;
1094
+ /** Input row height (the reservation adds a gap below this). */
1095
+ height: number;
1096
+ /** Resolved input placeholder text. */
1097
+ placeholder: string;
1098
+ /** Distinct color-channel values available for search, in data order. */
1099
+ values: string[];
1100
+ }
1101
+
1102
+ // ---------------------------------------------------------------------------
1103
+ // You draw it (resolved)
1104
+ // ---------------------------------------------------------------------------
1105
+
1106
+ /**
1107
+ * One x-sample the reader's drawing snaps to: a pixel x position paired with
1108
+ * its data-x value. `xValue` is the value reported by `onReveal`; it falls
1109
+ * back to the pixel x when the data value can't be recovered.
1110
+ */
1111
+ export interface YouDrawItSample {
1112
+ /** Pixel x position within the chart area. */
1113
+ px: number;
1114
+ /** Data-coordinate x value at this sample (from the target line's data). */
1115
+ xValue: string | number;
1116
+ }
1117
+
1118
+ /**
1119
+ * Linear y pixel-to-data anchors, so the vanilla layer can map a drawn pixel
1120
+ * y back to a data value for `onReveal` without holding the scale object.
1121
+ * Present only when the y scale is invertible (continuous scales).
1122
+ */
1123
+ export interface YouDrawItYInvert {
1124
+ /** Top pixel of the drawing area. */
1125
+ topPixel: number;
1126
+ /** Bottom pixel of the drawing area. */
1127
+ bottomPixel: number;
1128
+ /** Data value at the top pixel. */
1129
+ topData: number;
1130
+ /** Data value at the bottom pixel. */
1131
+ bottomData: number;
1132
+ }
1133
+
1134
+ /**
1135
+ * Resolved geometry and config for the "you draw it" interactive format
1136
+ * (`youDrawIt`). The vanilla adapter uses this to render the hatched drawing
1137
+ * region, capture pointer input, and mask/reveal the real line. Present only
1138
+ * when the spec enables `youDrawIt` on a valid single-series line chart.
1139
+ */
1140
+ export interface ResolvedYouDrawIt {
1141
+ /** Pixel x position of `from` within the chart area (drawing starts here). */
1142
+ fromX: number;
1143
+ /** The chart drawing area, for clamping pointer input and sizing the hatch region. */
1144
+ area: Rect;
1145
+ /** X samples (pixel + data value) at or after `from`, ascending by pixel x. Drawing snaps to these. */
1146
+ samples: YouDrawItSample[];
1147
+ /** Resolved prompt text for the hatched region. */
1148
+ prompt: string;
1149
+ /** Resolved skip-to-reveal button label. */
1150
+ revealLabel: string;
1151
+ /** Stroke color of the target line (the reader's guess uses a distinct pen style, but shares the palette for continuity). */
1152
+ lineColor: string;
1153
+ /** 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. */
1154
+ targetSeriesKey?: string;
1155
+ /** Pixel-to-data y anchors for reporting the guess in data coordinates. Absent when the y scale isn't invertible. */
1156
+ yInvert?: YouDrawItYInvert;
1157
+ /** Optional comparison line, resolved to pixel points parallel to the target line's points. */
1158
+ comparisonPoints?: Point[];
1159
+ }
1160
+
903
1161
  // ---------------------------------------------------------------------------
904
1162
  // ChartLayout (the main engine output for charts)
905
1163
  // ---------------------------------------------------------------------------
@@ -951,6 +1209,10 @@ export interface ChartLayout {
951
1209
  chrome: ResolvedChrome;
952
1210
  /** Resolved KPI metric bar. Present only when spec.metrics is supplied and fits. */
953
1211
  metrics?: ResolvedMetricBar;
1212
+ /** Reserved series-search band. Present only when spec.seriesSearch is enabled. */
1213
+ seriesSearch?: ResolvedSeriesSearch;
1214
+ /** Resolved "you draw it" config. Present only when spec.youDrawIt is enabled and valid. */
1215
+ youDrawIt?: ResolvedYouDrawIt;
954
1216
  /** Resolved axis layouts. */
955
1217
  axes: {
956
1218
  x?: AxisLayout;
@@ -962,8 +1224,22 @@ export interface ChartLayout {
962
1224
  marks: Mark[];
963
1225
  /** Resolved annotations with pixel positions. */
964
1226
  annotations: ResolvedAnnotation[];
965
- /** Legend layout (position, entries, bounds). */
1227
+ /**
1228
+ * Primary legend (the color legend). Kept as a singular slot: it is what every
1229
+ * existing consumer reads, and a chart's legend is a color legend unless it
1230
+ * says otherwise.
1231
+ */
966
1232
  legend: LegendLayout;
1233
+ /**
1234
+ * Every legend on the chart, in render order -- the color legend plus, when a
1235
+ * quantitative `size` channel is encoded, a size legend.
1236
+ *
1237
+ * `legend` is `legends[0]` when a color legend exists. Consumers that reserve
1238
+ * space or hit-test **must** iterate this array: a bubble chart keys both
1239
+ * continent (color) and population (size), and reserving margin for only the
1240
+ * first leaves the second drawing on top of the plot.
1241
+ */
1242
+ legends: LegendLayout[];
967
1243
  /**
968
1244
  * Right-side endpoint labels column for multi-series line/area charts.
969
1245
  * Empty `entries` means the column is suppressed (single-series, opt-out, or
@@ -1429,6 +1705,106 @@ export interface TileMapLayout {
1429
1705
  measureText: MeasureTextFn;
1430
1706
  }
1431
1707
 
1708
+ // ---------------------------------------------------------------------------
1709
+ // MapLayout (engine output for map visualizations)
1710
+ // ---------------------------------------------------------------------------
1711
+
1712
+ /** A structured compile warning with a code and context. */
1713
+ export interface CompileWarning {
1714
+ code: string;
1715
+ message: string;
1716
+ context?: Record<string, unknown>;
1717
+ }
1718
+
1719
+ /** Resolved focus target for map camera framing. */
1720
+ export interface MapFocusLayout {
1721
+ target: { x: number; y: number; width: number; height: number; padding: number };
1722
+ ids: Array<string | number>;
1723
+ }
1724
+
1725
+ /** A resolved map feature mark with computed SVG path and fill. */
1726
+ export interface MapFeatureMark {
1727
+ type: 'map-feature';
1728
+ /** SVG path `d` attribute for this feature's shape. */
1729
+ path: string;
1730
+ /** CSS fill color. */
1731
+ fill: string;
1732
+ /** CSS stroke color (border). */
1733
+ stroke: string;
1734
+ /** Stroke width in px. */
1735
+ strokeWidth: number;
1736
+ /** Feature ID (from TopoJSON). */
1737
+ id: string | number;
1738
+ /** Feature name (from properties, if available). */
1739
+ name?: string;
1740
+ /** Original joined data row (null if no data matched this feature). */
1741
+ data: Record<string, unknown> | null;
1742
+ /** Accessibility attributes. */
1743
+ aria: MarkAria;
1744
+ /** Index for stagger animation ordering. */
1745
+ animationIndex: number;
1746
+ /** Bounding box in map-local coordinates. */
1747
+ bounds: { x: number; y: number; width: number; height: number };
1748
+ /** Centroid in map-local coordinates. */
1749
+ centroid: [number, number];
1750
+ }
1751
+
1752
+ /** Border paths for the map (interior borders and coastline/outline). */
1753
+ export interface MapBorders {
1754
+ /** SVG path `d` for interior borders between features. */
1755
+ interiorPath: string;
1756
+ /** SVG path `d` for the outer boundary / coastline. */
1757
+ outlinePath: string;
1758
+ /** Stroke color for interior borders. */
1759
+ interiorStroke: string;
1760
+ /** Stroke color for outer boundary. */
1761
+ outlineStroke: string;
1762
+ }
1763
+
1764
+ /**
1765
+ * MapLayout: the complete engine output for map visualizations.
1766
+ *
1767
+ * Contains everything an adapter needs to render the map: dimensions,
1768
+ * chrome, feature paths, borders, legend, tooltip descriptors, and
1769
+ * accessibility metadata.
1770
+ */
1771
+ export interface MapLayout {
1772
+ /** The map drawing area (after chrome and legend are subtracted). */
1773
+ area: Rect;
1774
+ /** Resolved chrome text elements with positions and styles. */
1775
+ chrome: ResolvedChrome;
1776
+ /** Resolved feature marks with paths, fills, and joined data. */
1777
+ features: MapFeatureMark[];
1778
+ /** Border paths (interior mesh and outline). */
1779
+ borders: MapBorders;
1780
+ /** Continuous color legend for quantitative mode (null if hidden or categorical). */
1781
+ continuousLegend: ContinuousLegendLayout | null;
1782
+ /** Categorical swatch legend for nominal mode (null if hidden or quantitative). */
1783
+ categoricalLegend: CategoricalLegendLayout | null;
1784
+ /** Tooltip content descriptors keyed by feature ID. */
1785
+ tooltipDescriptors: Map<string, TooltipContent>;
1786
+ /** Accessibility metadata for the map. */
1787
+ a11y: A11yMetadata;
1788
+ /** Resolved theme. */
1789
+ theme: ResolvedTheme;
1790
+ /** Total SVG width in pixels. */
1791
+ width: number;
1792
+ /** Total SVG height in pixels. */
1793
+ height: number;
1794
+ /** Resolved animation config (undefined if animation is disabled). */
1795
+ animation: ResolvedAnimation | undefined;
1796
+ /** Whether to render the watermark. */
1797
+ watermark: boolean;
1798
+ /** Text measurement function for the rendering adapter. */
1799
+ measureText: MeasureTextFn;
1800
+ /** Structured compile warnings (unmatched keys, missing data, winding issues). */
1801
+ warnings: CompileWarning[];
1802
+ /** The (width, height) passed to the projection's fitSize -- camera fits against this, not the SVG viewBox. */
1803
+ mapSize: { width: number; height: number };
1804
+ /** Resolved focus target for camera framing (null if no focus). */
1805
+ focus: MapFocusLayout | null;
1806
+ }
1807
+
1432
1808
  // ---------------------------------------------------------------------------
1433
1809
  // BarListLayout (engine output for bar list visualizations)
1434
1810
  // ---------------------------------------------------------------------------
@@ -1529,6 +1905,24 @@ export interface CompileOptions {
1529
1905
  darkMode?: boolean;
1530
1906
  /** Whether to show the tryOpenData.ai watermark. Defaults to true. */
1531
1907
  watermark?: boolean;
1908
+ /**
1909
+ * Enable development-time diagnostics (WCAG contrast warnings via
1910
+ * `console.warn`). The engine is isomorphic, so it never sniffs
1911
+ * `process.env`; the host decides what "dev" means and passes this flag.
1912
+ * Warnings are advisory only and never throw. Defaults to false.
1913
+ */
1914
+ dev?: boolean;
1915
+ /**
1916
+ * Sink for advisory spec warnings (deprecations, did-you-mean suggestions,
1917
+ * unknown scheme names, overplotting/beeswarm budget notes, etc.). The engine
1918
+ * is isomorphic and side-effect-free by default: rather than writing to the
1919
+ * global `console`, it routes every advisory warning through this callback so
1920
+ * a host can collect, reroute, or silence them (e.g. suppress under SSR, fail
1921
+ * a test on any warning, surface them in a dev overlay). When omitted, the
1922
+ * engine defaults to `console.warn`. Never called with fatal errors, which are
1923
+ * thrown instead.
1924
+ */
1925
+ onWarn?: (message: string) => void;
1532
1926
  /**
1533
1927
  * Real text measurement function provided by the adapter.
1534
1928
  * Uses a hidden canvas or DOM element for accurate text dimensions.
@@ -1537,6 +1931,18 @@ export interface CompileOptions {
1537
1931
  measureText?: MeasureTextFn;
1538
1932
  /** Extra pixels to reserve on the right margin for a secondary y-axis. Set by compileLayer when resolve.scale.y is 'independent'. */
1539
1933
  rightAxisReserve?: number;
1934
+ /**
1935
+ * Extra pixels to reserve below the plot for the auto-thinning footnote list.
1936
+ * Chicken-and-egg: thinning needs the chart area to decide what to demote, but
1937
+ * the footnotes it produces need space carved out of that same area. compileChart
1938
+ * resolves it by compiling once to learn the footnote count, then recompiling with
1939
+ * this set. Callers should not set it; it is internal to that second pass.
1940
+ */
1941
+ footnoteReserve?: number;
1942
+ /** Recursion depth of the footnote-reserve convergence pass. Internal to compileChart. */
1943
+ footnotePass?: number;
1944
+ /** Set after facet height growth to prevent unbounded recursion. Internal to compileFaceted. */
1945
+ facetHeightGrown?: boolean;
1540
1946
  /**
1541
1947
  * Use this chart drawing area instead of computing one from chrome, axes,
1542
1948
  * and legend reservations. Set by compileLayer when compiling leaf layers so
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Concrete (non-generic) type aliases used as JSON Schema generation roots.
3
+ *
4
+ * `ts-json-schema-generator` cannot instantiate generic type parameters, so the
5
+ * schema generator (`scripts/generate-schema.mjs`) points at these fixed
6
+ * aliases instead of the generic `ChartSpec<TData>` / `TableSpec` directly.
7
+ * They resolve every generic to its `DataRow` default, which is the shape an
8
+ * LLM actually emits (untyped rows).
9
+ *
10
+ * These aliases exist only to shape schema output. They are not re-exported
11
+ * from the package barrel and add no runtime surface.
12
+ */
13
+
14
+ import type { ChartSpec, TableSpec, VizSpec } from './spec';
15
+
16
+ /**
17
+ * Root for the full published schema (`vizspec.schema.json`). The complete
18
+ * discriminated union of every openchart spec kind.
19
+ */
20
+ export type VizSpecSchema = VizSpec;
21
+
22
+ /**
23
+ * Root for the ChartSpec subset schema (`chart.schema.json`). Covers all 16
24
+ * mark types. This is the LLM chart workhorse and the primary tool-use surface.
25
+ */
26
+ export type ChartSpecSchema = ChartSpec;
27
+
28
+ /**
29
+ * Root for the TableSpec subset schema (`table.schema.json`). The data-table
30
+ * visualization spec.
31
+ */
32
+ export type TableSpecSchema = TableSpec;