@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,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fill pattern rendering: <pattern> defs lifecycle and mark fill references.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors gradient-ids.test.ts for the pattern half of the defs system:
|
|
5
|
-
* pattern IDs come from the shared nextSvgId counter, so they are
|
|
6
|
-
* deterministic at generation time and globally unique across charts.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { ChartSpec } from '@opendata-ai/openchart-core';
|
|
10
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
11
|
-
import { createContainer } from '../__test-fixtures__/dom';
|
|
12
|
-
import { createChart } from '../mount';
|
|
13
|
-
|
|
14
|
-
function stackedSpec(): ChartSpec {
|
|
15
|
-
return {
|
|
16
|
-
mark: { type: 'bar', fillPattern: 'auto' },
|
|
17
|
-
data: [
|
|
18
|
-
{ quarter: 'Q1', source: 'Solar', twh: 30 },
|
|
19
|
-
{ quarter: 'Q1', source: 'Wind', twh: 45 },
|
|
20
|
-
{ quarter: 'Q1', source: 'Hydro', twh: 25 },
|
|
21
|
-
{ quarter: 'Q1', source: 'Gas', twh: 60 },
|
|
22
|
-
{ quarter: 'Q2', source: 'Solar', twh: 35 },
|
|
23
|
-
{ quarter: 'Q2', source: 'Wind', twh: 50 },
|
|
24
|
-
{ quarter: 'Q2', source: 'Hydro', twh: 22 },
|
|
25
|
-
{ quarter: 'Q2', source: 'Gas', twh: 55 },
|
|
26
|
-
],
|
|
27
|
-
encoding: {
|
|
28
|
-
x: { field: 'quarter', type: 'nominal' },
|
|
29
|
-
y: { field: 'twh', type: 'quantitative', stack: 'zero' },
|
|
30
|
-
color: { field: 'source', type: 'nominal' },
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function collectPatternIds(root: HTMLElement): string[] {
|
|
36
|
-
const ids: string[] = [];
|
|
37
|
-
for (const el of root.querySelectorAll('pattern')) {
|
|
38
|
-
const id = el.getAttribute('id');
|
|
39
|
-
if (id) ids.push(id);
|
|
40
|
-
}
|
|
41
|
-
return ids;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
describe('pattern fill rendering', () => {
|
|
45
|
-
let a: HTMLDivElement;
|
|
46
|
-
let b: HTMLDivElement;
|
|
47
|
-
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
a = createContainer();
|
|
50
|
-
b = createContainer();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
afterEach(() => {
|
|
54
|
-
document.body.innerHTML = '';
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('creates one pattern def per series and references them from bar fills', () => {
|
|
58
|
-
const chart = createChart(a, stackedSpec());
|
|
59
|
-
|
|
60
|
-
const ids = collectPatternIds(a);
|
|
61
|
-
expect(ids.length).toBe(4);
|
|
62
|
-
for (const id of ids) {
|
|
63
|
-
expect(id).toMatch(/^oc-pattern-\d+$/);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Each pattern tile carries a base rect plus pattern geometry
|
|
67
|
-
for (const pattern of a.querySelectorAll('pattern')) {
|
|
68
|
-
expect(pattern.querySelector('rect')).not.toBeNull();
|
|
69
|
-
expect(pattern.children.length).toBe(2);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Every large-enough bar segment fills from a pattern url
|
|
73
|
-
const rects = a.querySelectorAll('.oc-mark-rect rect, .oc-mark-rect path');
|
|
74
|
-
const patternFills = [...rects].filter((el) =>
|
|
75
|
-
(el.getAttribute('fill') ?? '').startsWith('url(#oc-pattern-'),
|
|
76
|
-
);
|
|
77
|
-
expect(patternFills.length).toBeGreaterThan(0);
|
|
78
|
-
|
|
79
|
-
chart.destroy();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('renders solid fills when fillPattern is absent', () => {
|
|
83
|
-
const spec = stackedSpec();
|
|
84
|
-
spec.mark = { type: 'bar' };
|
|
85
|
-
const chart = createChart(a, spec);
|
|
86
|
-
expect(collectPatternIds(a)).toEqual([]);
|
|
87
|
-
chart.destroy();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('two charts produce disjoint pattern IDs', () => {
|
|
91
|
-
const chartA = createChart(a, stackedSpec());
|
|
92
|
-
const chartB = createChart(b, stackedSpec());
|
|
93
|
-
|
|
94
|
-
const idsA = collectPatternIds(a);
|
|
95
|
-
const idsB = collectPatternIds(b);
|
|
96
|
-
expect(idsA.length).toBeGreaterThan(0);
|
|
97
|
-
expect(idsB.length).toBeGreaterThan(0);
|
|
98
|
-
|
|
99
|
-
const all = [...idsA, ...idsB];
|
|
100
|
-
expect(new Set(all).size).toBe(all.length);
|
|
101
|
-
|
|
102
|
-
chartA.destroy();
|
|
103
|
-
chartB.destroy();
|
|
104
|
-
});
|
|
105
|
-
});
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resize timing behavior.
|
|
3
|
-
*
|
|
4
|
-
* These tests verify that:
|
|
5
|
-
* 1. The inner observer's 16ms debounce coalesces a rapid burst of
|
|
6
|
-
* ResizeObserver entries into a single render (no thrash).
|
|
7
|
-
* 2. First ResizeObserver fire does not blank-flash (SVG stays mounted).
|
|
8
|
-
* 3. Resize events during an entrance animation are deferred and replayed
|
|
9
|
-
* on animation end.
|
|
10
|
-
*
|
|
11
|
-
* happy-dom ships a ResizeObserver stub that does not auto-fire on layout,
|
|
12
|
-
* so we override it with a controllable mock that lets us invoke the
|
|
13
|
-
* observer callback synchronously.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
-
import { createAutoHeightContainer, createContainer } from '../__test-fixtures__/dom';
|
|
18
|
-
import { lineSpec, longChromeLineSpec } from '../__test-fixtures__/specs';
|
|
19
|
-
import { createChart } from '../mount';
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Controllable ResizeObserver mock
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
type ObserverCallback = (entries: ResizeObserverEntry[]) => void;
|
|
26
|
-
|
|
27
|
-
interface FakeObserver {
|
|
28
|
-
element: Element | null;
|
|
29
|
-
callback: ObserverCallback;
|
|
30
|
-
disconnected: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const observers: FakeObserver[] = [];
|
|
34
|
-
|
|
35
|
-
class MockResizeObserver {
|
|
36
|
-
private readonly record: FakeObserver;
|
|
37
|
-
|
|
38
|
-
constructor(callback: ObserverCallback) {
|
|
39
|
-
this.record = { element: null, callback, disconnected: false };
|
|
40
|
-
observers.push(this.record);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
observe(element: Element): void {
|
|
44
|
-
this.record.element = element;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
disconnect(): void {
|
|
48
|
-
this.record.disconnected = true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
unobserve(): void {
|
|
52
|
-
// no-op
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** Fire a ResizeObserver callback for the given element with width/height. */
|
|
57
|
-
function fireResize(element: Element, width: number, height: number): void {
|
|
58
|
-
for (const o of observers) {
|
|
59
|
-
if (o.element === element && !o.disconnected) {
|
|
60
|
-
const entry = {
|
|
61
|
-
contentRect: { width, height, top: 0, left: 0, right: width, bottom: height, x: 0, y: 0 },
|
|
62
|
-
target: element,
|
|
63
|
-
} as unknown as ResizeObserverEntry;
|
|
64
|
-
o.callback([entry]);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Tests
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
describe('chart resize timing', () => {
|
|
74
|
-
let container: HTMLDivElement;
|
|
75
|
-
let originalRO: typeof globalThis.ResizeObserver;
|
|
76
|
-
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
observers.length = 0;
|
|
79
|
-
originalRO = globalThis.ResizeObserver;
|
|
80
|
-
// Use MockResizeObserver so tests can drive observer fires deterministically.
|
|
81
|
-
// Cast via unknown to satisfy the stricter ResizeObserver type.
|
|
82
|
-
globalThis.ResizeObserver = MockResizeObserver as unknown as typeof ResizeObserver;
|
|
83
|
-
vi.useFakeTimers();
|
|
84
|
-
container = createContainer();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
afterEach(() => {
|
|
88
|
-
vi.useRealTimers();
|
|
89
|
-
globalThis.ResizeObserver = originalRO;
|
|
90
|
-
document.body.innerHTML = '';
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('coalesces a rapid burst of resize events into a single render', async () => {
|
|
94
|
-
const chart = createChart(container, lineSpec);
|
|
95
|
-
const initialSvg = container.querySelector('svg');
|
|
96
|
-
expect(initialSvg).not.toBeNull();
|
|
97
|
-
|
|
98
|
-
// Track SVG node identity across renders: each render() replaces the SVG,
|
|
99
|
-
// so counting how many distinct SVG nodes appear tells us how many renders ran.
|
|
100
|
-
let renderCount = 0;
|
|
101
|
-
const mo = new MutationObserver((records) => {
|
|
102
|
-
for (const r of records) {
|
|
103
|
-
for (let i = 0; i < r.addedNodes.length; i++) {
|
|
104
|
-
const node = r.addedNodes[i];
|
|
105
|
-
if (node instanceof Element && node.tagName.toLowerCase() === 'svg') {
|
|
106
|
-
renderCount++;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
mo.observe(container, { childList: true });
|
|
112
|
-
|
|
113
|
-
// Fire 20 resize events within a tight window.
|
|
114
|
-
for (let i = 0; i < 20; i++) {
|
|
115
|
-
fireResize(container, 600 + i, 400);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Before the debounce window elapses, no new SVG has been appended.
|
|
119
|
-
expect(renderCount).toBe(0);
|
|
120
|
-
|
|
121
|
-
// Run all pending timers (16ms inner debounce + any outer delay).
|
|
122
|
-
await vi.runAllTimersAsync();
|
|
123
|
-
|
|
124
|
-
// MutationObserver records are delivered as microtasks; flush them.
|
|
125
|
-
mo.takeRecords().forEach((r) => {
|
|
126
|
-
for (let i = 0; i < r.addedNodes.length; i++) {
|
|
127
|
-
const node = r.addedNodes[i];
|
|
128
|
-
if (node instanceof Element && node.tagName.toLowerCase() === 'svg') {
|
|
129
|
-
renderCount++;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// Exactly one re-render should have run for the whole burst (no thrash).
|
|
135
|
-
expect(renderCount).toBe(1);
|
|
136
|
-
|
|
137
|
-
mo.disconnect();
|
|
138
|
-
chart.destroy();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('does not blank-flash: SVG remains mounted through first observer fire', async () => {
|
|
142
|
-
const chart = createChart(container, lineSpec);
|
|
143
|
-
|
|
144
|
-
// SVG is painted immediately on mount, before the observer fires.
|
|
145
|
-
const svgBefore = container.querySelector('svg');
|
|
146
|
-
expect(svgBefore).not.toBeNull();
|
|
147
|
-
|
|
148
|
-
// Fire the observer as it would on first layout.
|
|
149
|
-
fireResize(container, 600, 400);
|
|
150
|
-
|
|
151
|
-
// Even before the debounce elapses, the SVG must still be in the DOM.
|
|
152
|
-
expect(container.querySelector('svg')).not.toBeNull();
|
|
153
|
-
|
|
154
|
-
// And after timers flush, the SVG is still there (possibly a new node).
|
|
155
|
-
await vi.runAllTimersAsync();
|
|
156
|
-
expect(container.querySelector('svg')).not.toBeNull();
|
|
157
|
-
|
|
158
|
-
chart.destroy();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('disconnects the observer on destroy', () => {
|
|
162
|
-
const chart = createChart(container, lineSpec);
|
|
163
|
-
expect(observers.length).toBe(1);
|
|
164
|
-
expect(observers[0].disconnected).toBe(false);
|
|
165
|
-
|
|
166
|
-
chart.destroy();
|
|
167
|
-
|
|
168
|
-
expect(observers[0].disconnected).toBe(true);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('clears any pending resize timer on destroy (no callback after teardown)', async () => {
|
|
172
|
-
const chart = createChart(container, lineSpec);
|
|
173
|
-
|
|
174
|
-
fireResize(container, 700, 500);
|
|
175
|
-
// Destroy before the debounce elapses.
|
|
176
|
-
chart.destroy();
|
|
177
|
-
|
|
178
|
-
await vi.runAllTimersAsync();
|
|
179
|
-
|
|
180
|
-
// destroy() removes the SVG; if the timer fired after destroy it would
|
|
181
|
-
// try to render a new one. No SVG should be present.
|
|
182
|
-
expect(container.querySelector('svg')).toBeNull();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe('auto-height observer feedback guard', () => {
|
|
186
|
-
it('ignores the observer echo from the figure growing itself', async () => {
|
|
187
|
-
const autoContainer = createAutoHeightContainer(390);
|
|
188
|
-
const chart = createChart(autoContainer, longChromeLineSpec);
|
|
189
|
-
|
|
190
|
-
const grownHeight = chart.layout.dimensions.height;
|
|
191
|
-
expect(grownHeight).toBeGreaterThan(400);
|
|
192
|
-
const genBefore = autoContainer.dataset.ocRenderGen;
|
|
193
|
-
|
|
194
|
-
// Growing the SVG grows the container, which fires the observer with
|
|
195
|
-
// our own dimensions. That echo must not trigger a re-render (the
|
|
196
|
-
// re-render would grow again and loop).
|
|
197
|
-
fireResize(autoContainer, 390, grownHeight);
|
|
198
|
-
await vi.runAllTimersAsync();
|
|
199
|
-
expect(autoContainer.dataset.ocRenderGen).toBe(genBefore);
|
|
200
|
-
|
|
201
|
-
chart.destroy();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('still re-renders when the width changes', async () => {
|
|
205
|
-
const autoContainer = createAutoHeightContainer(390);
|
|
206
|
-
const chart = createChart(autoContainer, longChromeLineSpec);
|
|
207
|
-
|
|
208
|
-
const grownHeight = chart.layout.dimensions.height;
|
|
209
|
-
const genBefore = autoContainer.dataset.ocRenderGen;
|
|
210
|
-
|
|
211
|
-
fireResize(autoContainer, 340, grownHeight);
|
|
212
|
-
await vi.runAllTimersAsync();
|
|
213
|
-
expect(autoContainer.dataset.ocRenderGen).not.toBe(genBefore);
|
|
214
|
-
|
|
215
|
-
chart.destroy();
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
});
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
-
import { createContainer } from '../__test-fixtures__/dom';
|
|
3
|
-
import { createSankey } from '../sankey-mount';
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Shared fixtures
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
const basicSankeySpec = {
|
|
10
|
-
type: 'sankey' as const,
|
|
11
|
-
data: [
|
|
12
|
-
{ from: 'A', to: 'C', amount: 10 },
|
|
13
|
-
{ from: 'B', to: 'C', amount: 20 },
|
|
14
|
-
{ from: 'C', to: 'D', amount: 15 },
|
|
15
|
-
{ from: 'C', to: 'E', amount: 15 },
|
|
16
|
-
],
|
|
17
|
-
encoding: {
|
|
18
|
-
source: { field: 'from', type: 'nominal' as const },
|
|
19
|
-
target: { field: 'to', type: 'nominal' as const },
|
|
20
|
-
value: { field: 'amount', type: 'quantitative' as const },
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Tests
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
describe('createSankey', () => {
|
|
29
|
-
let container: HTMLDivElement;
|
|
30
|
-
|
|
31
|
-
beforeEach(() => {
|
|
32
|
-
container = createContainer();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
afterEach(() => {
|
|
36
|
-
document.body.innerHTML = '';
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('mounts an SVG element into the container', () => {
|
|
40
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
41
|
-
|
|
42
|
-
const svg = container.querySelector('svg');
|
|
43
|
-
expect(svg).not.toBeNull();
|
|
44
|
-
expect(svg?.getAttribute('class')).toContain('oc-chart');
|
|
45
|
-
|
|
46
|
-
instance.destroy();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('renders the correct number of sankey node elements', () => {
|
|
50
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
51
|
-
|
|
52
|
-
const nodes = container.querySelectorAll('.oc-sankey-node');
|
|
53
|
-
// A, B, C, D, E = 5 nodes
|
|
54
|
-
expect(nodes).toHaveLength(5);
|
|
55
|
-
|
|
56
|
-
instance.destroy();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('renders the correct number of sankey link elements', () => {
|
|
60
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
61
|
-
|
|
62
|
-
const links = container.querySelectorAll('.oc-sankey-link');
|
|
63
|
-
// A->C, B->C, C->D, C->E = 4 links
|
|
64
|
-
expect(links).toHaveLength(4);
|
|
65
|
-
|
|
66
|
-
instance.destroy();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('creates gradient defs when linkStyle is gradient', () => {
|
|
70
|
-
const spec = { ...basicSankeySpec, linkStyle: 'gradient' as const };
|
|
71
|
-
const instance = createSankey(container, spec, { responsive: false });
|
|
72
|
-
|
|
73
|
-
const gradients = container.querySelectorAll('linearGradient');
|
|
74
|
-
expect(gradients.length).toBeGreaterThan(0);
|
|
75
|
-
|
|
76
|
-
instance.destroy();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('update() replaces the SVG with new content', () => {
|
|
80
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
81
|
-
|
|
82
|
-
const svgBefore = container.querySelector('svg');
|
|
83
|
-
expect(svgBefore).not.toBeNull();
|
|
84
|
-
|
|
85
|
-
// Update with different data
|
|
86
|
-
const updatedSpec = {
|
|
87
|
-
...basicSankeySpec,
|
|
88
|
-
data: [{ from: 'X', to: 'Y', amount: 50 }],
|
|
89
|
-
};
|
|
90
|
-
instance.update(updatedSpec);
|
|
91
|
-
|
|
92
|
-
const svgAfter = container.querySelector('svg');
|
|
93
|
-
expect(svgAfter).not.toBeNull();
|
|
94
|
-
|
|
95
|
-
// Should now have 2 nodes (X and Y) instead of 5
|
|
96
|
-
const nodes = container.querySelectorAll('.oc-sankey-node');
|
|
97
|
-
expect(nodes).toHaveLength(2);
|
|
98
|
-
|
|
99
|
-
instance.destroy();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('destroy() removes the SVG from the container', () => {
|
|
103
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
104
|
-
|
|
105
|
-
const svgBefore = container.querySelector('svg');
|
|
106
|
-
expect(svgBefore).not.toBeNull();
|
|
107
|
-
|
|
108
|
-
instance.destroy();
|
|
109
|
-
|
|
110
|
-
const svgAfter = container.querySelector('svg');
|
|
111
|
-
expect(svgAfter).toBeNull();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('has oc-animate class when animation is enabled', () => {
|
|
115
|
-
const spec = { ...basicSankeySpec, animation: true };
|
|
116
|
-
const instance = createSankey(container, spec, { responsive: false });
|
|
117
|
-
|
|
118
|
-
const svg = container.querySelector('svg');
|
|
119
|
-
expect(svg?.classList.contains('oc-animate')).toBe(true);
|
|
120
|
-
|
|
121
|
-
instance.destroy();
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('layout property returns the compiled layout', () => {
|
|
125
|
-
const instance = createSankey(container, basicSankeySpec, { responsive: false });
|
|
126
|
-
|
|
127
|
-
expect(instance.layout).toBeDefined();
|
|
128
|
-
expect(instance.layout.nodes).toHaveLength(5);
|
|
129
|
-
expect(instance.layout.links).toHaveLength(4);
|
|
130
|
-
|
|
131
|
-
instance.destroy();
|
|
132
|
-
});
|
|
133
|
-
});
|