@opendata-ai/openchart-core 8.0.0-rc.8 → 8.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 (82) hide show
  1. package/dist/index.d.ts +461 -56
  2. package/dist/index.js +62 -24
  3. package/dist/index.js.map +1 -1
  4. package/dist/styles.css +1 -1
  5. package/dist/styles.css.map +1 -1
  6. package/package.json +1 -2
  7. package/schema/chart.schema.json +373 -67
  8. package/schema/table.schema.json +35 -1
  9. package/schema/vizspec.schema.json +950 -152
  10. package/src/accessibility/__tests__/alt-text.test.ts +0 -110
  11. package/src/accessibility/__tests__/aria.test.ts +0 -125
  12. package/src/accessibility/alt-text.ts +0 -155
  13. package/src/accessibility/aria.ts +0 -75
  14. package/src/accessibility/index.ts +0 -6
  15. package/src/colors/__tests__/colorblind.test.ts +0 -75
  16. package/src/colors/__tests__/contrast.test.ts +0 -119
  17. package/src/colors/__tests__/palettes.test.ts +0 -74
  18. package/src/colors/colorblind.ts +0 -131
  19. package/src/colors/contrast.ts +0 -143
  20. package/src/colors/index.ts +0 -36
  21. package/src/colors/palettes.ts +0 -206
  22. package/src/helpers/__tests__/deep-merge.test.ts +0 -54
  23. package/src/helpers/__tests__/spec-builders.test.ts +0 -338
  24. package/src/helpers/deep-merge.ts +0 -29
  25. package/src/helpers/spec-builders.ts +0 -479
  26. package/src/index.ts +0 -215
  27. package/src/labels/__tests__/collision.test.ts +0 -310
  28. package/src/labels/collision.ts +0 -249
  29. package/src/labels/index.ts +0 -13
  30. package/src/layout/__tests__/chrome.test.ts +0 -208
  31. package/src/layout/__tests__/text-measure.test.ts +0 -57
  32. package/src/layout/chrome.ts +0 -470
  33. package/src/layout/index.ts +0 -18
  34. package/src/layout/text-measure.ts +0 -404
  35. package/src/layout/text-wrap.ts +0 -93
  36. package/src/locale/__tests__/format.test.ts +0 -606
  37. package/src/locale/format.ts +0 -459
  38. package/src/locale/index.ts +0 -21
  39. package/src/responsive/__tests__/breakpoints.test.ts +0 -146
  40. package/src/responsive/__tests__/metrics.test.ts +0 -52
  41. package/src/responsive/breakpoints.ts +0 -172
  42. package/src/responsive/index.ts +0 -53
  43. package/src/responsive/metrics.ts +0 -299
  44. package/src/schema/__tests__/generated-artifacts.test.ts +0 -42
  45. package/src/schema/__tests__/schema.test.ts +0 -179
  46. package/src/styles/__tests__/reduced-motion-selectors.test.ts +0 -28
  47. package/src/styles/animation.css +0 -208
  48. package/src/styles/base.css +0 -102
  49. package/src/styles/chrome.css +0 -174
  50. package/src/styles/dark.css +0 -44
  51. package/src/styles/graph.css +0 -135
  52. package/src/styles/index.css +0 -28
  53. package/src/styles/keyframes.css +0 -151
  54. package/src/styles/legend.css +0 -18
  55. package/src/styles/reduced-motion.css +0 -67
  56. package/src/styles/series-search.css +0 -153
  57. package/src/styles/sparkline.css +0 -16
  58. package/src/styles/table-animation.css +0 -93
  59. package/src/styles/table.css +0 -398
  60. package/src/styles/token-definitions.ts +0 -199
  61. package/src/styles/tokens.css +0 -156
  62. package/src/styles/tooltip.css +0 -126
  63. package/src/styles/you-draw-it.css +0 -126
  64. package/src/theme/__tests__/dark-mode.test.ts +0 -232
  65. package/src/theme/__tests__/defaults.test.ts +0 -73
  66. package/src/theme/__tests__/resolve.test.ts +0 -277
  67. package/src/theme/dark-mode.ts +0 -258
  68. package/src/theme/defaults.ts +0 -110
  69. package/src/theme/index.ts +0 -8
  70. package/src/theme/presets.ts +0 -64
  71. package/src/theme/resolve.ts +0 -312
  72. package/src/types/__tests__/encoding.test.ts +0 -368
  73. package/src/types/__tests__/gradient.test.ts +0 -95
  74. package/src/types/__tests__/spec.test.ts +0 -499
  75. package/src/types/encoding.ts +0 -280
  76. package/src/types/events.ts +0 -154
  77. package/src/types/index.ts +0 -289
  78. package/src/types/layout.ts +0 -2001
  79. package/src/types/schema-roots.ts +0 -32
  80. package/src/types/spec.ts +0 -2906
  81. package/src/types/table.ts +0 -108
  82. package/src/types/theme.ts +0 -209
@@ -1,368 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { MARK_ENCODING_RULES } from '../encoding';
3
- import type { MarkType } from '../spec';
4
-
5
- // ---------------------------------------------------------------------------
6
- // Helpers
7
- // ---------------------------------------------------------------------------
8
-
9
- function getChannelNames(markType: MarkType): string[] {
10
- return Object.keys(MARK_ENCODING_RULES[markType]);
11
- }
12
-
13
- function getRequiredChannels(markType: MarkType): string[] {
14
- return Object.entries(MARK_ENCODING_RULES[markType])
15
- .filter(([, rule]) => rule.required)
16
- .map(([ch]) => ch);
17
- }
18
-
19
- function getOptionalChannels(markType: MarkType): string[] {
20
- return Object.entries(MARK_ENCODING_RULES[markType])
21
- .filter(([, rule]) => !rule.required)
22
- .map(([ch]) => ch);
23
- }
24
-
25
- // ---------------------------------------------------------------------------
26
- // Tests
27
- // ---------------------------------------------------------------------------
28
-
29
- describe('MARK_ENCODING_RULES', () => {
30
- it('has entries for all 16 mark types', () => {
31
- const expectedTypes: MarkType[] = [
32
- 'bar',
33
- 'line',
34
- 'area',
35
- 'point',
36
- 'circle',
37
- 'arc',
38
- 'text',
39
- 'rule',
40
- 'tick',
41
- 'rect',
42
- 'lollipop',
43
- 'beeswarm',
44
- 'range',
45
- 'waffle',
46
- 'calendar',
47
- 'parliament',
48
- ];
49
- for (const type of expectedTypes) {
50
- expect(MARK_ENCODING_RULES[type]).toBeDefined();
51
- }
52
- });
53
-
54
- it('every mark type has x, y, color, size, and detail channels', () => {
55
- const baseChannels = ['x', 'y', 'color', 'size', 'detail'];
56
- for (const [_markType, rules] of Object.entries(MARK_ENCODING_RULES)) {
57
- for (const ch of baseChannels) {
58
- expect(rules[ch as keyof typeof rules]).toBeDefined();
59
- }
60
- }
61
- });
62
- });
63
-
64
- describe('bar encoding rules', () => {
65
- it('requires x and y', () => {
66
- expect(getRequiredChannels('bar')).toContain('x');
67
- expect(getRequiredChannels('bar')).toContain('y');
68
- });
69
-
70
- it('supports x2 and y2 as optional', () => {
71
- expect(getOptionalChannels('bar')).toContain('x2');
72
- expect(getOptionalChannels('bar')).toContain('y2');
73
- });
74
-
75
- it('supports opacity, tooltip, detail', () => {
76
- const optionals = getOptionalChannels('bar');
77
- expect(optionals).toContain('opacity');
78
- expect(optionals).toContain('tooltip');
79
- expect(optionals).toContain('detail');
80
- });
81
-
82
- it('does not support shape, strokeDash, text, theta, href, order, radius', () => {
83
- const channels = getChannelNames('bar');
84
- expect(channels).not.toContain('shape');
85
- expect(channels).not.toContain('strokeDash');
86
- expect(channels).not.toContain('text');
87
- expect(channels).not.toContain('theta');
88
- expect(channels).not.toContain('href');
89
- expect(channels).not.toContain('order');
90
- expect(channels).not.toContain('radius');
91
- });
92
- });
93
-
94
- describe('line encoding rules', () => {
95
- it('requires x (temporal/ordinal) and y (quantitative)', () => {
96
- const rules = MARK_ENCODING_RULES.line;
97
- expect(rules.x.required).toBe(true);
98
- expect(rules.x.allowedTypes).toContain('temporal');
99
- expect(rules.y.required).toBe(true);
100
- expect(rules.y.allowedTypes).toContain('quantitative');
101
- });
102
-
103
- it('supports strokeDash as optional', () => {
104
- expect(getOptionalChannels('line')).toContain('strokeDash');
105
- });
106
-
107
- it('does not support shape, text, theta, radius, x2, y2', () => {
108
- const channels = getChannelNames('line');
109
- expect(channels).not.toContain('shape');
110
- expect(channels).not.toContain('text');
111
- expect(channels).not.toContain('theta');
112
- expect(channels).not.toContain('radius');
113
- expect(channels).not.toContain('x2');
114
- expect(channels).not.toContain('y2');
115
- });
116
-
117
- it('does not support href or order (removed dead channels)', () => {
118
- const channels = getChannelNames('line');
119
- expect(channels).not.toContain('href');
120
- expect(channels).not.toContain('order');
121
- });
122
- });
123
-
124
- describe('point encoding rules', () => {
125
- it('accepts all four field types on x and y', () => {
126
- const rules = MARK_ENCODING_RULES.point;
127
- for (const axis of ['x', 'y'] as const) {
128
- expect(rules[axis].required).toBe(true);
129
- expect(rules[axis].allowedTypes).toContain('quantitative');
130
- expect(rules[axis].allowedTypes).toContain('temporal');
131
- expect(rules[axis].allowedTypes).toContain('nominal');
132
- expect(rules[axis].allowedTypes).toContain('ordinal');
133
- }
134
- });
135
-
136
- it('does not support shape (removed dead channel)', () => {
137
- expect(getChannelNames('point')).not.toContain('shape');
138
- });
139
-
140
- it('supports size as optional quantitative', () => {
141
- const rules = MARK_ENCODING_RULES.point;
142
- expect(rules.size.required).toBe(false);
143
- expect(rules.size.allowedTypes).toContain('quantitative');
144
- });
145
- });
146
-
147
- describe('arc encoding rules', () => {
148
- it('requires theta (quantitative) and color (nominal/ordinal)', () => {
149
- const rules = MARK_ENCODING_RULES.arc;
150
- expect(rules.theta.required).toBe(true);
151
- expect(rules.theta.allowedTypes).toContain('quantitative');
152
- expect(rules.color.required).toBe(true);
153
- expect(rules.color.allowedTypes).toContain('nominal');
154
- });
155
-
156
- it('supports y as optional deprecated alias (radius was removed)', () => {
157
- const optionals = getOptionalChannels('arc');
158
- expect(optionals).toContain('y');
159
- expect(getChannelNames('arc')).not.toContain('radius');
160
- });
161
- });
162
-
163
- describe('text encoding rules', () => {
164
- it('requires text channel', () => {
165
- const rules = MARK_ENCODING_RULES.text;
166
- expect(rules.text!.required).toBe(true);
167
- });
168
-
169
- it('has optional x and y', () => {
170
- const rules = MARK_ENCODING_RULES.text;
171
- expect(rules.x.required).toBe(false);
172
- expect(rules.y.required).toBe(false);
173
- });
174
-
175
- it('supports size as optional quantitative', () => {
176
- const rules = MARK_ENCODING_RULES.text;
177
- expect(rules.size.required).toBe(false);
178
- expect(rules.size.allowedTypes).toContain('quantitative');
179
- });
180
- });
181
-
182
- describe('rule encoding rules', () => {
183
- it('has optional x, y, x2, y2', () => {
184
- const rules = MARK_ENCODING_RULES.rule;
185
- expect(rules.x.required).toBe(false);
186
- expect(rules.y.required).toBe(false);
187
- expect(rules.x2!.required).toBe(false);
188
- expect(rules.y2!.required).toBe(false);
189
- });
190
-
191
- it('supports strokeDash as optional', () => {
192
- expect(getOptionalChannels('rule')).toContain('strokeDash');
193
- });
194
- });
195
-
196
- describe('tick encoding rules', () => {
197
- it('requires x and y', () => {
198
- expect(getRequiredChannels('tick')).toContain('x');
199
- expect(getRequiredChannels('tick')).toContain('y');
200
- });
201
-
202
- it('supports opacity, tooltip, detail', () => {
203
- const optionals = getOptionalChannels('tick');
204
- expect(optionals).toContain('opacity');
205
- expect(optionals).toContain('tooltip');
206
- expect(optionals).toContain('detail');
207
- });
208
- });
209
-
210
- describe('rect encoding rules', () => {
211
- it('requires x and y', () => {
212
- expect(getRequiredChannels('rect')).toContain('x');
213
- expect(getRequiredChannels('rect')).toContain('y');
214
- });
215
-
216
- it('supports x2, y2, opacity', () => {
217
- const optionals = getOptionalChannels('rect');
218
- expect(optionals).toContain('x2');
219
- expect(optionals).toContain('y2');
220
- expect(optionals).toContain('opacity');
221
- });
222
- });
223
-
224
- describe('lollipop encoding rules', () => {
225
- it('has the same rules as circle (semantic alias)', () => {
226
- const lollipopRules = MARK_ENCODING_RULES.lollipop;
227
- const circleRules = MARK_ENCODING_RULES.circle;
228
- expect(lollipopRules).toEqual(circleRules);
229
- });
230
-
231
- it('requires x (quantitative) and y (nominal/ordinal)', () => {
232
- const rules = MARK_ENCODING_RULES.lollipop;
233
- expect(rules.x.required).toBe(true);
234
- expect(rules.x.allowedTypes).toEqual(['quantitative']);
235
- expect(rules.y.required).toBe(true);
236
- expect(rules.y.allowedTypes).toContain('nominal');
237
- expect(rules.y.allowedTypes).toContain('ordinal');
238
- });
239
- });
240
-
241
- describe('beeswarm encoding rules', () => {
242
- it('marks both axes optional (the compiler enforces the combination)', () => {
243
- const rules = MARK_ENCODING_RULES.beeswarm;
244
- expect(rules.x.required).toBe(false);
245
- expect(rules.y.required).toBe(false);
246
- });
247
-
248
- it('accepts quantitative and nominal/ordinal on both axes', () => {
249
- const rules = MARK_ENCODING_RULES.beeswarm;
250
- for (const axis of [rules.x, rules.y]) {
251
- expect(axis.allowedTypes).toContain('quantitative');
252
- expect(axis.allowedTypes).toContain('nominal');
253
- expect(axis.allowedTypes).toContain('ordinal');
254
- }
255
- });
256
-
257
- it('supports optional size (quantitative) for sized dots', () => {
258
- const rules = MARK_ENCODING_RULES.beeswarm;
259
- expect(rules.size.required).toBe(false);
260
- expect(rules.size.allowedTypes).toEqual(['quantitative']);
261
- });
262
- });
263
-
264
- describe('waffle encoding rules', () => {
265
- it('requires a quantitative y (the share value) and a categorical color', () => {
266
- const rules = MARK_ENCODING_RULES.waffle;
267
- expect(rules.y.required).toBe(true);
268
- expect(rules.y.allowedTypes).toEqual(['quantitative']);
269
- expect(rules.color.required).toBe(true);
270
- expect(rules.color.allowedTypes).toContain('nominal');
271
- expect(rules.color.allowedTypes).toContain('ordinal');
272
- });
273
-
274
- it('accepts theta as an optional quantitative channel (arc parity)', () => {
275
- const rules = MARK_ENCODING_RULES.waffle;
276
- expect(rules.theta?.required).toBe(false);
277
- expect(rules.theta?.allowedTypes).toEqual(['quantitative']);
278
- });
279
-
280
- it('does not require positional x (no axes)', () => {
281
- const rules = MARK_ENCODING_RULES.waffle;
282
- expect(rules.x.required).toBe(false);
283
- });
284
- });
285
-
286
- describe('common channels across marks', () => {
287
- it('tooltip is optional on all mark types', () => {
288
- const allTypes: MarkType[] = [
289
- 'bar',
290
- 'line',
291
- 'area',
292
- 'point',
293
- 'circle',
294
- 'arc',
295
- 'text',
296
- 'rule',
297
- 'tick',
298
- 'rect',
299
- 'lollipop',
300
- 'beeswarm',
301
- 'range',
302
- 'waffle',
303
- 'calendar',
304
- 'parliament',
305
- ];
306
- for (const type of allTypes) {
307
- const rules = MARK_ENCODING_RULES[type];
308
- if (rules.tooltip) {
309
- expect(rules.tooltip.required).toBe(false);
310
- }
311
- }
312
- });
313
-
314
- it('href is optional on all mark types that have it', () => {
315
- const allTypes: MarkType[] = [
316
- 'bar',
317
- 'line',
318
- 'area',
319
- 'point',
320
- 'circle',
321
- 'arc',
322
- 'text',
323
- 'rule',
324
- 'tick',
325
- 'rect',
326
- 'lollipop',
327
- 'beeswarm',
328
- 'range',
329
- 'waffle',
330
- 'calendar',
331
- 'parliament',
332
- ];
333
- for (const type of allTypes) {
334
- const rules = MARK_ENCODING_RULES[type];
335
- if (rules.href) {
336
- expect(rules.href.required).toBe(false);
337
- }
338
- }
339
- });
340
-
341
- it('opacity is optional quantitative on all mark types that have it', () => {
342
- const allTypes: MarkType[] = [
343
- 'bar',
344
- 'line',
345
- 'area',
346
- 'point',
347
- 'circle',
348
- 'arc',
349
- 'text',
350
- 'rule',
351
- 'tick',
352
- 'rect',
353
- 'lollipop',
354
- 'beeswarm',
355
- 'range',
356
- 'waffle',
357
- 'calendar',
358
- 'parliament',
359
- ];
360
- for (const type of allTypes) {
361
- const rules = MARK_ENCODING_RULES[type];
362
- if (rules.opacity) {
363
- expect(rules.opacity.required).toBe(false);
364
- expect(rules.opacity.allowedTypes).toContain('quantitative');
365
- }
366
- }
367
- });
368
- });
@@ -1,95 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import type { GradientDef } from '../spec';
3
- import { getRepresentativeColor, isGradientDef } from '../spec';
4
-
5
- describe('isGradientDef', () => {
6
- it('returns true for a valid linear gradient', () => {
7
- expect(
8
- isGradientDef({
9
- gradient: 'linear',
10
- stops: [
11
- { offset: 0, color: '#f00' },
12
- { offset: 1, color: '#00f' },
13
- ],
14
- }),
15
- ).toBe(true);
16
- });
17
-
18
- it('returns true for a valid radial gradient', () => {
19
- expect(
20
- isGradientDef({
21
- gradient: 'radial',
22
- stops: [{ offset: 0, color: '#f00' }],
23
- }),
24
- ).toBe(true);
25
- });
26
-
27
- it('returns false for a plain string', () => {
28
- expect(isGradientDef('#ff0000')).toBe(false);
29
- });
30
-
31
- it('returns false for null', () => {
32
- expect(isGradientDef(null)).toBe(false);
33
- });
34
-
35
- it('returns false for undefined', () => {
36
- expect(isGradientDef(undefined)).toBe(false);
37
- });
38
-
39
- it('returns false for an object without gradient property', () => {
40
- expect(isGradientDef({ stops: [] })).toBe(false);
41
- });
42
-
43
- it('returns false for an object without stops', () => {
44
- expect(isGradientDef({ gradient: 'linear' })).toBe(false);
45
- });
46
-
47
- it('returns false for an object with invalid gradient type', () => {
48
- expect(isGradientDef({ gradient: 'mesh', stops: [] })).toBe(false);
49
- });
50
- });
51
-
52
- describe('getRepresentativeColor', () => {
53
- it('returns the string directly when given a string', () => {
54
- expect(getRepresentativeColor('#1b7fa3')).toBe('#1b7fa3');
55
- });
56
-
57
- it('returns the last stop color for a linear gradient', () => {
58
- const grad: GradientDef = {
59
- gradient: 'linear',
60
- stops: [
61
- { offset: 0, color: '#f00' },
62
- { offset: 0.5, color: '#0f0' },
63
- { offset: 1, color: '#00f' },
64
- ],
65
- };
66
- expect(getRepresentativeColor(grad)).toBe('#00f');
67
- });
68
-
69
- it('returns the last stop color for a radial gradient', () => {
70
- const grad: GradientDef = {
71
- gradient: 'radial',
72
- stops: [
73
- { offset: 0, color: '#fff' },
74
- { offset: 1, color: '#000' },
75
- ],
76
- };
77
- expect(getRepresentativeColor(grad)).toBe('#000');
78
- });
79
-
80
- it('returns #000000 for a gradient with empty stops array', () => {
81
- const grad: GradientDef = {
82
- gradient: 'linear',
83
- stops: [],
84
- };
85
- expect(getRepresentativeColor(grad)).toBe('#000000');
86
- });
87
-
88
- it('returns the single stop color for a gradient with one stop', () => {
89
- const grad: GradientDef = {
90
- gradient: 'radial',
91
- stops: [{ offset: 0, color: '#abc' }],
92
- };
93
- expect(getRepresentativeColor(grad)).toBe('#abc');
94
- });
95
- });