@opendata-ai/openchart-core 2.0.0

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 (51) hide show
  1. package/README.md +130 -0
  2. package/dist/index.d.ts +2030 -0
  3. package/dist/index.js +1176 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/styles.css +757 -0
  6. package/package.json +61 -0
  7. package/src/accessibility/__tests__/alt-text.test.ts +110 -0
  8. package/src/accessibility/__tests__/aria.test.ts +125 -0
  9. package/src/accessibility/alt-text.ts +120 -0
  10. package/src/accessibility/aria.ts +73 -0
  11. package/src/accessibility/index.ts +6 -0
  12. package/src/colors/__tests__/colorblind.test.ts +63 -0
  13. package/src/colors/__tests__/contrast.test.ts +71 -0
  14. package/src/colors/__tests__/palettes.test.ts +54 -0
  15. package/src/colors/colorblind.ts +122 -0
  16. package/src/colors/contrast.ts +94 -0
  17. package/src/colors/index.ts +27 -0
  18. package/src/colors/palettes.ts +118 -0
  19. package/src/helpers/__tests__/spec-builders.test.ts +336 -0
  20. package/src/helpers/spec-builders.ts +410 -0
  21. package/src/index.ts +129 -0
  22. package/src/labels/__tests__/collision.test.ts +197 -0
  23. package/src/labels/collision.ts +154 -0
  24. package/src/labels/index.ts +6 -0
  25. package/src/layout/__tests__/chrome.test.ts +114 -0
  26. package/src/layout/__tests__/text-measure.test.ts +49 -0
  27. package/src/layout/chrome.ts +223 -0
  28. package/src/layout/index.ts +6 -0
  29. package/src/layout/text-measure.ts +54 -0
  30. package/src/locale/__tests__/format.test.ts +90 -0
  31. package/src/locale/format.ts +132 -0
  32. package/src/locale/index.ts +6 -0
  33. package/src/responsive/__tests__/breakpoints.test.ts +58 -0
  34. package/src/responsive/breakpoints.ts +92 -0
  35. package/src/responsive/index.ts +18 -0
  36. package/src/styles/viz.css +757 -0
  37. package/src/theme/__tests__/dark-mode.test.ts +68 -0
  38. package/src/theme/__tests__/defaults.test.ts +47 -0
  39. package/src/theme/__tests__/resolve.test.ts +61 -0
  40. package/src/theme/dark-mode.ts +123 -0
  41. package/src/theme/defaults.ts +85 -0
  42. package/src/theme/index.ts +7 -0
  43. package/src/theme/resolve.ts +190 -0
  44. package/src/types/__tests__/spec.test.ts +387 -0
  45. package/src/types/encoding.ts +144 -0
  46. package/src/types/events.ts +96 -0
  47. package/src/types/index.ts +141 -0
  48. package/src/types/layout.ts +794 -0
  49. package/src/types/spec.ts +563 -0
  50. package/src/types/table.ts +105 -0
  51. package/src/types/theme.ts +159 -0
@@ -0,0 +1,794 @@
1
+ /**
2
+ * Layout types: the engine output contract.
3
+ *
4
+ * These are the structured layout objects that the engine produces after
5
+ * compiling a spec. Framework adapters consume these to render real DOM.
6
+ *
7
+ * The key distinction: spec.ts types are USER INPUT, layout.ts types are
8
+ * ENGINE OUTPUT. Input types have optionals and unions. Output types are
9
+ * fully resolved with computed positions, colors, and dimensions.
10
+ */
11
+
12
+ import type { ResolvedTheme } from './theme';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Geometry primitives
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /** A rectangle defined by position and dimensions. */
19
+ export interface Rect {
20
+ x: number;
21
+ y: number;
22
+ width: number;
23
+ height: number;
24
+ }
25
+
26
+ /** Margins around a region (top, right, bottom, left). */
27
+ export interface Margins {
28
+ top: number;
29
+ right: number;
30
+ bottom: number;
31
+ left: number;
32
+ }
33
+
34
+ /** A 2D point. */
35
+ export interface Point {
36
+ x: number;
37
+ y: number;
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Text styling
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /** Resolved text style with all computed values. */
45
+ export interface TextStyle {
46
+ /** Font family. */
47
+ fontFamily: string;
48
+ /** Font size in pixels. */
49
+ fontSize: number;
50
+ /** Font weight. */
51
+ fontWeight: number;
52
+ /** Text color (CSS color string). */
53
+ fill: string;
54
+ /** Line height multiplier. */
55
+ lineHeight: number;
56
+ /** Text anchor for SVG: start, middle, or end. */
57
+ textAnchor?: 'start' | 'middle' | 'end';
58
+ /** Dominant baseline for SVG text vertical alignment. */
59
+ dominantBaseline?: 'auto' | 'hanging' | 'central' | 'text-after-edge';
60
+ /** Font variant (e.g. "tabular-nums" for fixed-width digits). */
61
+ fontVariant?: string;
62
+ }
63
+
64
+ /** Cell style for table cells. */
65
+ export interface CellStyle {
66
+ /** Background color. */
67
+ backgroundColor?: string;
68
+ /** Text color. */
69
+ color?: string;
70
+ /** Font weight. */
71
+ fontWeight?: number;
72
+ /** Font variant. */
73
+ fontVariant?: string;
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Chrome (resolved)
78
+ // ---------------------------------------------------------------------------
79
+
80
+ /** A single resolved chrome text element with computed position and style. */
81
+ export interface ResolvedChromeElement {
82
+ /** The text content. */
83
+ text: string;
84
+ /** X position in the layout coordinate system. */
85
+ x: number;
86
+ /** Y position (baseline) in the layout coordinate system. */
87
+ y: number;
88
+ /** Maximum width for line wrapping. */
89
+ maxWidth: number;
90
+ /** Computed text style. */
91
+ style: TextStyle;
92
+ }
93
+
94
+ /**
95
+ * Fully resolved chrome with computed positions for all elements.
96
+ * Only present elements are included (no undefined checks needed at render time).
97
+ */
98
+ export interface ResolvedChrome {
99
+ /** Total height consumed by chrome elements above the chart area. */
100
+ topHeight: number;
101
+ /** Total height consumed by chrome elements below the chart area. */
102
+ bottomHeight: number;
103
+ /** Resolved chrome elements. Only present if specified in the spec. */
104
+ title?: ResolvedChromeElement;
105
+ subtitle?: ResolvedChromeElement;
106
+ source?: ResolvedChromeElement;
107
+ byline?: ResolvedChromeElement;
108
+ footer?: ResolvedChromeElement;
109
+ }
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Axes
113
+ // ---------------------------------------------------------------------------
114
+
115
+ /** A single axis tick with computed position and formatted label. */
116
+ export interface AxisTick {
117
+ /** The raw data value at this tick. */
118
+ value: unknown;
119
+ /** Pixel position along the axis. */
120
+ position: number;
121
+ /** Formatted label string for display. */
122
+ label: string;
123
+ }
124
+
125
+ /** A single gridline with computed positions. */
126
+ export interface Gridline {
127
+ /** Pixel position along the axis. */
128
+ position: number;
129
+ /** Whether this is a major or minor gridline. */
130
+ major: boolean;
131
+ }
132
+
133
+ /** Resolved axis layout with computed tick positions and labels. */
134
+ export interface AxisLayout {
135
+ /** Axis ticks with positions and labels. */
136
+ ticks: AxisTick[];
137
+ /** Gridlines at tick positions. */
138
+ gridlines: Gridline[];
139
+ /** Axis label text (e.g. "GDP Growth (%)"). */
140
+ label?: string;
141
+ /** Label style. */
142
+ labelStyle?: TextStyle;
143
+ /** Tick label style. */
144
+ tickLabelStyle: TextStyle;
145
+ /** Axis line start position. */
146
+ start: Point;
147
+ /** Axis line end position. */
148
+ end: Point;
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Marks (data visual elements)
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /** Accessibility attributes for a mark. */
156
+ export interface MarkAria {
157
+ /** ARIA label for the mark. */
158
+ label: string;
159
+ /** Optional longer description. */
160
+ description?: string;
161
+ /** ARIA role override. */
162
+ role?: string;
163
+ }
164
+
165
+ /**
166
+ * Line mark: a series of connected points.
167
+ * Used by line charts and area chart boundaries.
168
+ */
169
+ export interface LineMark {
170
+ type: 'line';
171
+ /** Ordered array of points defining the line path. */
172
+ points: Point[];
173
+ /** Pre-computed SVG path string (D3 monotone curve). When present, renderers
174
+ * should use this instead of reconstructing straight M/L segments from points. */
175
+ path?: string;
176
+ /** Stroke color. */
177
+ stroke: string;
178
+ /** Stroke width in pixels. */
179
+ strokeWidth: number;
180
+ /** Line dash pattern (empty for solid). */
181
+ strokeDasharray?: string;
182
+ /** Series identifier (for multi-series charts). */
183
+ seriesKey?: string;
184
+ /** Original data rows for this series. */
185
+ data: Record<string, unknown>[];
186
+ /** Resolved label after collision detection. */
187
+ label?: ResolvedLabel;
188
+ /** Accessibility attributes. */
189
+ aria: MarkAria;
190
+ }
191
+
192
+ /**
193
+ * Area mark: a filled region bounded by a top line and a baseline.
194
+ * Used by area charts.
195
+ */
196
+ export interface AreaMark {
197
+ type: 'area';
198
+ /** Upper boundary points. */
199
+ topPoints: Point[];
200
+ /** Lower boundary points (baseline, usually y=0). */
201
+ bottomPoints: Point[];
202
+ /** SVG path string for the complete area shape. */
203
+ path: string;
204
+ /** SVG path string for just the top boundary (for stroking the data line only). */
205
+ topPath: string;
206
+ /** Fill color. */
207
+ fill: string;
208
+ /** Fill opacity. */
209
+ fillOpacity: number;
210
+ /** Optional stroke for the top boundary. */
211
+ stroke?: string;
212
+ /** Stroke width. */
213
+ strokeWidth?: number;
214
+ /** Series identifier. */
215
+ seriesKey?: string;
216
+ /** Original data rows. */
217
+ data: Record<string, unknown>[];
218
+ /** Accessibility attributes. */
219
+ aria: MarkAria;
220
+ }
221
+
222
+ /**
223
+ * Rect mark: a rectangle.
224
+ * Used by bar charts, column charts, and heatmap cells.
225
+ */
226
+ export interface RectMark {
227
+ type: 'rect';
228
+ /** X position. */
229
+ x: number;
230
+ /** Y position. */
231
+ y: number;
232
+ /** Width. */
233
+ width: number;
234
+ /** Height. */
235
+ height: number;
236
+ /** Fill color. */
237
+ fill: string;
238
+ /** Stroke color. */
239
+ stroke?: string;
240
+ /** Stroke width. */
241
+ strokeWidth?: number;
242
+ /** Corner radius. */
243
+ cornerRadius?: number;
244
+ /** Original data row. */
245
+ data: Record<string, unknown>;
246
+ /** Resolved label. */
247
+ label?: ResolvedLabel;
248
+ /** Accessibility attributes. */
249
+ aria: MarkAria;
250
+ }
251
+
252
+ /**
253
+ * Arc mark: a pie/donut slice.
254
+ * Used by pie and donut charts.
255
+ */
256
+ export interface ArcMark {
257
+ type: 'arc';
258
+ /** SVG path string for the arc. */
259
+ path: string;
260
+ /** Centroid point (center of the arc, for label positioning). */
261
+ centroid: Point;
262
+ /** Center of the pie/donut chart (for SVG translate). */
263
+ center: Point;
264
+ /** Inner radius (0 for pie, >0 for donut). */
265
+ innerRadius: number;
266
+ /** Outer radius. */
267
+ outerRadius: number;
268
+ /** Start angle in radians. */
269
+ startAngle: number;
270
+ /** End angle in radians. */
271
+ endAngle: number;
272
+ /** Fill color. */
273
+ fill: string;
274
+ /** Stroke color (usually white for slice separation). */
275
+ stroke: string;
276
+ /** Stroke width. */
277
+ strokeWidth: number;
278
+ /** Original data row. */
279
+ data: Record<string, unknown>;
280
+ /** Resolved label. */
281
+ label?: ResolvedLabel;
282
+ /** Accessibility attributes. */
283
+ aria: MarkAria;
284
+ }
285
+
286
+ /**
287
+ * Point mark: a circle or dot.
288
+ * Used by scatter/bubble charts and dot plots.
289
+ */
290
+ export interface PointMark {
291
+ type: 'point';
292
+ /** Center x position. */
293
+ cx: number;
294
+ /** Center y position. */
295
+ cy: number;
296
+ /** Radius in pixels. */
297
+ r: number;
298
+ /** Fill color. */
299
+ fill: string;
300
+ /** Stroke color. */
301
+ stroke: string;
302
+ /** Stroke width. */
303
+ strokeWidth: number;
304
+ /** Fill opacity. */
305
+ fillOpacity?: number;
306
+ /** Original data row. */
307
+ data: Record<string, unknown>;
308
+ /** Resolved label. */
309
+ label?: ResolvedLabel;
310
+ /** Accessibility attributes. */
311
+ aria: MarkAria;
312
+ }
313
+
314
+ /** Discriminated union of all mark types. */
315
+ export type Mark = LineMark | AreaMark | RectMark | ArcMark | PointMark;
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // Labels
319
+ // ---------------------------------------------------------------------------
320
+
321
+ /**
322
+ * A resolved label: text with a computed position after collision detection.
323
+ * The collision engine determines whether labels are visible or demoted to tooltip-only.
324
+ */
325
+ export interface ResolvedLabel {
326
+ /** Label text content. */
327
+ text: string;
328
+ /** Computed x position. */
329
+ x: number;
330
+ /** Computed y position (baseline). */
331
+ y: number;
332
+ /** Text style. */
333
+ style: TextStyle;
334
+ /** Whether this label is visible or was demoted to tooltip-only by collision detection. */
335
+ visible: boolean;
336
+ /** If not at the anchor point, draw a connector line from label to anchor. */
337
+ connector?: {
338
+ /** Connector start (at the label). */
339
+ from: Point;
340
+ /** Connector end (at the data point). */
341
+ to: Point;
342
+ /** Connector line color. */
343
+ stroke: string;
344
+ /** Connector style: straight line, curved arrow, or directional caret. */
345
+ style: 'straight' | 'curve' | 'caret';
346
+ };
347
+ /** Background color behind the label text. */
348
+ background?: string;
349
+ }
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Annotations (resolved)
353
+ // ---------------------------------------------------------------------------
354
+
355
+ /** A resolved annotation with computed pixel positions. */
356
+ export interface ResolvedAnnotation {
357
+ /** Original annotation type. */
358
+ type: 'text' | 'range' | 'refline';
359
+ /** Label text (if any). */
360
+ label?: ResolvedLabel;
361
+ /** For range: the highlighted rectangle in pixel coordinates. */
362
+ rect?: Rect;
363
+ /** For refline: the line start and end in pixel coordinates. */
364
+ line?: { start: Point; end: Point };
365
+ /** Fill color. */
366
+ fill?: string;
367
+ /** Stroke color. */
368
+ stroke?: string;
369
+ /** Opacity. */
370
+ opacity?: number;
371
+ /** Line dash pattern for reflines. */
372
+ strokeDasharray?: string;
373
+ /** Stroke width. */
374
+ strokeWidth?: number;
375
+ /** Z-index for render ordering. Higher values render on top. */
376
+ zIndex?: number;
377
+ }
378
+
379
+ // ---------------------------------------------------------------------------
380
+ // Legend
381
+ // ---------------------------------------------------------------------------
382
+
383
+ /** A single entry in the legend. */
384
+ export interface LegendEntry {
385
+ /** The label text (category name or range description). */
386
+ label: string;
387
+ /** The color swatch. */
388
+ color: string;
389
+ /** Shape of the swatch (matches the mark type). */
390
+ shape: 'circle' | 'square' | 'line';
391
+ /** Whether this entry is currently highlighted/active. */
392
+ active?: boolean;
393
+ }
394
+
395
+ /** Resolved legend layout with position and entries. */
396
+ export interface LegendLayout {
397
+ /** Where the legend is positioned relative to the chart area. */
398
+ position: 'top' | 'right' | 'bottom' | 'bottom-right' | 'inline';
399
+ /** Legend entries. */
400
+ entries: LegendEntry[];
401
+ /** Bounding box for the legend (pixel coordinates). */
402
+ bounds: Rect;
403
+ /** Entry label style. */
404
+ labelStyle: TextStyle;
405
+ /** Swatch size in pixels. */
406
+ swatchSize: number;
407
+ /** Gap between swatch and label. */
408
+ swatchGap: number;
409
+ /** Gap between entries. */
410
+ entryGap: number;
411
+ }
412
+
413
+ // ---------------------------------------------------------------------------
414
+ // Tooltips
415
+ // ---------------------------------------------------------------------------
416
+
417
+ /** A single field-value pair in a tooltip. */
418
+ export interface TooltipField {
419
+ /** Field label (e.g. "GDP Growth"). */
420
+ label: string;
421
+ /** Formatted value (e.g. "4.2%"). */
422
+ value: string;
423
+ /** Optional color swatch for series identification. */
424
+ color?: string;
425
+ }
426
+
427
+ /** Tooltip content descriptor for a data point. */
428
+ export interface TooltipContent {
429
+ /** Title line (e.g. the x-axis value like "2020-Q1"). */
430
+ title?: string;
431
+ /** Field-value pairs to display. */
432
+ fields: TooltipField[];
433
+ }
434
+
435
+ // ---------------------------------------------------------------------------
436
+ // Accessibility
437
+ // ---------------------------------------------------------------------------
438
+
439
+ /** Accessibility metadata for the entire visualization. */
440
+ export interface A11yMetadata {
441
+ /** Generated alt text describing the visualization. */
442
+ altText: string;
443
+ /** Tabular data fallback for screen readers. Each inner array is a row. */
444
+ dataTableFallback: unknown[][];
445
+ /** ARIA role for the visualization root element. */
446
+ role: string;
447
+ /** Whether the visualization is keyboard-navigable. */
448
+ keyboardNavigable: boolean;
449
+ }
450
+
451
+ // ---------------------------------------------------------------------------
452
+ // ChartLayout (the main engine output for charts)
453
+ // ---------------------------------------------------------------------------
454
+
455
+ /**
456
+ * ChartLayout: the complete engine output for chart visualizations.
457
+ *
458
+ * Contains everything an adapter needs to render the chart: dimensions,
459
+ * chrome text, axes, data marks, annotations, legend, tooltip descriptors,
460
+ * and accessibility metadata. All values are fully computed pixel positions
461
+ * and resolved colors.
462
+ */
463
+ export interface ChartLayout {
464
+ /** The chart drawing area (after chrome, axes, and legend are subtracted). */
465
+ area: Rect;
466
+ /** Resolved chrome text elements with positions and styles. */
467
+ chrome: ResolvedChrome;
468
+ /** Resolved axis layouts. */
469
+ axes: {
470
+ x?: AxisLayout;
471
+ y?: AxisLayout;
472
+ };
473
+ /** Data marks: the visual representation of data points. */
474
+ marks: Mark[];
475
+ /** Resolved annotations with pixel positions. */
476
+ annotations: ResolvedAnnotation[];
477
+ /** Legend layout (position, entries, bounds). */
478
+ legend: LegendLayout;
479
+ /** Tooltip descriptors keyed by a mark identifier. */
480
+ tooltipDescriptors: Map<string, TooltipContent>;
481
+ /** Accessibility metadata. */
482
+ a11y: A11yMetadata;
483
+ /** The resolved theme used for rendering. */
484
+ theme: ResolvedTheme;
485
+ /** Total SVG dimensions. */
486
+ dimensions: { width: number; height: number };
487
+ }
488
+
489
+ // ---------------------------------------------------------------------------
490
+ // TableLayout (engine output for tables)
491
+ // ---------------------------------------------------------------------------
492
+
493
+ /** A resolved column definition with computed properties. */
494
+ export interface ResolvedColumn {
495
+ /** Column key (data field name). */
496
+ key: string;
497
+ /** Display label. */
498
+ label: string;
499
+ /** Computed width in pixels. */
500
+ width: number;
501
+ /** Whether sorting is enabled. */
502
+ sortable: boolean;
503
+ /** Text alignment. */
504
+ align: 'left' | 'center' | 'right';
505
+ /** Column cell type (determines rendering strategy). */
506
+ cellType: 'text' | 'heatmap' | 'category' | 'bar' | 'sparkline' | 'image' | 'flag';
507
+ }
508
+
509
+ /** Base properties for all table cell types. */
510
+ export interface TableCellBase {
511
+ /** Raw value from the data. */
512
+ value: unknown;
513
+ /** Formatted display value. */
514
+ formattedValue: string;
515
+ /** Computed cell style. */
516
+ style: CellStyle;
517
+ /** ARIA label for the cell. */
518
+ aria?: string;
519
+ }
520
+
521
+ /** Plain text cell. */
522
+ export interface TextTableCell extends TableCellBase {
523
+ cellType: 'text';
524
+ }
525
+
526
+ /** Heatmap-colored cell. */
527
+ export interface HeatmapTableCell extends TableCellBase {
528
+ cellType: 'heatmap';
529
+ }
530
+
531
+ /** Category-colored cell. */
532
+ export interface CategoryTableCell extends TableCellBase {
533
+ cellType: 'category';
534
+ }
535
+
536
+ /** Cell with an inline bar visualization. */
537
+ export interface BarTableCell extends TableCellBase {
538
+ cellType: 'bar';
539
+ /** Bar width as a proportion (0 to 1) of available cell width. */
540
+ barWidth: number;
541
+ /** Bar left-edge offset as a proportion (0 to 1). 0 = left edge. */
542
+ barOffset: number;
543
+ /** Bar fill color. */
544
+ barColor: string;
545
+ /** Whether this bar represents a negative value. */
546
+ isNegative: boolean;
547
+ }
548
+
549
+ /** Normalized sparkline data ready for rendering. */
550
+ export interface SparklineData {
551
+ /** Sparkline type. */
552
+ type: 'line' | 'bar' | 'column';
553
+ /** Normalized points in 0-1 range (for line type: x/y coordinates). */
554
+ points: Array<{ x: number; y: number }>;
555
+ /** For bar/column: widths or heights as 0-1 proportions. */
556
+ bars: number[];
557
+ /** Sparkline stroke/fill color. */
558
+ color: string;
559
+ /** Number of data points. */
560
+ count: number;
561
+ /** Raw value of the first data point. */
562
+ startValue: number;
563
+ /** Raw value of the last data point. */
564
+ endValue: number;
565
+ }
566
+
567
+ /** Cell with an inline sparkline. */
568
+ export interface SparklineTableCell extends TableCellBase {
569
+ cellType: 'sparkline';
570
+ /** Sparkline rendering data (normalized points, color, type). Null when no valid data. */
571
+ sparklineData: SparklineData | null;
572
+ }
573
+
574
+ /** Cell with an image. */
575
+ export interface ImageTableCell extends TableCellBase {
576
+ cellType: 'image';
577
+ /** Image URL. */
578
+ src: string;
579
+ /** Image width. */
580
+ imageWidth: number;
581
+ /** Image height. */
582
+ imageHeight: number;
583
+ /** Whether to apply rounded styling. */
584
+ rounded: boolean;
585
+ }
586
+
587
+ /** Cell displaying a flag. */
588
+ export interface FlagTableCell extends TableCellBase {
589
+ cellType: 'flag';
590
+ /** Country code or flag identifier. */
591
+ countryCode: string;
592
+ }
593
+
594
+ /** Discriminated union of all table cell types. */
595
+ export type TableCell =
596
+ | TextTableCell
597
+ | HeatmapTableCell
598
+ | CategoryTableCell
599
+ | BarTableCell
600
+ | SparklineTableCell
601
+ | ImageTableCell
602
+ | FlagTableCell;
603
+
604
+ /** A resolved table row. */
605
+ export interface TableRow {
606
+ /** Unique row identifier. */
607
+ id: string;
608
+ /** Resolved cells in column order. */
609
+ cells: TableCell[];
610
+ /** Original data row. */
611
+ data: Record<string, unknown>;
612
+ }
613
+
614
+ /** Pagination state for the table. */
615
+ export interface PaginationState {
616
+ /** Current page (0-indexed). */
617
+ page: number;
618
+ /** Rows per page. */
619
+ pageSize: number;
620
+ /** Total number of rows. */
621
+ totalRows: number;
622
+ /** Total number of pages. */
623
+ totalPages: number;
624
+ }
625
+
626
+ /** Sort state for the table. */
627
+ export interface SortState {
628
+ /** Column key being sorted. */
629
+ column: string;
630
+ /** Sort direction. */
631
+ direction: 'asc' | 'desc';
632
+ }
633
+
634
+ /**
635
+ * TableLayout: the complete engine output for table visualizations.
636
+ *
637
+ * Contains resolved columns, rows with computed cell values and styles,
638
+ * pagination, sort, search state, and accessibility metadata.
639
+ */
640
+ export interface TableLayout {
641
+ /** Resolved chrome text elements. */
642
+ chrome: ResolvedChrome;
643
+ /** Resolved column definitions. */
644
+ columns: ResolvedColumn[];
645
+ /** Resolved table rows with computed cell values and styles. */
646
+ rows: TableRow[];
647
+ /** Current sort state. */
648
+ sort?: SortState;
649
+ /** Pagination state. */
650
+ pagination?: PaginationState;
651
+ /** Whether search is enabled. */
652
+ search: { enabled: boolean; placeholder: string; query: string };
653
+ /** Whether the first column is sticky. */
654
+ stickyFirstColumn: boolean;
655
+ /** Whether compact mode is active. */
656
+ compact: boolean;
657
+ /** Accessibility metadata. */
658
+ a11y: { caption: string; summary: string };
659
+ /** The resolved theme. */
660
+ theme: ResolvedTheme;
661
+ }
662
+
663
+ // ---------------------------------------------------------------------------
664
+ // GraphLayout (engine output for network graphs)
665
+ // ---------------------------------------------------------------------------
666
+
667
+ /** A resolved graph node with computed position and visual properties. */
668
+ export interface GraphNodeLayout {
669
+ /** Node id. */
670
+ id: string;
671
+ /** Computed x position. */
672
+ x: number;
673
+ /** Computed y position. */
674
+ y: number;
675
+ /** Node radius in pixels. */
676
+ radius: number;
677
+ /** Fill color. */
678
+ fill: string;
679
+ /** Stroke color. */
680
+ stroke: string;
681
+ /** Stroke width. */
682
+ strokeWidth: number;
683
+ /** Node label. */
684
+ label?: ResolvedLabel;
685
+ /** Original node data. */
686
+ data: Record<string, unknown>;
687
+ /** Community/cluster identifier (from clustering config). */
688
+ community?: string;
689
+ /** Accessibility attributes. */
690
+ aria: MarkAria;
691
+ }
692
+
693
+ /** A resolved graph edge with computed positions and visual properties. */
694
+ export interface GraphEdgeLayout {
695
+ /** Source node id. */
696
+ source: string;
697
+ /** Target node id. */
698
+ target: string;
699
+ /** Source x position. */
700
+ x1: number;
701
+ /** Source y position. */
702
+ y1: number;
703
+ /** Target x position. */
704
+ x2: number;
705
+ /** Target y position. */
706
+ y2: number;
707
+ /** Stroke color. */
708
+ stroke: string;
709
+ /** Stroke width. */
710
+ strokeWidth: number;
711
+ /** Line style. */
712
+ style: 'solid' | 'dashed' | 'dotted';
713
+ /** Original edge data. */
714
+ data: Record<string, unknown>;
715
+ }
716
+
717
+ /**
718
+ * GraphLayout: the complete engine output for network graph visualizations.
719
+ *
720
+ * Note: GraphLayout is defined here for forward compatibility but the engine
721
+ * won't produce it until the graph renderer is implemented in a future phase.
722
+ */
723
+ export interface GraphLayout {
724
+ /** The graph drawing area. */
725
+ area: Rect;
726
+ /** Resolved chrome text. */
727
+ chrome: ResolvedChrome;
728
+ /** Resolved node positions and visual properties. */
729
+ nodes: GraphNodeLayout[];
730
+ /** Resolved edge positions and visual properties. */
731
+ edges: GraphEdgeLayout[];
732
+ /** Legend layout. */
733
+ legend: LegendLayout;
734
+ /** Tooltip descriptors. */
735
+ tooltipDescriptors: Map<string, TooltipContent>;
736
+ /** Accessibility metadata. */
737
+ a11y: A11yMetadata;
738
+ /** The resolved theme. */
739
+ theme: ResolvedTheme;
740
+ /** Total dimensions. */
741
+ dimensions: { width: number; height: number };
742
+ }
743
+
744
+ // ---------------------------------------------------------------------------
745
+ // Compile options (engine input)
746
+ // ---------------------------------------------------------------------------
747
+
748
+ /** Function signature for text measurement provided by adapters. */
749
+ export type MeasureTextFn = (
750
+ text: string,
751
+ fontSize: number,
752
+ fontWeight?: number,
753
+ ) => { width: number; height: number };
754
+
755
+ /**
756
+ * Options passed to the engine's compile functions.
757
+ *
758
+ * Width and height define the total available space. The engine subtracts
759
+ * chrome, axes, and legend to compute the chart drawing area.
760
+ */
761
+ export interface CompileOptions {
762
+ /** Total available width in pixels. */
763
+ width: number;
764
+ /** Total available height in pixels. */
765
+ height: number;
766
+ /** Theme overrides. */
767
+ theme?: import('./spec').ThemeConfig;
768
+ /**
769
+ * Resolved dark mode boolean.
770
+ *
771
+ * Note: this is a boolean, not the DarkMode union ("auto" | "force" | "off").
772
+ * Adapters resolve "auto" by checking window.matchMedia('(prefers-color-scheme: dark)')
773
+ * before calling compile. The engine always receives a resolved boolean.
774
+ */
775
+ darkMode?: boolean;
776
+ /**
777
+ * Real text measurement function provided by the adapter.
778
+ * Uses a hidden canvas or DOM element for accurate text dimensions.
779
+ * If not provided, the engine falls back to heuristic estimation.
780
+ */
781
+ measureText?: MeasureTextFn;
782
+ }
783
+
784
+ /** Extended compile options for table visualizations. */
785
+ export interface CompileTableOptions extends CompileOptions {
786
+ /** Current sort state to apply. */
787
+ sort?: SortState;
788
+ /** Search query to filter rows. */
789
+ search?: string;
790
+ /** Current page (0-indexed). */
791
+ page?: number;
792
+ /** Rows per page. */
793
+ pageSize?: number;
794
+ }