@opendata-ai/openchart-vanilla 8.0.0-rc.2 → 8.0.0-rc.21

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 (145) hide show
  1. package/dist/{chunk-W34KN7FD.js → chunk-2NAQRP66.js} +2 -2
  2. package/dist/chunk-DCODCLWI.js +234 -0
  3. package/dist/chunk-DCODCLWI.js.map +1 -0
  4. package/dist/{chunk-5EOV5JQZ.js → chunk-EKAUQAAX.js} +50 -46
  5. package/dist/chunk-EKAUQAAX.js.map +1 -0
  6. package/dist/{chunk-ENXSUXC5.js → chunk-KNT5AZ2O.js} +7309 -5490
  7. package/dist/chunk-KNT5AZ2O.js.map +1 -0
  8. package/dist/chunk-RM5XS6NR.js +113 -0
  9. package/dist/chunk-RM5XS6NR.js.map +1 -0
  10. package/dist/chunk-T32UHMZA.js +282 -0
  11. package/dist/chunk-T32UHMZA.js.map +1 -0
  12. package/dist/export-gif.d.ts +113 -0
  13. package/dist/export-gif.js +21 -0
  14. package/dist/export-gif.js.map +1 -0
  15. package/dist/index.d.ts +283 -24
  16. package/dist/index.js +2012 -763
  17. package/dist/index.js.map +1 -1
  18. package/dist/{mount-CUKQLeqD.d.ts → mount-Dvv8NYIv.d.ts} +117 -3
  19. package/dist/simulation-worker.js +83 -2
  20. package/dist/static.js +7 -69
  21. package/dist/static.js.map +1 -1
  22. package/dist/story/index.d.ts +3 -2
  23. package/dist/story/index.js +5 -3
  24. package/dist/story/index.js.map +1 -1
  25. package/dist/styles.css +1 -1
  26. package/dist/styles.css.map +1 -1
  27. package/package.json +14 -6
  28. package/dist/chunk-5EOV5JQZ.js.map +0 -1
  29. package/dist/chunk-ENXSUXC5.js.map +0 -1
  30. package/src/__test-fixtures__/dom.ts +0 -70
  31. package/src/__test-fixtures__/specs.ts +0 -214
  32. package/src/__tests__/animation.test.ts +0 -358
  33. package/src/__tests__/annotations-render.test.ts +0 -201
  34. package/src/__tests__/compound-labels.test.ts +0 -122
  35. package/src/__tests__/continuous-legend.test.ts +0 -91
  36. package/src/__tests__/crosshair.test.ts +0 -130
  37. package/src/__tests__/edit-events.test.ts +0 -902
  38. package/src/__tests__/events.test.ts +0 -387
  39. package/src/__tests__/export.test.ts +0 -241
  40. package/src/__tests__/font-race.test.ts +0 -199
  41. package/src/__tests__/gradient-ids.test.ts +0 -159
  42. package/src/__tests__/gradient-utils.test.ts +0 -125
  43. package/src/__tests__/known-bugs-render.test.ts +0 -183
  44. package/src/__tests__/map.test.ts +0 -293
  45. package/src/__tests__/mount.test.ts +0 -357
  46. package/src/__tests__/pattern-fills.test.ts +0 -105
  47. package/src/__tests__/resize-timing.test.ts +0 -218
  48. package/src/__tests__/sankey.test.ts +0 -133
  49. package/src/__tests__/selection-events.test.ts +0 -869
  50. package/src/__tests__/series-search.test.ts +0 -311
  51. package/src/__tests__/static.test.ts +0 -141
  52. package/src/__tests__/svg-renderer.test.ts +0 -895
  53. package/src/__tests__/table-keyboard.test.ts +0 -361
  54. package/src/__tests__/table-mount.test.ts +0 -639
  55. package/src/__tests__/tilemap.test.ts +0 -158
  56. package/src/__tests__/tooltip.test.ts +0 -337
  57. package/src/__tests__/transition.test.ts +0 -1777
  58. package/src/__tests__/you-draw-it.test.ts +0 -273
  59. package/src/animation-vars.ts +0 -42
  60. package/src/animation.ts +0 -76
  61. package/src/barlist-mount.ts +0 -383
  62. package/src/barlist-renderer.ts +0 -267
  63. package/src/dom-helpers.ts +0 -17
  64. package/src/export.ts +0 -482
  65. package/src/gradient-utils.ts +0 -138
  66. package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
  67. package/src/graph/__tests__/graph-mount.test.ts +0 -213
  68. package/src/graph/__tests__/interaction.test.ts +0 -205
  69. package/src/graph/__tests__/keyboard.test.ts +0 -653
  70. package/src/graph/__tests__/search.test.ts +0 -88
  71. package/src/graph/__tests__/simulation.test.ts +0 -233
  72. package/src/graph/__tests__/spatial-index.test.ts +0 -142
  73. package/src/graph/__tests__/zoom.test.ts +0 -203
  74. package/src/graph/canvas-renderer.ts +0 -747
  75. package/src/graph/interaction.ts +0 -367
  76. package/src/graph/keyboard.ts +0 -208
  77. package/src/graph/search.ts +0 -50
  78. package/src/graph/simulation-worker-url.ts +0 -12
  79. package/src/graph/simulation-worker.ts +0 -278
  80. package/src/graph/simulation.ts +0 -422
  81. package/src/graph/spatial-index.ts +0 -121
  82. package/src/graph/types.ts +0 -47
  83. package/src/graph/worker-protocol.ts +0 -70
  84. package/src/graph/zoom.ts +0 -134
  85. package/src/graph-mount.ts +0 -888
  86. package/src/index.ts +0 -76
  87. package/src/interactions/chart-events.ts +0 -139
  88. package/src/interactions/crosshair.ts +0 -233
  89. package/src/interactions/drag-handler.ts +0 -175
  90. package/src/interactions/editing-drags.ts +0 -515
  91. package/src/interactions/index.ts +0 -25
  92. package/src/interactions/keyboard-nav.ts +0 -111
  93. package/src/interactions/legend-interaction.ts +0 -46
  94. package/src/interactions/selection.ts +0 -280
  95. package/src/interactions/tooltip-events.ts +0 -72
  96. package/src/map-camera.ts +0 -88
  97. package/src/map-mount.ts +0 -701
  98. package/src/map-renderer.ts +0 -338
  99. package/src/map-transition.ts +0 -83
  100. package/src/measure-text.ts +0 -134
  101. package/src/mount.ts +0 -1382
  102. package/src/pattern-utils.ts +0 -110
  103. package/src/renderers/annotations.ts +0 -388
  104. package/src/renderers/axes.ts +0 -351
  105. package/src/renderers/brand.ts +0 -87
  106. package/src/renderers/chrome.ts +0 -196
  107. package/src/renderers/endpoint-labels.ts +0 -150
  108. package/src/renderers/legend.ts +0 -311
  109. package/src/renderers/marks.ts +0 -561
  110. package/src/renderers/metrics.ts +0 -63
  111. package/src/renderers/svg-dom.ts +0 -40
  112. package/src/renderers/table-cells.ts +0 -446
  113. package/src/resize-observer.ts +0 -46
  114. package/src/sankey-mount.ts +0 -622
  115. package/src/sankey-renderer.ts +0 -614
  116. package/src/series-search.ts +0 -392
  117. package/src/static.ts +0 -251
  118. package/src/story/__tests__/camera-math.test.ts +0 -145
  119. package/src/story/__tests__/create-chart-story.test.ts +0 -317
  120. package/src/story/__tests__/progress-math.test.ts +0 -95
  121. package/src/story/__tests__/scroll-driver.test.ts +0 -130
  122. package/src/story/__tests__/tween.test.ts +0 -159
  123. package/src/story/camera-math.ts +0 -125
  124. package/src/story/create-chart-story.ts +0 -353
  125. package/src/story/crossfade.ts +0 -70
  126. package/src/story/index.ts +0 -39
  127. package/src/story/progress-math.ts +0 -85
  128. package/src/story/resolve-camera-target.ts +0 -119
  129. package/src/story/scroll-driver.ts +0 -206
  130. package/src/story/story-camera.ts +0 -49
  131. package/src/story/tween.ts +0 -112
  132. package/src/story/types.ts +0 -76
  133. package/src/svg-ids.ts +0 -23
  134. package/src/svg-renderer.ts +0 -383
  135. package/src/table-keyboard.ts +0 -266
  136. package/src/table-mount.ts +0 -567
  137. package/src/table-renderer.ts +0 -414
  138. package/src/text-edit-overlay.ts +0 -255
  139. package/src/theme-tokens.ts +0 -51
  140. package/src/tilemap-mount.ts +0 -511
  141. package/src/tilemap-renderer.ts +0 -396
  142. package/src/tooltip.ts +0 -177
  143. package/src/transition.ts +0 -2816
  144. package/src/you-draw-it.ts +0 -480
  145. /package/dist/{chunk-W34KN7FD.js.map → chunk-2NAQRP66.js.map} +0 -0
@@ -1,70 +0,0 @@
1
- /**
2
- * Shared DOM helpers for vanilla package tests.
3
- *
4
- * Provides container creation with mocked getBoundingClientRect (needed because
5
- * happy-dom has no layout engine) and mouse event construction.
6
- */
7
-
8
- /**
9
- * Create an HTMLDivElement with mocked dimensions, appended to document.body.
10
- * happy-dom returns zero for getBoundingClientRect by default, so we provide
11
- * realistic values for the chart/table compilation pipeline to work with.
12
- */
13
- export function createContainer(width = 600, height = 400): HTMLDivElement {
14
- const container = document.createElement('div');
15
- Object.defineProperty(container, 'getBoundingClientRect', {
16
- value: () => ({
17
- width,
18
- height,
19
- top: 0,
20
- left: 0,
21
- right: width,
22
- bottom: height,
23
- x: 0,
24
- y: 0,
25
- toJSON: () => ({}),
26
- }),
27
- });
28
- document.body.appendChild(container);
29
- return container;
30
- }
31
-
32
- /**
33
- * Create an auto-height container: its measured height is 0 until a chart
34
- * SVG is mounted, then tracks the SVG's own style.height — mimicking a real
35
- * DOM container with no explicit height that wraps its content.
36
- */
37
- export function createAutoHeightContainer(width = 390): HTMLDivElement {
38
- const container = document.createElement('div');
39
- Object.defineProperty(container, 'getBoundingClientRect', {
40
- value: () => {
41
- const svg = container.querySelector('svg');
42
- const height = svg ? Number.parseFloat((svg as SVGElement).style.height) || 0 : 0;
43
- return {
44
- width,
45
- height,
46
- top: 0,
47
- left: 0,
48
- right: width,
49
- bottom: height,
50
- x: 0,
51
- y: 0,
52
- toJSON: () => ({}),
53
- };
54
- },
55
- });
56
- document.body.appendChild(container);
57
- return container;
58
- }
59
-
60
- /**
61
- * Create a MouseEvent with clientX/clientY coordinates.
62
- * happy-dom supports basic MouseEvent construction.
63
- */
64
- export function createMouseEvent(type: string, x = 100, y = 100): MouseEvent {
65
- return new MouseEvent(type, {
66
- bubbles: true,
67
- clientX: x,
68
- clientY: y,
69
- });
70
- }
@@ -1,214 +0,0 @@
1
- /**
2
- * Shared chart, table, and graph spec fixtures for vanilla package tests.
3
- *
4
- * These cover all chart types and common configurations so individual test
5
- * files don't need to duplicate spec definitions.
6
- */
7
-
8
- import type { ChartSpec, GraphSpec, TableSpec } from '@opendata-ai/openchart-engine';
9
-
10
- // ---------------------------------------------------------------------------
11
- // Line chart specs
12
- // ---------------------------------------------------------------------------
13
-
14
- export const lineSpec: ChartSpec = {
15
- mark: 'line',
16
- data: [
17
- { date: '2020-01-01', value: 10, country: 'US' },
18
- { date: '2021-01-01', value: 40, country: 'US' },
19
- { date: '2020-01-01', value: 15, country: 'UK' },
20
- { date: '2021-01-01', value: 35, country: 'UK' },
21
- ],
22
- encoding: {
23
- x: { field: 'date', type: 'temporal' },
24
- y: { field: 'value', type: 'quantitative' },
25
- color: { field: 'country', type: 'nominal' },
26
- },
27
- chrome: {
28
- title: 'GDP Growth',
29
- subtitle: 'US vs UK over time',
30
- source: 'World Bank',
31
- },
32
- };
33
-
34
- /**
35
- * Multi-series line with wrapping chrome and an explicit top legend — the
36
- * mobile auto-height growth scenario (long title + subtitle + legend +
37
- * source all stack above/below the plot).
38
- */
39
- export const longChromeLineSpec: ChartSpec = {
40
- mark: 'line',
41
- data: [
42
- { date: '2020-01-01', value: 10, country: 'US' },
43
- { date: '2021-01-01', value: 40, country: 'US' },
44
- { date: '2020-01-01', value: 15, country: 'UK' },
45
- { date: '2021-01-01', value: 35, country: 'UK' },
46
- ],
47
- encoding: {
48
- x: { field: 'date', type: 'temporal' },
49
- y: { field: 'value', type: 'quantitative' },
50
- color: { field: 'country', type: 'nominal' },
51
- },
52
- legend: { show: true, position: 'top' },
53
- chrome: {
54
- title:
55
- 'An intentionally long editorial chart title that wraps to several lines at phone widths',
56
- subtitle: 'A methodological subtitle that provides additional context for readers',
57
- source: 'World Bank',
58
- },
59
- };
60
-
61
- export const singleSeriesLineSpec: ChartSpec = {
62
- mark: 'line',
63
- data: [
64
- { date: '2020-01-01', value: 10 },
65
- { date: '2021-01-01', value: 40 },
66
- { date: '2022-01-01', value: 25 },
67
- ],
68
- encoding: {
69
- x: { field: 'date', type: 'temporal' },
70
- y: { field: 'value', type: 'quantitative' },
71
- },
72
- };
73
-
74
- // ---------------------------------------------------------------------------
75
- // Bar chart specs
76
- // ---------------------------------------------------------------------------
77
-
78
- export const barSpec: ChartSpec = {
79
- mark: 'bar',
80
- data: [
81
- { name: 'A', value: 10 },
82
- { name: 'B', value: 30 },
83
- { name: 'C', value: 20 },
84
- ],
85
- encoding: {
86
- x: { field: 'value', type: 'quantitative' },
87
- y: { field: 'name', type: 'nominal' },
88
- },
89
- chrome: {
90
- title: 'Updated Chart',
91
- },
92
- };
93
-
94
- // ---------------------------------------------------------------------------
95
- // Column chart specs
96
- // ---------------------------------------------------------------------------
97
-
98
- export const columnSpec: ChartSpec = {
99
- mark: 'bar',
100
- data: [
101
- { category: 'Q1', revenue: 100 },
102
- { category: 'Q2', revenue: 200 },
103
- { category: 'Q3', revenue: 150 },
104
- ],
105
- encoding: {
106
- x: { field: 'category', type: 'nominal' },
107
- y: { field: 'revenue', type: 'quantitative' },
108
- },
109
- chrome: {
110
- title: 'Quarterly Revenue',
111
- },
112
- };
113
-
114
- // ---------------------------------------------------------------------------
115
- // Scatter chart specs
116
- // ---------------------------------------------------------------------------
117
-
118
- export const scatterSpec: ChartSpec = {
119
- mark: 'point',
120
- data: [
121
- { x: 10, y: 20, group: 'A' },
122
- { x: 30, y: 40, group: 'A' },
123
- { x: 50, y: 10, group: 'B' },
124
- { x: 60, y: 30, group: 'B' },
125
- ],
126
- encoding: {
127
- x: { field: 'x', type: 'quantitative' },
128
- y: { field: 'y', type: 'quantitative' },
129
- color: { field: 'group', type: 'nominal' },
130
- },
131
- chrome: {
132
- title: 'Scatter Plot',
133
- },
134
- };
135
-
136
- // ---------------------------------------------------------------------------
137
- // Pie chart specs
138
- // ---------------------------------------------------------------------------
139
-
140
- export const pieSpec: ChartSpec = {
141
- mark: 'arc',
142
- data: [
143
- { category: 'Red', value: 30 },
144
- { category: 'Blue', value: 50 },
145
- { category: 'Green', value: 20 },
146
- ],
147
- encoding: {
148
- color: { field: 'category', type: 'nominal' },
149
- theta: { field: 'value', type: 'quantitative' },
150
- },
151
- chrome: {
152
- title: 'Market Share',
153
- },
154
- };
155
-
156
- // ---------------------------------------------------------------------------
157
- // Multi-series bar spec (grouped)
158
- // ---------------------------------------------------------------------------
159
-
160
- export const multiSeriesBarSpec: ChartSpec = {
161
- mark: 'bar',
162
- data: [
163
- { name: 'A', value: 10, group: 'X' },
164
- { name: 'B', value: 30, group: 'X' },
165
- { name: 'A', value: 20, group: 'Y' },
166
- { name: 'B', value: 15, group: 'Y' },
167
- ],
168
- encoding: {
169
- x: { field: 'value', type: 'quantitative' },
170
- y: { field: 'name', type: 'nominal' },
171
- color: { field: 'group', type: 'nominal' },
172
- },
173
- };
174
-
175
- // ---------------------------------------------------------------------------
176
- // Table specs
177
- // ---------------------------------------------------------------------------
178
-
179
- export function makeTableSpec(overrides?: Partial<TableSpec>): TableSpec {
180
- return {
181
- type: 'table',
182
- data: [
183
- { name: 'Alice', age: 30, city: 'Portland' },
184
- { name: 'Bob', age: 25, city: 'Seattle' },
185
- { name: 'Charlie', age: 35, city: 'Portland' },
186
- { name: 'Diana', age: 28, city: 'Denver' },
187
- { name: 'Eve', age: 22, city: 'Seattle' },
188
- ],
189
- columns: [
190
- { key: 'name', label: 'Name' },
191
- { key: 'age', label: 'Age' },
192
- { key: 'city', label: 'City' },
193
- ],
194
- chrome: { title: 'People' },
195
- ...overrides,
196
- };
197
- }
198
-
199
- // ---------------------------------------------------------------------------
200
- // Graph specs
201
- // ---------------------------------------------------------------------------
202
-
203
- export const graphSpec: GraphSpec = {
204
- type: 'graph',
205
- nodes: [
206
- { id: 'a', label: 'Node A', community: 'group1' },
207
- { id: 'b', label: 'Node B', community: 'group1' },
208
- { id: 'c', label: 'Node C', community: 'group2' },
209
- ],
210
- edges: [
211
- { source: 'a', target: 'b' },
212
- { source: 'b', target: 'c' },
213
- ],
214
- };
@@ -1,358 +0,0 @@
1
- /**
2
- * Animation integration tests for the vanilla renderer.
3
- *
4
- * Tests the full pipeline from spec -> compile -> render for animation-related
5
- * behavior. Verifies that the compiled layout carries animation config and that
6
- * the SVG output is correctly structured for CSS animations to work.
7
- *
8
- * Note: CSS animation keyframes and class-based triggers are defined in the
9
- * CSS partials under packages/core/src/styles/. These tests verify the DOM
10
- * structure that CSS hooks into, not the visual animation itself (which
11
- * requires a real browser).
12
- */
13
-
14
- import type { ChartSpec } from '@opendata-ai/openchart-engine';
15
- import { compileChart } from '@opendata-ai/openchart-engine';
16
- import { afterEach, describe, expect, it } from 'vitest';
17
- import { createContainer } from '../__test-fixtures__/dom';
18
- import { barSpec, columnSpec, lineSpec } from '../__test-fixtures__/specs';
19
- import { createChart } from '../mount';
20
- import { renderChartSVG } from '../svg-renderer';
21
-
22
- // ---------------------------------------------------------------------------
23
- // Helpers
24
- // ---------------------------------------------------------------------------
25
-
26
- function compileAndRender(spec: ChartSpec, width = 600, height = 400) {
27
- const container = createContainer(width, height);
28
- const layout = compileChart(spec, { width, height });
29
- const svg = renderChartSVG(layout, container);
30
- return { svg, container, layout };
31
- }
32
-
33
- // ---------------------------------------------------------------------------
34
- // Cleanup
35
- // ---------------------------------------------------------------------------
36
-
37
- afterEach(() => {
38
- document.body.innerHTML = '';
39
- });
40
-
41
- // ---------------------------------------------------------------------------
42
- // Layout carries animation config through to render
43
- // ---------------------------------------------------------------------------
44
-
45
- describe('animation in compiled layout', () => {
46
- it('layout has animation config when spec enables animation', () => {
47
- const spec = { ...barSpec, animation: true } as ChartSpec;
48
- const layout = compileChart(spec, { width: 600, height: 400 });
49
- expect(layout.animation).toBeDefined();
50
- expect(layout.animation!.enter).toBeDefined();
51
- });
52
-
53
- it('layout has no animation config when spec omits animation', () => {
54
- const layout = compileChart(barSpec, { width: 600, height: 400 });
55
- expect(layout.animation).toBeUndefined();
56
- });
57
- });
58
-
59
- // ---------------------------------------------------------------------------
60
- // SVG mark structure supports CSS animation selectors
61
- // ---------------------------------------------------------------------------
62
-
63
- describe('SVG structure for animation CSS hooks', () => {
64
- it('bar chart renders rect elements inside oc-mark groups', () => {
65
- const spec = { ...barSpec, animation: true } as ChartSpec;
66
- const { svg } = compileAndRender(spec);
67
-
68
- // CSS animations target .oc-mark-rect rect and .oc-mark-bar rect
69
- const rects = svg.querySelectorAll('.oc-marks rect');
70
- expect(rects.length).toBeGreaterThan(0);
71
- });
72
-
73
- it('column chart renders rect elements for vertical bars', () => {
74
- const spec = { ...columnSpec, animation: true } as ChartSpec;
75
- const { svg } = compileAndRender(spec);
76
-
77
- const rects = svg.querySelectorAll('.oc-marks rect');
78
- expect(rects.length).toBeGreaterThan(0);
79
- });
80
-
81
- it('line chart renders path elements inside oc-mark-line groups', () => {
82
- const spec = { ...lineSpec, animation: true } as ChartSpec;
83
- const { svg } = compileAndRender(spec);
84
-
85
- const paths = svg.querySelectorAll('.oc-mark-line path');
86
- expect(paths.length).toBeGreaterThan(0);
87
- });
88
-
89
- it('mark groups have data-mark-id attributes for targeting', () => {
90
- const spec = { ...barSpec, animation: true } as ChartSpec;
91
- const { svg } = compileAndRender(spec);
92
-
93
- const markGroups = svg.querySelectorAll('[data-mark-id]');
94
- expect(markGroups.length).toBeGreaterThan(0);
95
- });
96
-
97
- it('SVG root has oc-chart class (oc-animate is added by mount, not renderer)', () => {
98
- const spec = { ...barSpec, animation: true } as ChartSpec;
99
- const { svg } = compileAndRender(spec);
100
- expect(svg.getAttribute('class')).toContain('oc-chart');
101
- // oc-animate is added by mount.ts on first render only, not by the renderer
102
- expect(svg.getAttribute('class')).not.toContain('oc-animate');
103
- });
104
-
105
- it('SVG root does not have oc-animate class when animation is disabled', () => {
106
- const { svg } = compileAndRender(barSpec);
107
- expect(svg.getAttribute('class')).toContain('oc-chart');
108
- expect(svg.getAttribute('class')).not.toContain('oc-animate');
109
- });
110
- });
111
-
112
- // ---------------------------------------------------------------------------
113
- // Animation CSS custom properties on SVG root
114
- // ---------------------------------------------------------------------------
115
-
116
- describe('animation CSS custom properties', () => {
117
- it('sets --oc-animation-duration on SVG root', () => {
118
- const spec = {
119
- ...barSpec,
120
- animation: { enter: { duration: 800 } },
121
- } as ChartSpec;
122
- const { svg } = compileAndRender(spec);
123
- expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('800ms');
124
- });
125
-
126
- it('sets --oc-animation-stagger on SVG root', () => {
127
- const spec = { ...barSpec, animation: true } as ChartSpec;
128
- const { svg } = compileAndRender(spec);
129
- const stagger = svg.style.getPropertyValue('--oc-animation-stagger');
130
- expect(stagger).toMatch(/^\d+ms$/);
131
- });
132
-
133
- it('sets --oc-annotation-delay on SVG root', () => {
134
- const spec = {
135
- ...barSpec,
136
- animation: { enter: true, annotationDelay: 500 },
137
- } as ChartSpec;
138
- const { svg } = compileAndRender(spec);
139
- expect(svg.style.getPropertyValue('--oc-annotation-delay')).toBe('500ms');
140
- });
141
-
142
- it('does not set animation custom properties when animation is disabled', () => {
143
- const { svg } = compileAndRender(barSpec);
144
- expect(svg.style.getPropertyValue('--oc-animation-duration')).toBe('');
145
- expect(svg.style.getPropertyValue('--oc-animation-stagger')).toBe('');
146
- });
147
- });
148
-
149
- // ---------------------------------------------------------------------------
150
- // --oc-mark-index stamped on mark elements
151
- // ---------------------------------------------------------------------------
152
-
153
- describe('mark animation index attributes', () => {
154
- it('stamps --oc-mark-index on rect mark groups', () => {
155
- const spec = { ...columnSpec, animation: true } as ChartSpec;
156
- const { svg } = compileAndRender(spec);
157
-
158
- const markGroups = svg.querySelectorAll('.oc-mark-rect');
159
- expect(markGroups.length).toBeGreaterThan(0);
160
-
161
- for (const group of markGroups) {
162
- const el = group as SVGElement & ElementCSSInlineStyle;
163
- const markIndex = el.style.getPropertyValue('--oc-mark-index');
164
- expect(markIndex).not.toBe('');
165
- }
166
- });
167
-
168
- it('stamps data-animation-index attribute on mark groups', () => {
169
- const spec = { ...columnSpec, animation: true } as ChartSpec;
170
- const { svg } = compileAndRender(spec);
171
-
172
- const markGroups = svg.querySelectorAll('[data-animation-index]');
173
- expect(markGroups.length).toBeGreaterThan(0);
174
-
175
- // Each should have a numeric index
176
- for (const group of markGroups) {
177
- const idx = group.getAttribute('data-animation-index');
178
- expect(idx).toMatch(/^\d+$/);
179
- }
180
- });
181
-
182
- it('does not stamp animation attributes when animation is disabled', () => {
183
- const { svg } = compileAndRender(columnSpec);
184
-
185
- const withAnimIdx = svg.querySelectorAll('[data-animation-index]');
186
- expect(withAnimIdx.length).toBe(0);
187
- });
188
-
189
- it('stamps --oc-mark-index on line mark groups', () => {
190
- const spec = { ...lineSpec, animation: true } as ChartSpec;
191
- const { svg } = compileAndRender(spec);
192
-
193
- const lineGroups = svg.querySelectorAll('.oc-mark-line');
194
- expect(lineGroups.length).toBeGreaterThan(0);
195
-
196
- for (const group of lineGroups) {
197
- const el = group as SVGElement & ElementCSSInlineStyle;
198
- const markIndex = el.style.getPropertyValue('--oc-mark-index');
199
- expect(markIndex).not.toBe('');
200
- }
201
- });
202
- });
203
-
204
- // ---------------------------------------------------------------------------
205
- // data-orient attribute for horizontal bars
206
- // ---------------------------------------------------------------------------
207
-
208
- describe('data-orient for horizontal bars', () => {
209
- it('sets data-orient=horizontal on mark groups for horizontal bar charts', () => {
210
- // barSpec has x=quantitative, y=nominal which is a horizontal bar chart
211
- const spec = { ...barSpec, animation: true } as ChartSpec;
212
- const { svg } = compileAndRender(spec);
213
-
214
- const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
215
- expect(horizontalGroups.length).toBeGreaterThan(0);
216
- });
217
-
218
- it('does not set data-orient=horizontal on vertical column charts', () => {
219
- // columnSpec has x=nominal, y=quantitative which is a vertical column chart
220
- const spec = { ...columnSpec, animation: true } as ChartSpec;
221
- const { svg } = compileAndRender(spec);
222
-
223
- const horizontalGroups = svg.querySelectorAll('[data-orient="horizontal"]');
224
- expect(horizontalGroups.length).toBe(0);
225
- });
226
- });
227
-
228
- // ---------------------------------------------------------------------------
229
- // Value-based stagger ordering assigns animationIndex on marks
230
- // ---------------------------------------------------------------------------
231
-
232
- describe('animationIndex for value-based stagger', () => {
233
- it('assigns animationIndex to rect marks when stagger order is value', () => {
234
- const spec = {
235
- ...columnSpec,
236
- animation: {
237
- enter: { stagger: { order: 'value' as const } },
238
- },
239
- } as ChartSpec;
240
- const layout = compileChart(spec, { width: 600, height: 400 });
241
-
242
- const rectMarks = layout.marks.filter((m) => m.type === 'rect');
243
- expect(rectMarks.length).toBe(3);
244
-
245
- // Each rect mark should have animationIndex
246
- for (const mark of rectMarks) {
247
- const idx = (mark as unknown as { animationIndex?: number }).animationIndex;
248
- expect(idx).toBeDefined();
249
- }
250
- });
251
-
252
- it('orders marks by their primary quantitative value', () => {
253
- // columnSpec has revenues: Q1=100, Q2=200, Q3=150
254
- // Value ordering should sort by bar height (revenue)
255
- const spec = {
256
- ...columnSpec,
257
- animation: {
258
- enter: { stagger: { order: 'value' as const } },
259
- },
260
- } as ChartSpec;
261
- const layout = compileChart(spec, { width: 600, height: 400 });
262
-
263
- const rectMarks = layout.marks.filter((m) => m.type === 'rect');
264
- const indexed = rectMarks.map((m) => ({
265
- index: (m as unknown as { animationIndex: number }).animationIndex,
266
- }));
267
-
268
- // All indices should be unique
269
- const indices = indexed.map((m) => m.index);
270
- const uniqueIndices = new Set(indices);
271
- expect(uniqueIndices.size).toBe(rectMarks.length);
272
- });
273
- });
274
-
275
- // ---------------------------------------------------------------------------
276
- // createChart mount lifecycle with animation
277
- // ---------------------------------------------------------------------------
278
-
279
- describe('createChart animation lifecycle', () => {
280
- it('produces valid SVG on mount with animation enabled', () => {
281
- const container = createContainer();
282
- const spec = { ...barSpec, animation: true } as ChartSpec;
283
- const chart = createChart(container, spec);
284
-
285
- const svg = container.querySelector('svg');
286
- expect(svg).not.toBeNull();
287
- expect(svg?.getAttribute('class')).toContain('oc-chart');
288
- expect(svg?.getAttribute('class')).toContain('oc-animate');
289
-
290
- // Marks are rendered
291
- const rects = container.querySelectorAll('.oc-marks rect');
292
- expect(rects.length).toBeGreaterThan(0);
293
-
294
- chart.destroy();
295
- });
296
-
297
- it('update re-renders chart correctly with animation', () => {
298
- const container = createContainer();
299
- const spec = { ...barSpec, animation: true } as ChartSpec;
300
- const chart = createChart(container, spec);
301
-
302
- // Verify initial render
303
- let rects = container.querySelectorAll('.oc-marks rect');
304
- const initialCount = rects.length;
305
- expect(initialCount).toBeGreaterThan(0);
306
-
307
- // Update with different data but same animation setting
308
- const updatedSpec = {
309
- ...barSpec,
310
- data: [
311
- { name: 'X', value: 50 },
312
- { name: 'Y', value: 70 },
313
- ],
314
- animation: true,
315
- } as ChartSpec;
316
- chart.update(updatedSpec);
317
-
318
- // SVG is re-rendered
319
- const svg = container.querySelector('svg');
320
- expect(svg).not.toBeNull();
321
-
322
- // Marks reflect updated data
323
- rects = container.querySelectorAll('.oc-marks rect');
324
- expect(rects.length).toBeGreaterThan(0);
325
-
326
- chart.destroy();
327
- });
328
-
329
- it('works correctly when animation is toggled off on update', () => {
330
- const container = createContainer();
331
- const specWithAnim = { ...barSpec, animation: true } as ChartSpec;
332
- const chart = createChart(container, specWithAnim);
333
-
334
- // Update with animation disabled
335
- const specNoAnim = { ...barSpec, animation: false } as ChartSpec;
336
- chart.update(specNoAnim);
337
-
338
- // Chart should still render correctly
339
- const svg = container.querySelector('svg');
340
- expect(svg).not.toBeNull();
341
- const rects = container.querySelectorAll('.oc-marks rect');
342
- expect(rects.length).toBeGreaterThan(0);
343
-
344
- chart.destroy();
345
- });
346
-
347
- it('destroy cleans up the container', () => {
348
- const container = createContainer();
349
- const spec = { ...barSpec, animation: true } as ChartSpec;
350
- const chart = createChart(container, spec);
351
-
352
- expect(container.querySelector('svg')).not.toBeNull();
353
-
354
- chart.destroy();
355
-
356
- expect(container.querySelector('svg')).toBeNull();
357
- });
358
- });