@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.
- package/dist/{chunk-W34KN7FD.js → chunk-2NAQRP66.js} +2 -2
- package/dist/chunk-DCODCLWI.js +234 -0
- package/dist/chunk-DCODCLWI.js.map +1 -0
- package/dist/{chunk-5EOV5JQZ.js → chunk-EKAUQAAX.js} +50 -46
- package/dist/chunk-EKAUQAAX.js.map +1 -0
- package/dist/{chunk-ENXSUXC5.js → chunk-KNT5AZ2O.js} +7309 -5490
- package/dist/chunk-KNT5AZ2O.js.map +1 -0
- package/dist/chunk-RM5XS6NR.js +113 -0
- package/dist/chunk-RM5XS6NR.js.map +1 -0
- package/dist/chunk-T32UHMZA.js +282 -0
- package/dist/chunk-T32UHMZA.js.map +1 -0
- package/dist/export-gif.d.ts +113 -0
- package/dist/export-gif.js +21 -0
- package/dist/export-gif.js.map +1 -0
- package/dist/index.d.ts +283 -24
- package/dist/index.js +2012 -763
- package/dist/index.js.map +1 -1
- package/dist/{mount-CUKQLeqD.d.ts → mount-Dvv8NYIv.d.ts} +117 -3
- package/dist/simulation-worker.js +83 -2
- package/dist/static.js +7 -69
- package/dist/static.js.map +1 -1
- package/dist/story/index.d.ts +3 -2
- package/dist/story/index.js +5 -3
- package/dist/story/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.css.map +1 -1
- package/package.json +14 -6
- package/dist/chunk-5EOV5JQZ.js.map +0 -1
- package/dist/chunk-ENXSUXC5.js.map +0 -1
- package/src/__test-fixtures__/dom.ts +0 -70
- package/src/__test-fixtures__/specs.ts +0 -214
- package/src/__tests__/animation.test.ts +0 -358
- package/src/__tests__/annotations-render.test.ts +0 -201
- package/src/__tests__/compound-labels.test.ts +0 -122
- package/src/__tests__/continuous-legend.test.ts +0 -91
- package/src/__tests__/crosshair.test.ts +0 -130
- package/src/__tests__/edit-events.test.ts +0 -902
- package/src/__tests__/events.test.ts +0 -387
- package/src/__tests__/export.test.ts +0 -241
- package/src/__tests__/font-race.test.ts +0 -199
- package/src/__tests__/gradient-ids.test.ts +0 -159
- package/src/__tests__/gradient-utils.test.ts +0 -125
- package/src/__tests__/known-bugs-render.test.ts +0 -183
- package/src/__tests__/map.test.ts +0 -293
- package/src/__tests__/mount.test.ts +0 -357
- package/src/__tests__/pattern-fills.test.ts +0 -105
- package/src/__tests__/resize-timing.test.ts +0 -218
- package/src/__tests__/sankey.test.ts +0 -133
- package/src/__tests__/selection-events.test.ts +0 -869
- package/src/__tests__/series-search.test.ts +0 -311
- package/src/__tests__/static.test.ts +0 -141
- package/src/__tests__/svg-renderer.test.ts +0 -895
- package/src/__tests__/table-keyboard.test.ts +0 -361
- package/src/__tests__/table-mount.test.ts +0 -639
- package/src/__tests__/tilemap.test.ts +0 -158
- package/src/__tests__/tooltip.test.ts +0 -337
- package/src/__tests__/transition.test.ts +0 -1777
- package/src/__tests__/you-draw-it.test.ts +0 -273
- package/src/animation-vars.ts +0 -42
- package/src/animation.ts +0 -76
- package/src/barlist-mount.ts +0 -383
- package/src/barlist-renderer.ts +0 -267
- package/src/dom-helpers.ts +0 -17
- package/src/export.ts +0 -482
- package/src/gradient-utils.ts +0 -138
- package/src/graph/__tests__/canvas-renderer.test.ts +0 -713
- package/src/graph/__tests__/graph-mount.test.ts +0 -213
- package/src/graph/__tests__/interaction.test.ts +0 -205
- package/src/graph/__tests__/keyboard.test.ts +0 -653
- package/src/graph/__tests__/search.test.ts +0 -88
- package/src/graph/__tests__/simulation.test.ts +0 -233
- package/src/graph/__tests__/spatial-index.test.ts +0 -142
- package/src/graph/__tests__/zoom.test.ts +0 -203
- package/src/graph/canvas-renderer.ts +0 -747
- package/src/graph/interaction.ts +0 -367
- package/src/graph/keyboard.ts +0 -208
- package/src/graph/search.ts +0 -50
- package/src/graph/simulation-worker-url.ts +0 -12
- package/src/graph/simulation-worker.ts +0 -278
- package/src/graph/simulation.ts +0 -422
- package/src/graph/spatial-index.ts +0 -121
- package/src/graph/types.ts +0 -47
- package/src/graph/worker-protocol.ts +0 -70
- package/src/graph/zoom.ts +0 -134
- package/src/graph-mount.ts +0 -888
- package/src/index.ts +0 -76
- package/src/interactions/chart-events.ts +0 -139
- package/src/interactions/crosshair.ts +0 -233
- package/src/interactions/drag-handler.ts +0 -175
- package/src/interactions/editing-drags.ts +0 -515
- package/src/interactions/index.ts +0 -25
- package/src/interactions/keyboard-nav.ts +0 -111
- package/src/interactions/legend-interaction.ts +0 -46
- package/src/interactions/selection.ts +0 -280
- package/src/interactions/tooltip-events.ts +0 -72
- package/src/map-camera.ts +0 -88
- package/src/map-mount.ts +0 -701
- package/src/map-renderer.ts +0 -338
- package/src/map-transition.ts +0 -83
- package/src/measure-text.ts +0 -134
- package/src/mount.ts +0 -1382
- package/src/pattern-utils.ts +0 -110
- package/src/renderers/annotations.ts +0 -388
- package/src/renderers/axes.ts +0 -351
- package/src/renderers/brand.ts +0 -87
- package/src/renderers/chrome.ts +0 -196
- package/src/renderers/endpoint-labels.ts +0 -150
- package/src/renderers/legend.ts +0 -311
- package/src/renderers/marks.ts +0 -561
- package/src/renderers/metrics.ts +0 -63
- package/src/renderers/svg-dom.ts +0 -40
- package/src/renderers/table-cells.ts +0 -446
- package/src/resize-observer.ts +0 -46
- package/src/sankey-mount.ts +0 -622
- package/src/sankey-renderer.ts +0 -614
- package/src/series-search.ts +0 -392
- package/src/static.ts +0 -251
- package/src/story/__tests__/camera-math.test.ts +0 -145
- package/src/story/__tests__/create-chart-story.test.ts +0 -317
- package/src/story/__tests__/progress-math.test.ts +0 -95
- package/src/story/__tests__/scroll-driver.test.ts +0 -130
- package/src/story/__tests__/tween.test.ts +0 -159
- package/src/story/camera-math.ts +0 -125
- package/src/story/create-chart-story.ts +0 -353
- package/src/story/crossfade.ts +0 -70
- package/src/story/index.ts +0 -39
- package/src/story/progress-math.ts +0 -85
- package/src/story/resolve-camera-target.ts +0 -119
- package/src/story/scroll-driver.ts +0 -206
- package/src/story/story-camera.ts +0 -49
- package/src/story/tween.ts +0 -112
- package/src/story/types.ts +0 -76
- package/src/svg-ids.ts +0 -23
- package/src/svg-renderer.ts +0 -383
- package/src/table-keyboard.ts +0 -266
- package/src/table-mount.ts +0 -567
- package/src/table-renderer.ts +0 -414
- package/src/text-edit-overlay.ts +0 -255
- package/src/theme-tokens.ts +0 -51
- package/src/tilemap-mount.ts +0 -511
- package/src/tilemap-renderer.ts +0 -396
- package/src/tooltip.ts +0 -177
- package/src/transition.ts +0 -2816
- package/src/you-draw-it.ts +0 -480
- /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
|
-
});
|