@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,280 +0,0 @@
1
- /**
2
- * Per-mark-type encoding validation rules.
3
- *
4
- * Defines which encoding channels are required vs optional for each mark type.
5
- * The engine compiler uses these rules to validate specs at runtime (TypeScript
6
- * catches compile-time errors; these catch runtime JSON from Claude or APIs).
7
- */
8
-
9
- import type { FieldType, MarkType } from './spec';
10
-
11
- // ---------------------------------------------------------------------------
12
- // Encoding rule types
13
- // ---------------------------------------------------------------------------
14
-
15
- /** Constraint on what field types are valid for an encoding channel. */
16
- export interface ChannelRule {
17
- /** Whether this channel is required for the chart type. */
18
- required: boolean;
19
- /** Allowed field types. If empty, any field type is accepted. */
20
- allowedTypes: FieldType[];
21
- }
22
-
23
- /** Encoding rules for a single chart type: which channels are required/optional. */
24
- export interface EncodingRule {
25
- x: ChannelRule;
26
- y: ChannelRule;
27
- x2?: ChannelRule;
28
- y2?: ChannelRule;
29
- color: ChannelRule;
30
- size: ChannelRule;
31
- opacity?: ChannelRule;
32
- strokeDash?: ChannelRule;
33
- text?: ChannelRule;
34
- tooltip?: ChannelRule;
35
- theta?: ChannelRule;
36
- detail: ChannelRule;
37
- }
38
-
39
- // ---------------------------------------------------------------------------
40
- // Mark encoding rules
41
- // ---------------------------------------------------------------------------
42
-
43
- /** Helper to create a required channel rule. */
44
- function required(...types: FieldType[]): ChannelRule {
45
- return { required: true, allowedTypes: types };
46
- }
47
-
48
- /** Helper to create an optional channel rule. */
49
- function optional(...types: FieldType[]): ChannelRule {
50
- return { required: false, allowedTypes: types };
51
- }
52
-
53
- /**
54
- * Encoding rules per mark type.
55
- *
56
- * Defines which channels are required and what field types they accept.
57
- * The compiler uses this map to validate user specs at runtime.
58
- *
59
- * Key design decisions:
60
- * - line/area: x is temporal/ordinal (the axis), y is quantitative (the value)
61
- * - bar: covers both horizontal and vertical orientations, so both axes accept
62
- * all relevant types. The engine validates the combination later (one axis
63
- * must be quantitative).
64
- * - arc: no x axis; y is the value (quantitative), color is the category
65
- * - circle: y is the category, x is quantitative
66
- * - point: both axes are quantitative
67
- * - text/rule: fully optional positioning
68
- * - tick/rect: both axes required, any type
69
- * - beeswarm: both axes optional at the channel level; the compiler enforces
70
- * the combination (exactly one quantitative value axis, the other axis an
71
- * optional nominal/ordinal lane channel)
72
- * - waffle: no positional axes; y is the share value (quantitative, with
73
- * theta accepted as an alias like arc), color is the category
74
- * - calendar: x is the daily date (temporal), color the per-day value
75
- * (quantitative); no y channel (the calendar owns its own geometry)
76
- * - parliament: no positional axes; theta (aliased to y) is the seat count
77
- * (quantitative), color the party
78
- */
79
- export const MARK_ENCODING_RULES: Record<MarkType, EncodingRule> = {
80
- bar: {
81
- x: required('quantitative', 'nominal', 'ordinal', 'temporal'),
82
- y: required('quantitative', 'nominal', 'ordinal'),
83
- x2: optional('quantitative'),
84
- y2: optional('quantitative'),
85
- color: optional('nominal', 'ordinal', 'quantitative'),
86
- size: optional(),
87
- opacity: optional('quantitative'),
88
- tooltip: optional(),
89
- detail: optional('nominal'),
90
- },
91
- line: {
92
- x: required('temporal', 'ordinal'),
93
- y: required('quantitative'),
94
- color: optional('nominal', 'ordinal', 'quantitative'),
95
- size: optional(),
96
- opacity: optional('quantitative'),
97
- strokeDash: optional('nominal', 'ordinal'),
98
- tooltip: optional(),
99
- detail: optional('nominal'),
100
- },
101
- area: {
102
- x: required('temporal', 'ordinal'),
103
- y: required('quantitative'),
104
- y2: optional('quantitative'),
105
- color: optional('nominal', 'ordinal', 'quantitative'),
106
- size: optional(),
107
- opacity: optional('quantitative'),
108
- tooltip: optional(),
109
- detail: optional('nominal'),
110
- },
111
- point: {
112
- x: required('quantitative', 'temporal', 'nominal', 'ordinal'),
113
- y: required('quantitative', 'temporal', 'nominal', 'ordinal'),
114
- color: optional('nominal', 'ordinal', 'quantitative'),
115
- size: optional('quantitative'),
116
- opacity: optional('quantitative'),
117
- tooltip: optional(),
118
- detail: optional('nominal'),
119
- },
120
- circle: {
121
- x: required('quantitative'),
122
- y: required('nominal', 'ordinal'),
123
- color: optional('nominal', 'ordinal', 'quantitative'),
124
- size: optional('quantitative'),
125
- opacity: optional('quantitative'),
126
- tooltip: optional(),
127
- detail: optional('nominal'),
128
- },
129
- lollipop: {
130
- x: required('quantitative'),
131
- y: required('nominal', 'ordinal'),
132
- color: optional('nominal', 'ordinal', 'quantitative'),
133
- size: optional('quantitative'),
134
- opacity: optional('quantitative'),
135
- tooltip: optional(),
136
- detail: optional('nominal'),
137
- },
138
- beeswarm: {
139
- x: optional('quantitative', 'nominal', 'ordinal'),
140
- y: optional('quantitative', 'nominal', 'ordinal'),
141
- color: optional('nominal', 'ordinal', 'quantitative'),
142
- size: optional('quantitative'),
143
- opacity: optional('quantitative'),
144
- tooltip: optional(),
145
- detail: optional('nominal'),
146
- },
147
- // Range: x+x2 with nominal y (horizontal) or y+y2 with nominal x (vertical).
148
- // The orientation-dependent x2/y2 requirement is enforced by a dedicated
149
- // check in the validator; the static table can only mark them optional.
150
- range: {
151
- x: required('quantitative', 'nominal', 'ordinal'),
152
- y: required('quantitative', 'nominal', 'ordinal'),
153
- x2: optional('quantitative'),
154
- y2: optional('quantitative'),
155
- color: optional('nominal', 'ordinal'),
156
- size: optional(),
157
- opacity: optional('quantitative'),
158
- tooltip: optional(),
159
- detail: optional('nominal'),
160
- },
161
- arc: {
162
- x: optional(),
163
- y: optional('quantitative'),
164
- theta: required('quantitative'),
165
- color: required('nominal', 'ordinal'),
166
- size: optional(),
167
- opacity: optional('quantitative'),
168
- tooltip: optional(),
169
- detail: optional('nominal'),
170
- },
171
- waffle: {
172
- x: optional(),
173
- y: required('quantitative'),
174
- color: required('nominal', 'ordinal'),
175
- size: optional(),
176
- opacity: optional('quantitative'),
177
- tooltip: optional(),
178
- theta: optional('quantitative'),
179
- detail: optional('nominal'),
180
- },
181
- // parliament: hemicycle seat chart. Like arc/waffle, no positional axes;
182
- // color is the party (required), theta the seat count (aliased to y in the
183
- // sugar pass). y is accepted so the theta alias resolves before the rules
184
- // check runs.
185
- parliament: {
186
- x: optional(),
187
- y: required('quantitative'),
188
- color: required('nominal', 'ordinal'),
189
- size: optional(),
190
- opacity: optional('quantitative'),
191
- tooltip: optional(),
192
- theta: optional('quantitative'),
193
- detail: optional('nominal'),
194
- },
195
- text: {
196
- x: optional(),
197
- y: optional(),
198
- color: optional(),
199
- size: optional('quantitative'),
200
- opacity: optional('quantitative'),
201
- text: required(),
202
- tooltip: optional(),
203
- detail: optional('nominal'),
204
- },
205
- rule: {
206
- x: optional(),
207
- y: optional(),
208
- x2: optional(),
209
- y2: optional(),
210
- color: optional(),
211
- size: optional(),
212
- opacity: optional('quantitative'),
213
- strokeDash: optional('nominal', 'ordinal'),
214
- tooltip: optional(),
215
- detail: optional('nominal'),
216
- },
217
- tick: {
218
- x: required(),
219
- y: required(),
220
- color: optional(),
221
- size: optional(),
222
- opacity: optional('quantitative'),
223
- tooltip: optional(),
224
- detail: optional('nominal'),
225
- },
226
- rect: {
227
- x: required(),
228
- y: required(),
229
- x2: optional(),
230
- y2: optional(),
231
- color: optional(),
232
- size: optional(),
233
- opacity: optional('quantitative'),
234
- tooltip: optional(),
235
- detail: optional('nominal'),
236
- },
237
- // calendar: x is the daily date (temporal), color the per-day value
238
- // (quantitative). No y channel: the calendar computes its own
239
- // weeks-x-weekdays geometry, so y is left optional here and the dedicated
240
- // validator rejects it with a pointed message.
241
- calendar: {
242
- x: required('temporal'),
243
- y: optional(),
244
- color: required('quantitative'),
245
- size: optional(),
246
- opacity: optional('quantitative'),
247
- tooltip: optional(),
248
- detail: optional('nominal'),
249
- },
250
- };
251
-
252
- /** @deprecated Use MARK_ENCODING_RULES instead. */
253
- export const CHART_ENCODING_RULES = MARK_ENCODING_RULES;
254
-
255
- // ---------------------------------------------------------------------------
256
- // Graph encoding rules
257
- // ---------------------------------------------------------------------------
258
-
259
- /** Encoding rule for a single graph visual channel. */
260
- export interface GraphChannelRule {
261
- /** Whether this channel is required. */
262
- required: boolean;
263
- /** Allowed field types. Empty means any type. */
264
- allowedTypes: FieldType[];
265
- }
266
-
267
- /**
268
- * Encoding rules for graph visualizations.
269
- *
270
- * All graph encoding channels are optional since a graph can be rendered
271
- * with just nodes and edges (uniform appearance). Encoding channels add
272
- * visual differentiation based on data fields.
273
- */
274
- export const GRAPH_ENCODING_RULES: Record<string, GraphChannelRule> = {
275
- nodeColor: { required: false, allowedTypes: ['nominal', 'ordinal'] },
276
- nodeSize: { required: false, allowedTypes: ['quantitative'] },
277
- edgeColor: { required: false, allowedTypes: ['nominal', 'ordinal'] },
278
- edgeWidth: { required: false, allowedTypes: ['quantitative'] },
279
- nodeLabel: { required: false, allowedTypes: [] },
280
- };
@@ -1,154 +0,0 @@
1
- /**
2
- * Chart interaction event types.
3
- *
4
- * These types define the callback signatures for user interactions with
5
- * chart elements: clicking marks, hovering, legend toggles, and annotation clicks.
6
- *
7
- * Event handlers are optional and passed through MountOptions (vanilla) or
8
- * ChartProps (React). The vanilla adapter wires DOM event listeners to
9
- * mark elements and constructs these typed events from the raw browser events.
10
- */
11
-
12
- import type {
13
- Annotation,
14
- AnnotationOffset,
15
- DataRow,
16
- RangeAnnotation,
17
- RefLineAnnotation,
18
- TextAnnotation,
19
- } from './spec';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Chrome key identifiers
23
- // ---------------------------------------------------------------------------
24
-
25
- /** Identifies a specific chrome text element (title, subtitle, source, byline, footer). */
26
- export type ChromeKey = 'title' | 'subtitle' | 'source' | 'byline' | 'footer';
27
-
28
- // ---------------------------------------------------------------------------
29
- // Element references (identity for selection/edit callbacks)
30
- // ---------------------------------------------------------------------------
31
-
32
- /**
33
- * Reference to an editable chart element.
34
- * Carries enough info to find the element in both the spec and the DOM.
35
- * The `annotation` variant uses two-tier identity: `id` (when the consumer provides one)
36
- * and `index` (always available, position in the spec's annotations array).
37
- */
38
- export type ElementRef =
39
- | { type: 'annotation'; index: number; id?: string }
40
- | { type: 'chrome'; key: ChromeKey }
41
- | { type: 'series-label'; series: string }
42
- | { type: 'legend' }
43
- | { type: 'legend-entry'; series: string; index: number };
44
-
45
- /** Helper constructors for ergonomic ElementRef creation. */
46
- export const elementRef = {
47
- annotation: (index: number, id?: string): ElementRef => ({ type: 'annotation', index, id }),
48
- chrome: (key: ChromeKey): ElementRef => ({ type: 'chrome', key }),
49
- seriesLabel: (series: string): ElementRef => ({ type: 'series-label', series }),
50
- legend: (): ElementRef => ({ type: 'legend' }),
51
- legendEntry: (series: string, index: number): ElementRef => ({
52
- type: 'legend-entry',
53
- series,
54
- index,
55
- }),
56
- } as const;
57
-
58
- // ---------------------------------------------------------------------------
59
- // Element edit events
60
- // ---------------------------------------------------------------------------
61
-
62
- /**
63
- * Discriminated union of all element edit events.
64
- * Fired by the `onEdit` callback when any editable chart element is modified.
65
- * Covers repositioning (drag), deletion (Delete key), and text editing (double-click).
66
- * Selection events (onSelect/onDeselect) are separate callbacks, not part of this union.
67
- *
68
- * For the `annotation` variant:
69
- * - `annotation` is the ORIGINAL spec annotation (may have a stale or missing offset)
70
- * - `offset` is the NEW accumulated position after the drag completes
71
- * - To persist a drag edit back into your spec: `{ ...edit.annotation, offset: edit.offset }`
72
- */
73
- export type ElementEdit =
74
- | { type: 'annotation'; annotation: TextAnnotation; offset: AnnotationOffset }
75
- | {
76
- type: 'annotation-connector';
77
- annotation: TextAnnotation;
78
- endpoint: 'from' | 'to';
79
- offset: AnnotationOffset;
80
- }
81
- | { type: 'range-label'; annotation: RangeAnnotation; labelOffset: AnnotationOffset }
82
- | { type: 'refline-label'; annotation: RefLineAnnotation; labelOffset: AnnotationOffset }
83
- | { type: 'chrome'; key: ChromeKey; text: string; offset: AnnotationOffset }
84
- | { type: 'series-label'; series: string; offset: AnnotationOffset }
85
- | { type: 'legend'; offset: AnnotationOffset }
86
- | { type: 'legend-toggle'; series: string; hidden: boolean }
87
- | { type: 'delete'; element: ElementRef }
88
- | { type: 'text-edit'; element: ElementRef; oldText: string; newText: string };
89
-
90
- // ---------------------------------------------------------------------------
91
- // Mark events
92
- // ---------------------------------------------------------------------------
93
-
94
- /**
95
- * Event fired when a user interacts with a data mark (bar, point, line segment, etc.).
96
- *
97
- * Contains the underlying data row, the series it belongs to (if multi-series),
98
- * the position within the chart container, and the raw browser MouseEvent.
99
- */
100
- export interface MarkEvent {
101
- /** The data row associated with the mark that was interacted with. */
102
- datum: DataRow;
103
- /** Series identifier, if the chart has multiple series (e.g. multi-line). */
104
- series?: string;
105
- /** Position of the interaction relative to the chart container. */
106
- position: { x: number; y: number };
107
- /** The raw browser MouseEvent. */
108
- event: MouseEvent;
109
- }
110
-
111
- // ---------------------------------------------------------------------------
112
- // Chart event handler interface
113
- // ---------------------------------------------------------------------------
114
-
115
- /**
116
- * Event handler callbacks for chart interactions.
117
- *
118
- * All handlers are optional. Pass these through MountOptions (vanilla adapter)
119
- * or ChartProps (React component) to receive interaction callbacks.
120
- */
121
- export interface ChartEventHandlers {
122
- /** Called when a data mark is clicked. */
123
- onMarkClick?: (event: MarkEvent) => void;
124
- /** Called when the mouse enters a data mark. */
125
- onMarkHover?: (event: MarkEvent) => void;
126
- /** Called when the mouse leaves a data mark. */
127
- onMarkLeave?: () => void;
128
- /** Called when a legend entry is toggled (clicked to show/hide a series). */
129
- onLegendToggle?: (series: string, visible: boolean) => void;
130
- /**
131
- * Called when the series search (`seriesSearch`) changes the highlight set.
132
- * Receives the full set of highlighted values after the change, including
133
- * the authored `encoding.color.highlight` baseline.
134
- */
135
- onHighlightChange?: (values: string[]) => void;
136
- /** Called when an annotation element is clicked. */
137
- onAnnotationClick?: (annotation: Annotation, event: MouseEvent) => void;
138
- /** Called when a text annotation label is dragged to a new position. */
139
- onAnnotationEdit?: (annotation: TextAnnotation, updatedOffset: AnnotationOffset) => void;
140
- /** Unified edit callback. Fires for any spec-modifying edit (repositioning, deletion, text editing). */
141
- onEdit?: (edit: ElementEdit) => void;
142
- /** Fired when an element is selected via click or programmatic select(). */
143
- onSelect?: (element: ElementRef) => void;
144
- /** Fired when the current element is deselected (click empty, Escape, or new selection replaces old). */
145
- onDeselect?: (element: ElementRef) => void;
146
- /** Fired when inline text editing commits. Also flows through onEdit as a 'text-edit' event. */
147
- onTextEdit?: (element: ElementRef, oldText: string, newText: string) => void;
148
- /**
149
- * Fired when a "you draw it" (`youDrawIt`) drawing is revealed (button click
150
- * or programmatic reveal), with the reader's guess in data coordinates,
151
- * ordered by x. For newsrooms aggregating reader guesses.
152
- */
153
- onReveal?: (guess: Array<{ x: string | number; y: number }>) => void;
154
- }