@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
package/dist/index.js ADDED
@@ -0,0 +1,1176 @@
1
+ // src/types/encoding.ts
2
+ function required(...types) {
3
+ return { required: true, allowedTypes: types };
4
+ }
5
+ function optional(...types) {
6
+ return { required: false, allowedTypes: types };
7
+ }
8
+ var CHART_ENCODING_RULES = {
9
+ line: {
10
+ x: required("temporal", "ordinal"),
11
+ y: required("quantitative"),
12
+ color: optional("nominal", "ordinal"),
13
+ size: optional("quantitative"),
14
+ detail: optional("nominal")
15
+ },
16
+ area: {
17
+ x: required("temporal", "ordinal"),
18
+ y: required("quantitative"),
19
+ color: optional("nominal", "ordinal"),
20
+ size: optional("quantitative"),
21
+ detail: optional("nominal")
22
+ },
23
+ bar: {
24
+ x: required("quantitative"),
25
+ y: required("nominal", "ordinal"),
26
+ color: optional("nominal", "ordinal", "quantitative"),
27
+ size: optional("quantitative"),
28
+ detail: optional("nominal")
29
+ },
30
+ column: {
31
+ x: required("nominal", "ordinal", "temporal"),
32
+ y: required("quantitative"),
33
+ color: optional("nominal", "ordinal", "quantitative"),
34
+ size: optional("quantitative"),
35
+ detail: optional("nominal")
36
+ },
37
+ pie: {
38
+ x: optional(),
39
+ y: required("quantitative"),
40
+ color: required("nominal", "ordinal"),
41
+ size: optional("quantitative"),
42
+ detail: optional("nominal")
43
+ },
44
+ donut: {
45
+ x: optional(),
46
+ y: required("quantitative"),
47
+ color: required("nominal", "ordinal"),
48
+ size: optional("quantitative"),
49
+ detail: optional("nominal")
50
+ },
51
+ dot: {
52
+ x: required("quantitative"),
53
+ y: required("nominal", "ordinal"),
54
+ color: optional("nominal", "ordinal"),
55
+ size: optional("quantitative"),
56
+ detail: optional("nominal")
57
+ },
58
+ scatter: {
59
+ x: required("quantitative"),
60
+ y: required("quantitative"),
61
+ color: optional("nominal", "ordinal"),
62
+ size: optional("quantitative"),
63
+ detail: optional("nominal")
64
+ }
65
+ };
66
+ var GRAPH_ENCODING_RULES = {
67
+ nodeColor: { required: false, allowedTypes: ["nominal", "ordinal"] },
68
+ nodeSize: { required: false, allowedTypes: ["quantitative"] },
69
+ edgeColor: { required: false, allowedTypes: ["nominal", "ordinal"] },
70
+ edgeWidth: { required: false, allowedTypes: ["quantitative"] },
71
+ nodeLabel: { required: false, allowedTypes: [] }
72
+ };
73
+
74
+ // src/types/spec.ts
75
+ var CHART_TYPES = /* @__PURE__ */ new Set([
76
+ "line",
77
+ "area",
78
+ "bar",
79
+ "column",
80
+ "pie",
81
+ "donut",
82
+ "dot",
83
+ "scatter"
84
+ ]);
85
+ function isChartSpec(spec) {
86
+ return CHART_TYPES.has(spec.type);
87
+ }
88
+ function isTableSpec(spec) {
89
+ return spec.type === "table";
90
+ }
91
+ function isGraphSpec(spec) {
92
+ return spec.type === "graph";
93
+ }
94
+ function isTextAnnotation(annotation) {
95
+ return annotation.type === "text";
96
+ }
97
+ function isRangeAnnotation(annotation) {
98
+ return annotation.type === "range";
99
+ }
100
+ function isRefLineAnnotation(annotation) {
101
+ return annotation.type === "refline";
102
+ }
103
+
104
+ // src/colors/colorblind.ts
105
+ import { rgb } from "d3-color";
106
+ var PROTAN_MATRIX = [
107
+ [0.567, 0.433, 0],
108
+ [0.558, 0.442, 0],
109
+ [0, 0.242, 0.758]
110
+ ];
111
+ var DEUTAN_MATRIX = [
112
+ [0.625, 0.375, 0],
113
+ [0.7, 0.3, 0],
114
+ [0, 0.3, 0.7]
115
+ ];
116
+ var TRITAN_MATRIX = [
117
+ [0.95, 0.05, 0],
118
+ [0, 0.433, 0.567],
119
+ [0, 0.475, 0.525]
120
+ ];
121
+ var MATRICES = {
122
+ protanopia: PROTAN_MATRIX,
123
+ deuteranopia: DEUTAN_MATRIX,
124
+ tritanopia: TRITAN_MATRIX
125
+ };
126
+ function linearize(v) {
127
+ const s = v / 255;
128
+ return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
129
+ }
130
+ function delinearize(v) {
131
+ const s = v <= 31308e-7 ? v * 12.92 : 1.055 * v ** (1 / 2.4) - 0.055;
132
+ return Math.round(Math.max(0, Math.min(255, s * 255)));
133
+ }
134
+ function simulateColorBlindness(color, type) {
135
+ const c = rgb(color);
136
+ if (c == null) return color;
137
+ const lin = [linearize(c.r), linearize(c.g), linearize(c.b)];
138
+ const m = MATRICES[type];
139
+ const r = m[0][0] * lin[0] + m[0][1] * lin[1] + m[0][2] * lin[2];
140
+ const g = m[1][0] * lin[0] + m[1][1] * lin[1] + m[1][2] * lin[2];
141
+ const b = m[2][0] * lin[0] + m[2][1] * lin[1] + m[2][2] * lin[2];
142
+ return rgb(delinearize(r), delinearize(g), delinearize(b)).formatHex();
143
+ }
144
+ function checkPaletteDistinguishability(colors, type, minDistance = 30) {
145
+ const simulated = colors.map((c) => {
146
+ const s = rgb(simulateColorBlindness(c, type));
147
+ return s ? [s.r, s.g, s.b] : [0, 0, 0];
148
+ });
149
+ for (let i = 0; i < simulated.length; i++) {
150
+ for (let j = i + 1; j < simulated.length; j++) {
151
+ const dr = simulated[i][0] - simulated[j][0];
152
+ const dg = simulated[i][1] - simulated[j][1];
153
+ const db = simulated[i][2] - simulated[j][2];
154
+ const dist = Math.sqrt(dr * dr + dg * dg + db * db);
155
+ if (dist < minDistance) return false;
156
+ }
157
+ }
158
+ return true;
159
+ }
160
+
161
+ // src/colors/contrast.ts
162
+ import { rgb as rgb2 } from "d3-color";
163
+ function relativeLuminance(color) {
164
+ const c = rgb2(color);
165
+ if (c == null) return 0;
166
+ const srgb = [c.r / 255, c.g / 255, c.b / 255];
167
+ const linear = srgb.map((v) => v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4);
168
+ return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
169
+ }
170
+ function contrastRatio(fg, bg) {
171
+ const l1 = relativeLuminance(fg);
172
+ const l2 = relativeLuminance(bg);
173
+ const lighter = Math.max(l1, l2);
174
+ const darker = Math.min(l1, l2);
175
+ return (lighter + 0.05) / (darker + 0.05);
176
+ }
177
+ function meetsAA(fg, bg, largeText = false) {
178
+ const ratio = contrastRatio(fg, bg);
179
+ return largeText ? ratio >= 3 : ratio >= 4.5;
180
+ }
181
+ function findAccessibleColor(baseColor, bg, targetRatio = 4.5) {
182
+ if (contrastRatio(baseColor, bg) >= targetRatio) {
183
+ return baseColor;
184
+ }
185
+ const c = rgb2(baseColor);
186
+ if (c == null) return baseColor;
187
+ const bgLum = relativeLuminance(bg);
188
+ const bgIsLight = bgLum > 0.5;
189
+ let lo = 0;
190
+ let hi = 1;
191
+ let best = baseColor;
192
+ for (let i = 0; i < 20; i++) {
193
+ const mid = (lo + hi) / 2;
194
+ const adjusted = bgIsLight ? rgb2(c.r * (1 - mid), c.g * (1 - mid), c.b * (1 - mid)) : rgb2(c.r + (255 - c.r) * mid, c.g + (255 - c.g) * mid, c.b + (255 - c.b) * mid);
195
+ const hex = adjusted.formatHex();
196
+ const ratio = contrastRatio(hex, bg);
197
+ if (ratio >= targetRatio) {
198
+ best = hex;
199
+ hi = mid;
200
+ } else {
201
+ lo = mid;
202
+ }
203
+ }
204
+ return best;
205
+ }
206
+
207
+ // src/colors/palettes.ts
208
+ var CATEGORICAL_PALETTE = [
209
+ "#1b7fa3",
210
+ // teal-blue (primary)
211
+ "#c44e52",
212
+ // warm red (secondary)
213
+ "#6a9f58",
214
+ // softer green (tertiary)
215
+ "#d47215",
216
+ // orange
217
+ "#507e79",
218
+ // muted teal
219
+ "#9a6a8d",
220
+ // purple
221
+ "#c4636b",
222
+ // rose
223
+ "#9c755f",
224
+ // brown
225
+ "#a88f22",
226
+ // olive gold
227
+ "#858078"
228
+ // warm gray
229
+ ];
230
+ var SEQUENTIAL_BLUE = {
231
+ name: "blue",
232
+ stops: ["#deebf7", "#c6dbef", "#9ecae1", "#6baed6", "#3182bd", "#08519c"]
233
+ };
234
+ var SEQUENTIAL_GREEN = {
235
+ name: "green",
236
+ stops: ["#e5f5e0", "#c7e9c0", "#a1d99b", "#74c476", "#31a354", "#006d2c"]
237
+ };
238
+ var SEQUENTIAL_ORANGE = {
239
+ name: "orange",
240
+ stops: ["#fee6ce", "#fdd0a2", "#fdae6b", "#fd8d3c", "#e6550d", "#a63603"]
241
+ };
242
+ var SEQUENTIAL_PURPLE = {
243
+ name: "purple",
244
+ stops: ["#efedf5", "#dadaeb", "#bcbddc", "#9e9ac8", "#756bb1", "#54278f"]
245
+ };
246
+ var SEQUENTIAL_PALETTES = {
247
+ blue: [...SEQUENTIAL_BLUE.stops],
248
+ green: [...SEQUENTIAL_GREEN.stops],
249
+ orange: [...SEQUENTIAL_ORANGE.stops],
250
+ purple: [...SEQUENTIAL_PURPLE.stops]
251
+ };
252
+ var DIVERGING_RED_BLUE = {
253
+ name: "redBlue",
254
+ stops: [
255
+ "#b2182b",
256
+ // strong red
257
+ "#d6604d",
258
+ // medium red
259
+ "#f4a582",
260
+ // light red
261
+ "#f7f7f7",
262
+ // neutral
263
+ "#92c5de",
264
+ // light blue
265
+ "#4393c3",
266
+ // medium blue
267
+ "#2166ac"
268
+ // strong blue
269
+ ]
270
+ };
271
+ var DIVERGING_BROWN_TEAL = {
272
+ name: "brownTeal",
273
+ stops: [
274
+ "#8c510a",
275
+ // strong brown
276
+ "#bf812d",
277
+ // medium brown
278
+ "#dfc27d",
279
+ // light brown
280
+ "#f6e8c3",
281
+ // neutral
282
+ "#80cdc1",
283
+ // light teal
284
+ "#35978f",
285
+ // medium teal
286
+ "#01665e"
287
+ // strong teal
288
+ ]
289
+ };
290
+ var DIVERGING_PALETTES = {
291
+ redBlue: [...DIVERGING_RED_BLUE.stops],
292
+ brownTeal: [...DIVERGING_BROWN_TEAL.stops]
293
+ };
294
+
295
+ // src/theme/dark-mode.ts
296
+ import { hsl, rgb as rgb3 } from "d3-color";
297
+ var DARK_BG = "#1a1a2e";
298
+ var DARK_TEXT = "#e0e0e0";
299
+ function adaptColorForDarkMode(color, lightBg, darkBg) {
300
+ const originalRatio = contrastRatio(color, lightBg);
301
+ const c = hsl(color);
302
+ if (c == null || Number.isNaN(c.h)) {
303
+ const r = rgb3(color);
304
+ if (r == null) return color;
305
+ const darkBgLum = _luminanceFromHex(darkBg);
306
+ const isLight = darkBgLum < 0.5;
307
+ if (isLight) return color;
308
+ const inverted = hsl(color);
309
+ if (inverted == null) return color;
310
+ inverted.l = 1 - inverted.l;
311
+ return inverted.formatHex();
312
+ }
313
+ let lo = 0;
314
+ let hi = 1;
315
+ let bestColor = color;
316
+ let bestDiff = Infinity;
317
+ for (let i = 0; i < 20; i++) {
318
+ const mid = (lo + hi) / 2;
319
+ const candidate = hsl(c.h, c.s, mid);
320
+ const hex = candidate.formatHex();
321
+ const ratio = contrastRatio(hex, darkBg);
322
+ const diff = Math.abs(ratio - originalRatio);
323
+ if (diff < bestDiff) {
324
+ bestDiff = diff;
325
+ bestColor = hex;
326
+ }
327
+ if (ratio < originalRatio) {
328
+ lo = mid;
329
+ } else {
330
+ hi = mid;
331
+ }
332
+ }
333
+ return bestColor;
334
+ }
335
+ function _luminanceFromHex(color) {
336
+ const c = rgb3(color);
337
+ if (c == null) return 0;
338
+ return (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) / 255;
339
+ }
340
+ function adaptTheme(theme) {
341
+ const lightBg = theme.colors.background;
342
+ const darkBg = DARK_BG;
343
+ return {
344
+ ...theme,
345
+ isDark: true,
346
+ colors: {
347
+ ...theme.colors,
348
+ background: darkBg,
349
+ text: DARK_TEXT,
350
+ gridline: "#333344",
351
+ axis: "#888899",
352
+ annotationFill: "rgba(255,255,255,0.08)",
353
+ annotationText: "#bbbbcc",
354
+ categorical: theme.colors.categorical.map((c) => adaptColorForDarkMode(c, lightBg, darkBg))
355
+ // Sequential and diverging palettes are kept as-is since they're
356
+ // typically used for fills where the lightness range still works.
357
+ // If a specific use case needs adaptation, it can be done per-color.
358
+ },
359
+ chrome: {
360
+ title: { ...theme.chrome.title, color: DARK_TEXT },
361
+ subtitle: { ...theme.chrome.subtitle, color: "#aaaaaa" },
362
+ source: { ...theme.chrome.source, color: "#888888" },
363
+ byline: { ...theme.chrome.byline, color: "#888888" },
364
+ footer: { ...theme.chrome.footer, color: "#888888" }
365
+ }
366
+ };
367
+ }
368
+
369
+ // src/theme/defaults.ts
370
+ var DEFAULT_THEME = {
371
+ colors: {
372
+ categorical: [...CATEGORICAL_PALETTE],
373
+ sequential: SEQUENTIAL_PALETTES,
374
+ diverging: DIVERGING_PALETTES,
375
+ background: "#ffffff",
376
+ text: "#1d1d1d",
377
+ gridline: "#e8e8e8",
378
+ axis: "#888888",
379
+ annotationFill: "rgba(0,0,0,0.04)",
380
+ annotationText: "#555555"
381
+ },
382
+ fonts: {
383
+ family: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
384
+ mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
385
+ sizes: {
386
+ title: 22,
387
+ subtitle: 15,
388
+ body: 13,
389
+ small: 11,
390
+ axisTick: 11
391
+ },
392
+ weights: {
393
+ normal: 400,
394
+ medium: 500,
395
+ semibold: 600,
396
+ bold: 700
397
+ }
398
+ },
399
+ spacing: {
400
+ padding: 12,
401
+ chromeGap: 4,
402
+ chromeToChart: 8,
403
+ chartToFooter: 8,
404
+ axisMargin: 6
405
+ },
406
+ borderRadius: 4,
407
+ chrome: {
408
+ title: {
409
+ fontSize: 22,
410
+ fontWeight: 700,
411
+ color: "#333333",
412
+ lineHeight: 1.3
413
+ },
414
+ subtitle: {
415
+ fontSize: 15,
416
+ fontWeight: 400,
417
+ color: "#666666",
418
+ lineHeight: 1.4
419
+ },
420
+ source: {
421
+ fontSize: 12,
422
+ fontWeight: 400,
423
+ color: "#999999",
424
+ lineHeight: 1.3
425
+ },
426
+ byline: {
427
+ fontSize: 12,
428
+ fontWeight: 400,
429
+ color: "#999999",
430
+ lineHeight: 1.3
431
+ },
432
+ footer: {
433
+ fontSize: 12,
434
+ fontWeight: 400,
435
+ color: "#999999",
436
+ lineHeight: 1.3
437
+ }
438
+ }
439
+ };
440
+
441
+ // src/theme/resolve.ts
442
+ function deepMerge(target, source) {
443
+ const result = { ...target };
444
+ for (const key of Object.keys(source)) {
445
+ const sourceVal = source[key];
446
+ const targetVal = target[key];
447
+ if (sourceVal !== void 0 && sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
448
+ result[key] = deepMerge(targetVal, sourceVal);
449
+ } else if (sourceVal !== void 0) {
450
+ result[key] = sourceVal;
451
+ }
452
+ }
453
+ return result;
454
+ }
455
+ function themeConfigToPartial(config) {
456
+ const partial = {};
457
+ if (config.colors) {
458
+ const colors = {};
459
+ if (config.colors.categorical) colors.categorical = config.colors.categorical;
460
+ if (config.colors.sequential) colors.sequential = config.colors.sequential;
461
+ if (config.colors.diverging) colors.diverging = config.colors.diverging;
462
+ if (config.colors.background) colors.background = config.colors.background;
463
+ if (config.colors.text) colors.text = config.colors.text;
464
+ if (config.colors.gridline) colors.gridline = config.colors.gridline;
465
+ if (config.colors.axis) colors.axis = config.colors.axis;
466
+ partial.colors = colors;
467
+ }
468
+ if (config.fonts) {
469
+ const fonts = {};
470
+ if (config.fonts.family) fonts.family = config.fonts.family;
471
+ if (config.fonts.mono) fonts.mono = config.fonts.mono;
472
+ partial.fonts = fonts;
473
+ }
474
+ if (config.spacing) {
475
+ const spacing = {};
476
+ if (config.spacing.padding !== void 0) spacing.padding = config.spacing.padding;
477
+ if (config.spacing.chromeGap !== void 0) spacing.chromeGap = config.spacing.chromeGap;
478
+ partial.spacing = spacing;
479
+ }
480
+ if (config.borderRadius !== void 0) {
481
+ partial.borderRadius = config.borderRadius;
482
+ }
483
+ return partial;
484
+ }
485
+ function relativeLuminance2(hex) {
486
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
487
+ if (!m) return 0;
488
+ const r = parseInt(m[1].slice(0, 2), 16) / 255;
489
+ const g = parseInt(m[1].slice(2, 4), 16) / 255;
490
+ const b = parseInt(m[1].slice(4, 6), 16) / 255;
491
+ const toLinear = (c) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
492
+ return 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
493
+ }
494
+ function isDarkBackground(hex) {
495
+ return relativeLuminance2(hex) < 0.2;
496
+ }
497
+ function adaptChromeForDarkBg(theme, textColor) {
498
+ const light = DEFAULT_THEME.chrome;
499
+ return {
500
+ ...theme,
501
+ chrome: {
502
+ title: {
503
+ ...theme.chrome.title,
504
+ color: theme.chrome.title.color === light.title.color ? textColor : theme.chrome.title.color
505
+ },
506
+ subtitle: {
507
+ ...theme.chrome.subtitle,
508
+ color: theme.chrome.subtitle.color === light.subtitle.color ? adjustOpacity(textColor, 0.7) : theme.chrome.subtitle.color
509
+ },
510
+ source: {
511
+ ...theme.chrome.source,
512
+ color: theme.chrome.source.color === light.source.color ? adjustOpacity(textColor, 0.5) : theme.chrome.source.color
513
+ },
514
+ byline: {
515
+ ...theme.chrome.byline,
516
+ color: theme.chrome.byline.color === light.byline.color ? adjustOpacity(textColor, 0.5) : theme.chrome.byline.color
517
+ },
518
+ footer: {
519
+ ...theme.chrome.footer,
520
+ color: theme.chrome.footer.color === light.footer.color ? adjustOpacity(textColor, 0.5) : theme.chrome.footer.color
521
+ }
522
+ }
523
+ };
524
+ }
525
+ function adjustOpacity(hex, opacity) {
526
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
527
+ if (!m) return hex;
528
+ const r = parseInt(m[1].slice(0, 2), 16);
529
+ const g = parseInt(m[1].slice(2, 4), 16);
530
+ const b = parseInt(m[1].slice(4, 6), 16);
531
+ const mix = (c) => Math.round(c * opacity + 128 * (1 - opacity));
532
+ const toHex = (n) => n.toString(16).padStart(2, "0");
533
+ return `#${toHex(mix(r))}${toHex(mix(g))}${toHex(mix(b))}`;
534
+ }
535
+ function resolveTheme(userTheme, base = DEFAULT_THEME) {
536
+ let merged = userTheme ? deepMerge(base, themeConfigToPartial(userTheme)) : { ...base };
537
+ const dark = isDarkBackground(merged.colors.background);
538
+ if (dark) {
539
+ merged = adaptChromeForDarkBg(merged, merged.colors.text);
540
+ }
541
+ return {
542
+ ...merged,
543
+ isDark: dark
544
+ };
545
+ }
546
+
547
+ // src/layout/text-measure.ts
548
+ var AVG_CHAR_WIDTH_RATIO = 0.55;
549
+ var WEIGHT_ADJUSTMENT = {
550
+ 100: 0.9,
551
+ 200: 0.92,
552
+ 300: 0.95,
553
+ 400: 1,
554
+ 500: 1.02,
555
+ 600: 1.05,
556
+ 700: 1.08,
557
+ 800: 1.1,
558
+ 900: 1.12
559
+ };
560
+ function estimateTextWidth(text, fontSize, fontWeight = 400) {
561
+ const weightFactor = WEIGHT_ADJUSTMENT[fontWeight] ?? 1;
562
+ return text.length * fontSize * AVG_CHAR_WIDTH_RATIO * weightFactor;
563
+ }
564
+ function estimateTextHeight(fontSize, lineCount = 1, lineHeight = 1.3) {
565
+ return fontSize * lineHeight * lineCount;
566
+ }
567
+
568
+ // src/layout/chrome.ts
569
+ function normalizeChromeText(value) {
570
+ if (value === void 0) return null;
571
+ if (typeof value === "string") return { text: value };
572
+ return { text: value.text, style: value.style, offset: value.offset };
573
+ }
574
+ function buildTextStyle(defaults, fontFamily, textColor, overrides) {
575
+ return {
576
+ fontFamily: overrides?.fontFamily ?? fontFamily,
577
+ fontSize: overrides?.fontSize ?? defaults.fontSize,
578
+ fontWeight: overrides?.fontWeight ?? defaults.fontWeight,
579
+ fill: overrides?.color ?? textColor ?? defaults.color,
580
+ lineHeight: defaults.lineHeight,
581
+ textAnchor: "start",
582
+ dominantBaseline: "hanging"
583
+ };
584
+ }
585
+ function measureWidth(text, style, measureText) {
586
+ if (measureText) {
587
+ return measureText(text, style.fontSize, style.fontWeight).width;
588
+ }
589
+ return estimateTextWidth(text, style.fontSize, style.fontWeight);
590
+ }
591
+ function estimateLineCount(text, style, maxWidth, measureText) {
592
+ const fullWidth = measureWidth(text, style, measureText);
593
+ if (fullWidth <= maxWidth) return 1;
594
+ return Math.ceil(fullWidth / maxWidth);
595
+ }
596
+ function computeChrome(chrome, theme, width, measureText) {
597
+ if (!chrome) {
598
+ return { topHeight: 0, bottomHeight: 0 };
599
+ }
600
+ const padding = theme.spacing.padding;
601
+ const chromeGap = theme.spacing.chromeGap;
602
+ const maxWidth = width - padding * 2;
603
+ const fontFamily = theme.fonts.family;
604
+ let topY = padding;
605
+ const topElements = {};
606
+ const titleNorm = normalizeChromeText(chrome.title);
607
+ if (titleNorm) {
608
+ const style = buildTextStyle(
609
+ theme.chrome.title,
610
+ fontFamily,
611
+ theme.chrome.title.color,
612
+ titleNorm.style
613
+ );
614
+ const lineCount = estimateLineCount(titleNorm.text, style, maxWidth, measureText);
615
+ const element = {
616
+ text: titleNorm.text,
617
+ x: padding + (titleNorm.offset?.dx ?? 0),
618
+ y: topY + (titleNorm.offset?.dy ?? 0),
619
+ maxWidth,
620
+ style
621
+ };
622
+ topElements.title = element;
623
+ topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
624
+ }
625
+ const subtitleNorm = normalizeChromeText(chrome.subtitle);
626
+ if (subtitleNorm) {
627
+ const style = buildTextStyle(
628
+ theme.chrome.subtitle,
629
+ fontFamily,
630
+ theme.chrome.subtitle.color,
631
+ subtitleNorm.style
632
+ );
633
+ const lineCount = estimateLineCount(subtitleNorm.text, style, maxWidth, measureText);
634
+ const element = {
635
+ text: subtitleNorm.text,
636
+ x: padding + (subtitleNorm.offset?.dx ?? 0),
637
+ y: topY + (subtitleNorm.offset?.dy ?? 0),
638
+ maxWidth,
639
+ style
640
+ };
641
+ topElements.subtitle = element;
642
+ topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
643
+ }
644
+ const hasTopChrome = titleNorm || subtitleNorm;
645
+ const topHeight = hasTopChrome ? topY - padding + theme.spacing.chromeToChart - chromeGap : 0;
646
+ const bottomElements = {};
647
+ let bottomHeight = 0;
648
+ const bottomItems = [];
649
+ const sourceNorm = normalizeChromeText(chrome.source);
650
+ if (sourceNorm) {
651
+ bottomItems.push({
652
+ key: "source",
653
+ norm: sourceNorm,
654
+ defaults: theme.chrome.source
655
+ });
656
+ }
657
+ const bylineNorm = normalizeChromeText(chrome.byline);
658
+ if (bylineNorm) {
659
+ bottomItems.push({
660
+ key: "byline",
661
+ norm: bylineNorm,
662
+ defaults: theme.chrome.byline
663
+ });
664
+ }
665
+ const footerNorm = normalizeChromeText(chrome.footer);
666
+ if (footerNorm) {
667
+ bottomItems.push({
668
+ key: "footer",
669
+ norm: footerNorm,
670
+ defaults: theme.chrome.footer
671
+ });
672
+ }
673
+ if (bottomItems.length > 0) {
674
+ bottomHeight += theme.spacing.chartToFooter;
675
+ for (const item of bottomItems) {
676
+ const style = buildTextStyle(item.defaults, fontFamily, item.defaults.color, item.norm.style);
677
+ const lineCount = estimateLineCount(item.norm.text, style, maxWidth, measureText);
678
+ const height = estimateTextHeight(style.fontSize, lineCount, style.lineHeight);
679
+ bottomElements[item.key] = {
680
+ text: item.norm.text,
681
+ x: padding + (item.norm.offset?.dx ?? 0),
682
+ y: bottomHeight + (item.norm.offset?.dy ?? 0),
683
+ // offset from where bottom chrome starts
684
+ maxWidth,
685
+ style
686
+ };
687
+ bottomHeight += height + chromeGap;
688
+ }
689
+ bottomHeight -= chromeGap;
690
+ bottomHeight += padding;
691
+ }
692
+ return {
693
+ topHeight,
694
+ bottomHeight,
695
+ ...topElements,
696
+ ...bottomElements
697
+ };
698
+ }
699
+
700
+ // src/responsive/breakpoints.ts
701
+ var BREAKPOINT_COMPACT_MAX = 400;
702
+ var BREAKPOINT_MEDIUM_MAX = 700;
703
+ function getBreakpoint(width) {
704
+ if (width < BREAKPOINT_COMPACT_MAX) return "compact";
705
+ if (width <= BREAKPOINT_MEDIUM_MAX) return "medium";
706
+ return "full";
707
+ }
708
+ function getLayoutStrategy(breakpoint) {
709
+ switch (breakpoint) {
710
+ case "compact":
711
+ return {
712
+ labelMode: "none",
713
+ legendPosition: "top",
714
+ annotationPosition: "tooltip-only",
715
+ axisLabelDensity: "minimal"
716
+ };
717
+ case "medium":
718
+ return {
719
+ labelMode: "important",
720
+ legendPosition: "top",
721
+ annotationPosition: "inline",
722
+ axisLabelDensity: "reduced"
723
+ };
724
+ case "full":
725
+ return {
726
+ labelMode: "all",
727
+ legendPosition: "right",
728
+ annotationPosition: "inline",
729
+ axisLabelDensity: "full"
730
+ };
731
+ }
732
+ }
733
+
734
+ // src/labels/collision.ts
735
+ var PRIORITY_ORDER = {
736
+ data: 0,
737
+ annotation: 1,
738
+ axis: 2
739
+ };
740
+ function detectCollision(a, b) {
741
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
742
+ }
743
+ var OFFSET_STRATEGIES = [
744
+ { dx: 0, dy: 0 },
745
+ // original position
746
+ { dx: 0, dy: -1.2 },
747
+ // above (factor of height)
748
+ { dx: 0, dy: 1.2 },
749
+ // below
750
+ { dx: 1.1, dy: 0 },
751
+ // right
752
+ { dx: -1.1, dy: 0 },
753
+ // left
754
+ { dx: 1.1, dy: -1.2 },
755
+ // upper-right
756
+ { dx: -1.1, dy: -1.2 }
757
+ // upper-left
758
+ ];
759
+ function resolveCollisions(labels) {
760
+ const sorted = [...labels].sort(
761
+ (a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]
762
+ );
763
+ const placed = [];
764
+ const results = [];
765
+ for (const label of sorted) {
766
+ let bestRect = null;
767
+ let bestX = label.anchorX;
768
+ let bestY = label.anchorY;
769
+ for (const offset of OFFSET_STRATEGIES) {
770
+ const candidateX = label.anchorX + offset.dx * label.width;
771
+ const candidateY = label.anchorY + offset.dy * label.height;
772
+ const candidateRect = {
773
+ x: candidateX,
774
+ y: candidateY,
775
+ width: label.width,
776
+ height: label.height
777
+ };
778
+ const hasCollision = placed.some((p) => detectCollision(candidateRect, p));
779
+ if (!hasCollision) {
780
+ bestRect = candidateRect;
781
+ bestX = candidateX;
782
+ bestY = candidateY;
783
+ break;
784
+ }
785
+ }
786
+ if (bestRect) {
787
+ placed.push(bestRect);
788
+ const needsConnector = bestX !== label.anchorX || bestY !== label.anchorY;
789
+ results.push({
790
+ text: label.text,
791
+ x: bestX,
792
+ y: bestY,
793
+ style: label.style,
794
+ visible: true,
795
+ connector: needsConnector ? {
796
+ from: { x: bestX, y: bestY },
797
+ to: { x: label.anchorX, y: label.anchorY },
798
+ stroke: label.style.fill,
799
+ style: "straight"
800
+ } : void 0
801
+ });
802
+ } else {
803
+ results.push({
804
+ text: label.text,
805
+ x: label.anchorX,
806
+ y: label.anchorY,
807
+ style: label.style,
808
+ visible: false
809
+ });
810
+ }
811
+ }
812
+ return results;
813
+ }
814
+
815
+ // src/locale/format.ts
816
+ import { format as d3Format } from "d3-format";
817
+ import { timeFormat, utcFormat } from "d3-time-format";
818
+ function formatNumber(value, _locale) {
819
+ if (!Number.isFinite(value)) return String(value);
820
+ if (Number.isInteger(value)) {
821
+ return d3Format(",")(value);
822
+ }
823
+ return d3Format(",.2f")(value);
824
+ }
825
+ var ABBREVIATIONS = [
826
+ { threshold: 1e12, suffix: "T", divisor: 1e12 },
827
+ { threshold: 1e9, suffix: "B", divisor: 1e9 },
828
+ { threshold: 1e6, suffix: "M", divisor: 1e6 },
829
+ { threshold: 1e3, suffix: "K", divisor: 1e3 }
830
+ ];
831
+ function abbreviateNumber(value) {
832
+ if (!Number.isFinite(value)) return String(value);
833
+ const absValue = Math.abs(value);
834
+ const sign = value < 0 ? "-" : "";
835
+ for (const { threshold, suffix, divisor } of ABBREVIATIONS) {
836
+ if (absValue >= threshold) {
837
+ const abbreviated = absValue / divisor;
838
+ const formatted = abbreviated % 1 === 0 ? String(abbreviated) : d3Format(".1f")(abbreviated);
839
+ return `${sign}${formatted}${suffix}`;
840
+ }
841
+ }
842
+ return formatNumber(value);
843
+ }
844
+ var GRANULARITY_FORMATS = {
845
+ year: "%Y",
846
+ quarter: "",
847
+ // Quarter is always special-cased in formatDate() below
848
+ month: "%b %Y",
849
+ week: "%b %d",
850
+ day: "%b %d, %Y",
851
+ hour: "%b %d %H:%M",
852
+ minute: "%H:%M"
853
+ };
854
+ function formatDate(value, _locale, granularity) {
855
+ const date = value instanceof Date ? value : new Date(value);
856
+ if (Number.isNaN(date.getTime())) return String(value);
857
+ const gran = granularity ?? inferGranularity(date);
858
+ if (gran === "quarter") {
859
+ const q = Math.ceil((date.getMonth() + 1) / 3);
860
+ return `Q${q} ${date.getFullYear()}`;
861
+ }
862
+ const formatStr = GRANULARITY_FORMATS[gran];
863
+ if (["year", "month", "day"].includes(gran)) {
864
+ return utcFormat(formatStr)(date);
865
+ }
866
+ return timeFormat(formatStr)(date);
867
+ }
868
+ function inferGranularity(date) {
869
+ if (date.getHours() !== 0 || date.getMinutes() !== 0) {
870
+ return date.getMinutes() !== 0 ? "minute" : "hour";
871
+ }
872
+ if (date.getDate() !== 1) return "day";
873
+ if (date.getMonth() !== 0) return "month";
874
+ return "year";
875
+ }
876
+
877
+ // src/accessibility/alt-text.ts
878
+ var CHART_TYPE_NAMES = {
879
+ line: "Line chart",
880
+ area: "Area chart",
881
+ bar: "Bar chart",
882
+ column: "Column chart",
883
+ pie: "Pie chart",
884
+ donut: "Donut chart",
885
+ dot: "Dot plot",
886
+ scatter: "Scatter plot"
887
+ };
888
+ function generateAltText(spec, data) {
889
+ const chartName = CHART_TYPE_NAMES[spec.type] ?? `${spec.type} chart`;
890
+ const parts = [chartName];
891
+ const title = spec.chrome?.title;
892
+ if (title) {
893
+ const titleText = typeof title === "string" ? title : title.text;
894
+ parts.push(`showing ${titleText}`);
895
+ }
896
+ if (spec.encoding.x && data.length > 0) {
897
+ const field = spec.encoding.x.field;
898
+ const values = data.map((d) => d[field]).filter((v) => v != null);
899
+ if (values.length > 0) {
900
+ if (spec.encoding.x.type === "temporal") {
901
+ const dates = values.map((v) => v instanceof Date ? v : new Date(String(v)));
902
+ const validDates = dates.filter((d) => !Number.isNaN(d.getTime()));
903
+ if (validDates.length >= 2) {
904
+ validDates.sort((a, b) => a.getTime() - b.getTime());
905
+ const first = validDates[0].getUTCFullYear();
906
+ const last = validDates[validDates.length - 1].getUTCFullYear();
907
+ if (first !== last) {
908
+ parts.push(`from ${first} to ${last}`);
909
+ }
910
+ }
911
+ } else if (spec.encoding.x.type === "nominal" || spec.encoding.x.type === "ordinal") {
912
+ const uniqueValues = [...new Set(values.map(String))];
913
+ parts.push(`across ${uniqueValues.length} categories`);
914
+ }
915
+ }
916
+ }
917
+ if (spec.encoding.color && data.length > 0) {
918
+ const colorField = spec.encoding.color.field;
919
+ const uniqueSeries = [...new Set(data.map((d) => String(d[colorField])).filter(Boolean))];
920
+ if (uniqueSeries.length > 0) {
921
+ parts.push(`with ${uniqueSeries.length} series (${uniqueSeries.join(", ")})`);
922
+ }
923
+ }
924
+ parts.push(`(${data.length} data points)`);
925
+ return parts.join(" ");
926
+ }
927
+ function generateDataTable(spec, data) {
928
+ const fields = [];
929
+ const encoding = spec.encoding;
930
+ if (encoding.x) fields.push(encoding.x.field);
931
+ if (encoding.y) fields.push(encoding.y.field);
932
+ if (encoding.color) fields.push(encoding.color.field);
933
+ if (encoding.size) fields.push(encoding.size.field);
934
+ const uniqueFields = [...new Set(fields)];
935
+ if (uniqueFields.length === 0) return [];
936
+ const headers = uniqueFields;
937
+ const rows = data.map((row) => uniqueFields.map((field) => row[field] ?? ""));
938
+ return [headers, ...rows];
939
+ }
940
+
941
+ // src/accessibility/aria.ts
942
+ function generateAriaLabels(marks) {
943
+ const labels = /* @__PURE__ */ new Map();
944
+ for (let i = 0; i < marks.length; i++) {
945
+ const mark = marks[i];
946
+ const key = `mark-${i}`;
947
+ switch (mark.type) {
948
+ case "line": {
949
+ const series = mark.seriesKey ?? "Series";
950
+ const pointCount = mark.points.length;
951
+ labels.set(key, `Line series: ${series} with ${pointCount} points`);
952
+ break;
953
+ }
954
+ case "area": {
955
+ const series = mark.seriesKey ?? "Area";
956
+ labels.set(key, `Area series: ${series}`);
957
+ break;
958
+ }
959
+ case "rect": {
960
+ const dataEntries = Object.entries(mark.data).filter(([k]) => !k.startsWith("_"));
961
+ const description = dataEntries.map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
962
+ labels.set(key, `Data point: ${description}`);
963
+ break;
964
+ }
965
+ case "arc": {
966
+ const dataEntries = Object.entries(mark.data).filter(([k]) => !k.startsWith("_"));
967
+ const description = dataEntries.map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
968
+ labels.set(key, `Slice: ${description}`);
969
+ break;
970
+ }
971
+ case "point": {
972
+ const dataEntries = Object.entries(mark.data).filter(([k]) => !k.startsWith("_"));
973
+ const description = dataEntries.map(([k, v]) => `${k}: ${formatValue(v)}`).join(", ");
974
+ labels.set(key, `Data point: ${description}`);
975
+ break;
976
+ }
977
+ }
978
+ }
979
+ return labels;
980
+ }
981
+ function formatValue(value) {
982
+ if (value == null) return "N/A";
983
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
984
+ if (typeof value === "number") {
985
+ return Number.isInteger(value) ? String(value) : value.toFixed(2);
986
+ }
987
+ return String(value);
988
+ }
989
+
990
+ // src/helpers/spec-builders.ts
991
+ var ISO_DATE_RE = /^\d{4}(-\d{2}(-\d{2}(T\d{2}(:\d{2}(:\d{2})?)?)?)?)?$/;
992
+ function inferFieldType(data, field) {
993
+ const sampleSize = Math.min(data.length, 20);
994
+ let hasNumber = false;
995
+ let hasDateString = false;
996
+ let hasOtherString = false;
997
+ for (let i = 0; i < sampleSize; i++) {
998
+ const value = data[i][field];
999
+ if (value == null) continue;
1000
+ if (typeof value === "number") {
1001
+ hasNumber = true;
1002
+ } else if (typeof value === "string") {
1003
+ if (ISO_DATE_RE.test(value) && !Number.isNaN(Date.parse(value))) {
1004
+ hasDateString = true;
1005
+ } else {
1006
+ hasOtherString = true;
1007
+ }
1008
+ } else if (value instanceof Date) {
1009
+ hasDateString = true;
1010
+ } else {
1011
+ hasOtherString = true;
1012
+ }
1013
+ }
1014
+ if (hasNumber && !hasDateString && !hasOtherString) return "quantitative";
1015
+ if (hasDateString && !hasNumber && !hasOtherString) return "temporal";
1016
+ return "nominal";
1017
+ }
1018
+ function resolveField(ref, data) {
1019
+ if (typeof ref === "string") {
1020
+ return { field: ref, type: inferFieldType(data, ref) };
1021
+ }
1022
+ return ref;
1023
+ }
1024
+ function buildEncoding(channels, options, data) {
1025
+ const encoding = { ...channels };
1026
+ if (options?.color && data) {
1027
+ encoding.color = resolveField(options.color, data);
1028
+ }
1029
+ if (options?.size && data) {
1030
+ encoding.size = resolveField(options.size, data);
1031
+ }
1032
+ return encoding;
1033
+ }
1034
+ function buildChartSpec(type, data, encoding, options) {
1035
+ const spec = { type, data, encoding };
1036
+ if (options?.chrome) spec.chrome = options.chrome;
1037
+ if (options?.annotations) spec.annotations = options.annotations;
1038
+ if (options?.responsive !== void 0) spec.responsive = options.responsive;
1039
+ if (options?.theme) spec.theme = options.theme;
1040
+ if (options?.darkMode) spec.darkMode = options.darkMode;
1041
+ return spec;
1042
+ }
1043
+ function lineChart(data, x, y, options) {
1044
+ const xChannel = resolveField(x, data);
1045
+ const yChannel = resolveField(y, data);
1046
+ const encoding = buildEncoding({ x: xChannel, y: yChannel }, options, data);
1047
+ return buildChartSpec("line", data, encoding, options);
1048
+ }
1049
+ function barChart(data, category, value, options) {
1050
+ const categoryChannel = resolveField(category, data);
1051
+ const valueChannel = resolveField(value, data);
1052
+ const encoding = buildEncoding({ x: valueChannel, y: categoryChannel }, options, data);
1053
+ return buildChartSpec("bar", data, encoding, options);
1054
+ }
1055
+ function columnChart(data, x, y, options) {
1056
+ const xChannel = resolveField(x, data);
1057
+ const yChannel = resolveField(y, data);
1058
+ const encoding = buildEncoding({ x: xChannel, y: yChannel }, options, data);
1059
+ return buildChartSpec("column", data, encoding, options);
1060
+ }
1061
+ function pieChart(data, category, value, options) {
1062
+ const categoryChannel = resolveField(category, data);
1063
+ const valueChannel = resolveField(value, data);
1064
+ const encoding = {
1065
+ y: valueChannel,
1066
+ color: categoryChannel
1067
+ };
1068
+ if (options?.size && data) {
1069
+ encoding.size = resolveField(options.size, data);
1070
+ }
1071
+ return buildChartSpec("pie", data, encoding, options);
1072
+ }
1073
+ function areaChart(data, x, y, options) {
1074
+ const xChannel = resolveField(x, data);
1075
+ const yChannel = resolveField(y, data);
1076
+ const encoding = buildEncoding({ x: xChannel, y: yChannel }, options, data);
1077
+ return buildChartSpec("area", data, encoding, options);
1078
+ }
1079
+ function donutChart(data, category, value, options) {
1080
+ const categoryChannel = resolveField(category, data);
1081
+ const valueChannel = resolveField(value, data);
1082
+ const encoding = {
1083
+ y: valueChannel,
1084
+ color: categoryChannel
1085
+ };
1086
+ if (options?.size && data) {
1087
+ encoding.size = resolveField(options.size, data);
1088
+ }
1089
+ return buildChartSpec("donut", data, encoding, options);
1090
+ }
1091
+ function dotChart(data, x, y, options) {
1092
+ const xChannel = resolveField(x, data);
1093
+ const yChannel = resolveField(y, data);
1094
+ const encoding = buildEncoding({ x: xChannel, y: yChannel }, options, data);
1095
+ return buildChartSpec("dot", data, encoding, options);
1096
+ }
1097
+ function scatterChart(data, x, y, options) {
1098
+ const xChannel = resolveField(x, data);
1099
+ const yChannel = resolveField(y, data);
1100
+ const encoding = buildEncoding({ x: xChannel, y: yChannel }, options, data);
1101
+ return buildChartSpec("scatter", data, encoding, options);
1102
+ }
1103
+ function dataTable(data, options) {
1104
+ let columns = options?.columns;
1105
+ if (!columns && data.length > 0) {
1106
+ columns = Object.keys(data[0]).map((key) => {
1107
+ const fieldType = inferFieldType(data, key);
1108
+ const align = fieldType === "quantitative" ? "right" : "left";
1109
+ return {
1110
+ key,
1111
+ label: key,
1112
+ align
1113
+ };
1114
+ });
1115
+ }
1116
+ const spec = {
1117
+ type: "table",
1118
+ data,
1119
+ columns: columns ?? []
1120
+ };
1121
+ if (options?.rowKey) spec.rowKey = options.rowKey;
1122
+ if (options?.chrome) spec.chrome = options.chrome;
1123
+ if (options?.theme) spec.theme = options.theme;
1124
+ if (options?.darkMode) spec.darkMode = options.darkMode;
1125
+ if (options?.search !== void 0) spec.search = options.search;
1126
+ if (options?.pagination !== void 0) spec.pagination = options.pagination;
1127
+ if (options?.stickyFirstColumn !== void 0) spec.stickyFirstColumn = options.stickyFirstColumn;
1128
+ if (options?.compact !== void 0) spec.compact = options.compact;
1129
+ if (options?.responsive !== void 0) spec.responsive = options.responsive;
1130
+ return spec;
1131
+ }
1132
+ export {
1133
+ CATEGORICAL_PALETTE,
1134
+ CHART_ENCODING_RULES,
1135
+ CHART_TYPES,
1136
+ DEFAULT_THEME,
1137
+ DIVERGING_PALETTES,
1138
+ GRAPH_ENCODING_RULES,
1139
+ SEQUENTIAL_PALETTES,
1140
+ abbreviateNumber,
1141
+ adaptColorForDarkMode,
1142
+ adaptTheme,
1143
+ areaChart,
1144
+ barChart,
1145
+ checkPaletteDistinguishability,
1146
+ columnChart,
1147
+ computeChrome,
1148
+ contrastRatio,
1149
+ dataTable,
1150
+ donutChart,
1151
+ dotChart,
1152
+ estimateTextWidth,
1153
+ findAccessibleColor,
1154
+ formatDate,
1155
+ formatNumber,
1156
+ generateAltText,
1157
+ generateAriaLabels,
1158
+ generateDataTable,
1159
+ getBreakpoint,
1160
+ getLayoutStrategy,
1161
+ inferFieldType,
1162
+ isChartSpec,
1163
+ isGraphSpec,
1164
+ isRangeAnnotation,
1165
+ isRefLineAnnotation,
1166
+ isTableSpec,
1167
+ isTextAnnotation,
1168
+ lineChart,
1169
+ meetsAA,
1170
+ pieChart,
1171
+ resolveCollisions,
1172
+ resolveTheme,
1173
+ scatterChart,
1174
+ simulateColorBlindness
1175
+ };
1176
+ //# sourceMappingURL=index.js.map