waveform-analysis 0.1.63 → 0.1.64
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/README.md +9 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +953 -946
- package/dist/style.css +1 -1
- package/dist/types/types/chart.d.ts +2 -1
- package/package.json +1 -1
- package/src/components/WaveformChart.vue +1 -1
- package/src/components/core/xDomain.ts +26 -2
- package/src/components/rendering/WaveformLegend.vue +1 -1
- package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
- package/src/demo/DemoChartHost.vue +1 -0
- package/src/types/chart.ts +2 -1
- package/src/App.test.ts +0 -400
- package/src/components/annotation/components.test.ts +0 -372
- package/src/components/annotation/markup.test.ts +0 -371
- package/src/components/annotation/serialization.test.ts +0 -114
- package/src/components/core/constants.test.ts +0 -29
- package/src/components/core/fixedYDomainLayout.test.ts +0 -204
- package/src/components/core/grid.test.ts +0 -197
- package/src/components/core/latestTaskScheduler.test.ts +0 -51
- package/src/components/core/layout.test.ts +0 -373
- package/src/components/core/title.test.ts +0 -76
- package/src/components/core/useWaveformData.test.ts +0 -95
- package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
- package/src/components/core/xDomain.test.ts +0 -28
- package/src/components/core/yAxisNice.test.ts +0 -174
- package/src/components/core/yAxisSlotLayout.test.ts +0 -165
- package/src/components/core/yDomain.test.ts +0 -56
- package/src/components/interaction/WaveformTooltip.test.ts +0 -276
- package/src/components/interaction/viewportInteractionState.test.ts +0 -101
- package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
- package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
- package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
- package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
- package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
- package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
- package/src/components/waveformChartCases/gridFill.test.ts +0 -95
- package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
- package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
- package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
- package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
- package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
- package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
- package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
- package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
- package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
- package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
- package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
- package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
- package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
- package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
- package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
- package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
- package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
- package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
- package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
- package/src/components/waveformLegendGeometry.test.ts +0 -21
- package/src/components/waveformNormalization.test.ts +0 -170
- package/src/core/data.test.ts +0 -241
- package/src/core/rendering.test.ts +0 -267
- package/src/core/renderingStrategies.test.ts +0 -90
- package/src/core/samplingReference.test.ts +0 -124
- package/src/core/wasmSampling.test.ts +0 -205
- package/src/core/workerSampling/multiresolution.test.ts +0 -69
- package/src/core/workerSampling/protocol.test.ts +0 -34
- package/src/core/workerSampling/repository.test.ts +0 -400
- package/src/core/workerSampling/repositoryCache.test.ts +0 -163
- package/src/core/workerSampling/repositorySampling.test.ts +0 -106
- package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
- package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
- package/src/data/simulatedWaveforms.test.ts +0 -60
- package/src/demo/plotMarginControls.test.ts +0 -29
- package/src/demo/xAxisLabelControls.test.ts +0 -86
- package/src/test/setup.ts +0 -82
- package/src/test/waveformChart.ts +0 -75
- package/src/utils/domain.test.ts +0 -28
- package/src/utils/formatters.test.ts +0 -131
- package/src/utils/sampling.test.ts +0 -226
- package/src/views/FixedYDomainDemo.test.ts +0 -44
- package/src/views/WasmSamplingDemo.test.ts +0 -13
- package/src/views/wasmSamplingDemoSampling.test.ts +0 -27
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
-
import { defineComponent, h, ref } from 'vue'
|
|
3
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import WaveformChart from '../WaveformChart.vue'
|
|
6
|
-
import type { WaveformData } from '../waveform'
|
|
7
|
-
import { resizeObservers } from '../../test/setup'
|
|
8
|
-
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
9
|
-
|
|
10
|
-
function seriesWindow(ids: string[]) {
|
|
11
|
-
return {
|
|
12
|
-
kind: 'series' as const,
|
|
13
|
-
series: ids.map((id, index) => ({
|
|
14
|
-
id,
|
|
15
|
-
name: id,
|
|
16
|
-
data: {
|
|
17
|
-
kind: 'points' as const,
|
|
18
|
-
points: [
|
|
19
|
-
{ x: 40, y: index },
|
|
20
|
-
{ x: 50, y: index + 1 },
|
|
21
|
-
{ x: 60, y: index + 2 },
|
|
22
|
-
],
|
|
23
|
-
},
|
|
24
|
-
})),
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function fullSeries(ids: string[]): WaveformData {
|
|
29
|
-
return {
|
|
30
|
-
kind: 'series',
|
|
31
|
-
series: ids.map((id, index) => ({
|
|
32
|
-
id,
|
|
33
|
-
name: id,
|
|
34
|
-
data: {
|
|
35
|
-
kind: 'points',
|
|
36
|
-
points: [
|
|
37
|
-
{ x: 0, y: index },
|
|
38
|
-
{ x: 50, y: index + 1 },
|
|
39
|
-
{ x: 100, y: index + 2 },
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
})),
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function configureOverlay(overlay: ReturnType<typeof mount>['element']) {
|
|
47
|
-
const width = Number(overlay.getAttribute('width'))
|
|
48
|
-
const height = Number(overlay.getAttribute('height'))
|
|
49
|
-
Object.defineProperty(overlay, 'getBoundingClientRect', {
|
|
50
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
51
|
-
})
|
|
52
|
-
return { width, height }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function dispatchWheel(overlay: Element, deltaY: number, width: number, height: number) {
|
|
56
|
-
overlay.dispatchEvent(
|
|
57
|
-
new WheelEvent('wheel', {
|
|
58
|
-
deltaY,
|
|
59
|
-
clientX: width / 2,
|
|
60
|
-
clientY: height / 2,
|
|
61
|
-
bubbles: true,
|
|
62
|
-
cancelable: true,
|
|
63
|
-
}),
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe('WaveformChart zoom intent and data rebind', () => {
|
|
68
|
-
it('keeps one shared wheel end after an immediate intent-driven data replacement', async () => {
|
|
69
|
-
vi.useFakeTimers()
|
|
70
|
-
try {
|
|
71
|
-
const data = ref<WaveformData>(fullSeries(['shared']))
|
|
72
|
-
const host = mount(
|
|
73
|
-
defineComponent({
|
|
74
|
-
setup() {
|
|
75
|
-
return () =>
|
|
76
|
-
h(WaveformChart, {
|
|
77
|
-
data: data.value,
|
|
78
|
-
displayMode: 'separated',
|
|
79
|
-
initialXDomain: [0, 100],
|
|
80
|
-
onZoomIntent: () => {
|
|
81
|
-
data.value = seriesWindow(['shared'])
|
|
82
|
-
},
|
|
83
|
-
})
|
|
84
|
-
},
|
|
85
|
-
}),
|
|
86
|
-
)
|
|
87
|
-
resizeObservers.at(-1)?.resize(800, 360)
|
|
88
|
-
await flushPromises()
|
|
89
|
-
|
|
90
|
-
const chart = host.findComponent(WaveformChart)
|
|
91
|
-
const overlay = chart.get('.waveform-chart__overlay')
|
|
92
|
-
const { width, height } = configureOverlay(overlay.element)
|
|
93
|
-
dispatchWheel(overlay.element, -4_000, width, height)
|
|
94
|
-
|
|
95
|
-
const firstIntent = chart.emitted('zoom-intent')?.[0]?.[0] as {
|
|
96
|
-
start: number
|
|
97
|
-
end: number
|
|
98
|
-
}
|
|
99
|
-
expect(firstIntent.end - firstIntent.start).toBeCloseTo(2.5)
|
|
100
|
-
expect(chart.emitted('zoom-change')).toBeUndefined()
|
|
101
|
-
|
|
102
|
-
await flushPromises()
|
|
103
|
-
dispatchWheel(overlay.element, 1_000, width, height)
|
|
104
|
-
await flushPromises()
|
|
105
|
-
await vi.advanceTimersByTimeAsync(199)
|
|
106
|
-
expect(chart.emitted('zoom-end')).toBeUndefined()
|
|
107
|
-
|
|
108
|
-
await vi.advanceTimersByTimeAsync(1)
|
|
109
|
-
await flushPromises()
|
|
110
|
-
const zoomEnd = chart.emitted('zoom-end')?.[0]?.[0] as { start: number; end: number }
|
|
111
|
-
expect(chart.emitted('zoom-intent')).toHaveLength(2)
|
|
112
|
-
expect(chart.emitted('zoom-end')).toHaveLength(1)
|
|
113
|
-
expect(zoomEnd.end - zoomEnd.start).toBeGreaterThan(firstIntent.end - firstIntent.start)
|
|
114
|
-
} finally {
|
|
115
|
-
vi.useRealTimers()
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('retains a pending independent wheel gesture by stable series identity', async () => {
|
|
120
|
-
vi.useFakeTimers()
|
|
121
|
-
try {
|
|
122
|
-
const data = ref<WaveformData>(fullSeries(['channel-a', 'channel-b']))
|
|
123
|
-
const host = mount(
|
|
124
|
-
defineComponent({
|
|
125
|
-
setup() {
|
|
126
|
-
return () =>
|
|
127
|
-
h(WaveformChart, {
|
|
128
|
-
data: data.value,
|
|
129
|
-
displayMode: 'independent',
|
|
130
|
-
grid: { rowCount: 1, columnCount: 2 },
|
|
131
|
-
initialXDomain: [0, 100],
|
|
132
|
-
onZoomIntent: () => {
|
|
133
|
-
data.value = seriesWindow(['channel-b', 'channel-a'])
|
|
134
|
-
},
|
|
135
|
-
})
|
|
136
|
-
},
|
|
137
|
-
}),
|
|
138
|
-
)
|
|
139
|
-
resizeObservers.at(-1)?.resize(800, 360)
|
|
140
|
-
await flushPromises()
|
|
141
|
-
|
|
142
|
-
const chart = host.findComponent(WaveformChart)
|
|
143
|
-
const overlay = chart.findAll('.waveform-chart__overlay--independent')[0]!
|
|
144
|
-
const { width, height } = configureOverlay(overlay.element)
|
|
145
|
-
dispatchWheel(overlay.element, -4_000, width, height)
|
|
146
|
-
await flushPromises()
|
|
147
|
-
await vi.advanceTimersByTimeAsync(200)
|
|
148
|
-
await flushPromises()
|
|
149
|
-
|
|
150
|
-
const zoomEnd = chart.emitted('zoom-end')?.[0]?.[0] as {
|
|
151
|
-
trackIndex: number
|
|
152
|
-
seriesIds: string[]
|
|
153
|
-
}
|
|
154
|
-
expect(chart.emitted('zoom-intent')?.[0]?.[0]).toMatchObject({
|
|
155
|
-
trackIndex: 0,
|
|
156
|
-
seriesIds: ['channel-a'],
|
|
157
|
-
gesture: 'wheel',
|
|
158
|
-
})
|
|
159
|
-
expect(zoomEnd).toMatchObject({ trackIndex: 1, seriesIds: ['channel-a'], gesture: 'wheel' })
|
|
160
|
-
} finally {
|
|
161
|
-
vi.useRealTimers()
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('emits box intent with the selected independent series', async () => {
|
|
166
|
-
const wrapper = await mountSizedChart(fullSeries(['series-a']), { initialXDomain: [0, 100] })
|
|
167
|
-
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
168
|
-
const { width, height } = configureOverlay(overlay.element)
|
|
169
|
-
const dispatchPointer = (type: string, clientX: number, clientY: number) => {
|
|
170
|
-
const event = new MouseEvent(type, { button: 0, clientX, clientY, bubbles: true })
|
|
171
|
-
Object.defineProperty(event, 'pointerId', { value: 11 })
|
|
172
|
-
overlay.element.dispatchEvent(event)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
dispatchPointer('pointerdown', width * 0.25, height / 2)
|
|
176
|
-
dispatchPointer('pointermove', width * 0.75, height / 2)
|
|
177
|
-
dispatchPointer('pointerup', width * 0.75, height / 2)
|
|
178
|
-
await flushPromises()
|
|
179
|
-
|
|
180
|
-
expect(wrapper.emitted('zoom-intent')?.[0]?.[0]).toMatchObject({
|
|
181
|
-
trackIndex: 0,
|
|
182
|
-
seriesIds: ['series-a'],
|
|
183
|
-
gesture: 'box',
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('cancels pending wheel ends when reset or pagination changes the viewport', async () => {
|
|
188
|
-
vi.useFakeTimers()
|
|
189
|
-
try {
|
|
190
|
-
const reset = await mountSizedChart(fullSeries(['reset']), { initialXDomain: [0, 100] })
|
|
191
|
-
const resetOverlay = reset.get('.waveform-chart__overlay--independent')
|
|
192
|
-
const resetBounds = configureOverlay(resetOverlay.element)
|
|
193
|
-
dispatchWheel(resetOverlay.element, -4_000, resetBounds.width, resetBounds.height)
|
|
194
|
-
;(reset.vm as unknown as { resetViewport: () => void }).resetViewport()
|
|
195
|
-
await vi.advanceTimersByTimeAsync(200)
|
|
196
|
-
expect(reset.emitted('zoom-end')).toBeUndefined()
|
|
197
|
-
|
|
198
|
-
const paged = await mountSizedChart(gridSeries(2), {
|
|
199
|
-
displayMode: 'independent',
|
|
200
|
-
grid: { rowCount: 1, columnCount: 1 },
|
|
201
|
-
})
|
|
202
|
-
const pageOverlay = paged.get('.waveform-chart__overlay--independent')
|
|
203
|
-
const pageBounds = configureOverlay(pageOverlay.element)
|
|
204
|
-
dispatchWheel(pageOverlay.element, -4_000, pageBounds.width, pageBounds.height)
|
|
205
|
-
await paged.get('.ant-pagination-next button').trigger('click')
|
|
206
|
-
await flushPromises()
|
|
207
|
-
await vi.advanceTimersByTimeAsync(200)
|
|
208
|
-
expect(paged.emitted('zoom-end')).toBeUndefined()
|
|
209
|
-
} finally {
|
|
210
|
-
vi.useRealTimers()
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
})
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { flushPromises, type VueWrapper } from '@vue/test-utils'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { flushAnimationFrames } from '../../test/setup'
|
|
5
|
-
import { mountSizedChart } from '../../test/waveformChart'
|
|
6
|
-
|
|
7
|
-
const regularPoints = Array.from({ length: 1_200 }, (_, index) => ({ x: index, y: index % 11 }))
|
|
8
|
-
|
|
9
|
-
function visibleXCount(domain: [number, number], values = regularPoints): number {
|
|
10
|
-
return new Set(
|
|
11
|
-
values.filter((point) => point.x >= domain[0] && point.x <= domain[1]).map((point) => point.x),
|
|
12
|
-
).size
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function prepareOverlay(wrapper: VueWrapper, selector: string) {
|
|
16
|
-
const overlay = wrapper.get(selector)
|
|
17
|
-
const width = Number(overlay.attributes('width'))
|
|
18
|
-
const height = Number(overlay.attributes('height'))
|
|
19
|
-
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
20
|
-
value: () => ({ left: 0, top: 0, width, height }),
|
|
21
|
-
})
|
|
22
|
-
return { overlay, width, height }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function dispatchWheel(
|
|
26
|
-
overlay: ReturnType<VueWrapper['get']>,
|
|
27
|
-
width: number,
|
|
28
|
-
height: number,
|
|
29
|
-
deltaY: number,
|
|
30
|
-
) {
|
|
31
|
-
overlay.element.dispatchEvent(
|
|
32
|
-
new WheelEvent('wheel', {
|
|
33
|
-
deltaY,
|
|
34
|
-
clientX: width / 2,
|
|
35
|
-
clientY: height / 2,
|
|
36
|
-
bubbles: true,
|
|
37
|
-
cancelable: true,
|
|
38
|
-
}),
|
|
39
|
-
)
|
|
40
|
-
flushAnimationFrames()
|
|
41
|
-
await flushPromises()
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function dispatchBox(
|
|
45
|
-
overlay: ReturnType<VueWrapper['get']>,
|
|
46
|
-
startX: number,
|
|
47
|
-
endX: number,
|
|
48
|
-
height: number,
|
|
49
|
-
pointerId: number,
|
|
50
|
-
) {
|
|
51
|
-
for (const [type, clientX] of [
|
|
52
|
-
['pointerdown', startX],
|
|
53
|
-
['pointermove', endX],
|
|
54
|
-
['pointerup', endX],
|
|
55
|
-
] as const) {
|
|
56
|
-
const event = new MouseEvent(type, {
|
|
57
|
-
button: 0,
|
|
58
|
-
clientX,
|
|
59
|
-
clientY: height / 2,
|
|
60
|
-
bubbles: true,
|
|
61
|
-
})
|
|
62
|
-
Object.defineProperty(event, 'pointerId', { value: pointerId })
|
|
63
|
-
overlay.element.dispatchEvent(event)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe('WaveformChart point-aware zoom constraints', () => {
|
|
68
|
-
it('zooms to two distinct X values when the scale limit is disabled', async () => {
|
|
69
|
-
const wrapper = await mountSizedChart(
|
|
70
|
-
{ kind: 'points', points: regularPoints },
|
|
71
|
-
{ minVisiblePoints: 2, maxZoomScale: null },
|
|
72
|
-
)
|
|
73
|
-
const { overlay, width, height } = prepareOverlay(
|
|
74
|
-
wrapper,
|
|
75
|
-
'.waveform-chart__overlay--independent',
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
79
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
80
|
-
const twoPointDomain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
81
|
-
expect(visibleXCount(twoPointDomain)).toBe(2)
|
|
82
|
-
|
|
83
|
-
const eventCount = wrapper.emitted('zoom-change')?.length ?? 0
|
|
84
|
-
await dispatchWheel(overlay, width, height, -1_000)
|
|
85
|
-
expect(wrapper.emitted('zoom-change')?.length ?? 0).toBe(eventCount)
|
|
86
|
-
|
|
87
|
-
await dispatchWheel(overlay, width, height, 4_000)
|
|
88
|
-
const partiallyZoomedOut = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
89
|
-
expect(partiallyZoomedOut[1] - partiallyZoomedOut[0]).toBeGreaterThan(
|
|
90
|
-
twoPointDomain[1] - twoPointDomain[0],
|
|
91
|
-
)
|
|
92
|
-
await dispatchWheel(overlay, width, height, 4_000)
|
|
93
|
-
const fullyZoomedOut = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
94
|
-
expect(fullyZoomedOut).toEqual([0, 1_199])
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('keeps two fractional samples when the wheel focus falls between sample positions', async () => {
|
|
98
|
-
const points = Array.from({ length: 1_000 }, (_, index) => ({
|
|
99
|
-
x: -5 + (index * 10) / 999,
|
|
100
|
-
y: index,
|
|
101
|
-
}))
|
|
102
|
-
const wrapper = await mountSizedChart(
|
|
103
|
-
{ kind: 'points', points },
|
|
104
|
-
{ minVisiblePoints: 2, initialXDomain: [-5, 5] },
|
|
105
|
-
)
|
|
106
|
-
const { overlay, width, height } = prepareOverlay(
|
|
107
|
-
wrapper,
|
|
108
|
-
'.waveform-chart__overlay--independent',
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
for (let index = 0; index < 3; index += 1) {
|
|
112
|
-
overlay.element.dispatchEvent(
|
|
113
|
-
new WheelEvent('wheel', {
|
|
114
|
-
deltaY: -4_000,
|
|
115
|
-
clientX: width * 0.5032,
|
|
116
|
-
clientY: height / 2,
|
|
117
|
-
bubbles: true,
|
|
118
|
-
cancelable: true,
|
|
119
|
-
}),
|
|
120
|
-
)
|
|
121
|
-
flushAnimationFrames()
|
|
122
|
-
await flushPromises()
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
126
|
-
expect(visibleXCount(domain, points)).toBe(2)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('uses repeated box zooms to reach exactly two distinct X values', async () => {
|
|
130
|
-
const wrapper = await mountSizedChart(
|
|
131
|
-
{ kind: 'points', points: regularPoints },
|
|
132
|
-
{ minVisiblePoints: 2 },
|
|
133
|
-
)
|
|
134
|
-
const { overlay, width, height } = prepareOverlay(
|
|
135
|
-
wrapper,
|
|
136
|
-
'.waveform-chart__overlay--independent',
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
for (let index = 0; index < 3; index += 1) {
|
|
140
|
-
dispatchBox(overlay, width / 2 - 4, width / 2 + 4, height, index + 1)
|
|
141
|
-
await flushPromises()
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
145
|
-
expect(visibleXCount(domain)).toBe(2)
|
|
146
|
-
const eventCount = wrapper.emitted('zoom-change')?.length ?? 0
|
|
147
|
-
dispatchBox(overlay, width / 2 - 4, width / 2 + 4, height, 10)
|
|
148
|
-
await flushPromises()
|
|
149
|
-
const constrainedDomain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
150
|
-
expect(wrapper.emitted('zoom-change')?.length ?? 0).toBe(eventCount)
|
|
151
|
-
expect(visibleXCount(constrainedDomain)).toBe(2)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('retains the backwards-compatible default maximum zoom scale', async () => {
|
|
155
|
-
const wrapper = await mountSizedChart({ kind: 'points', points: regularPoints })
|
|
156
|
-
const { overlay, width, height } = prepareOverlay(
|
|
157
|
-
wrapper,
|
|
158
|
-
'.waveform-chart__overlay--independent',
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
162
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
163
|
-
expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 40)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('honors an explicit maximum zoom scale', async () => {
|
|
167
|
-
const wrapper = await mountSizedChart(
|
|
168
|
-
{ kind: 'points', points: regularPoints },
|
|
169
|
-
{ maxZoomScale: 20 },
|
|
170
|
-
)
|
|
171
|
-
const { overlay, width, height } = prepareOverlay(
|
|
172
|
-
wrapper,
|
|
173
|
-
'.waveform-chart__overlay--independent',
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
177
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
178
|
-
expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 20)
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('disables the compatibility scale limit when configured with null', async () => {
|
|
182
|
-
const wrapper = await mountSizedChart(
|
|
183
|
-
{ kind: 'points', points: regularPoints },
|
|
184
|
-
{ maxZoomScale: null },
|
|
185
|
-
)
|
|
186
|
-
const { overlay, width, height } = prepareOverlay(
|
|
187
|
-
wrapper,
|
|
188
|
-
'.waveform-chart__overlay--independent',
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
192
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
193
|
-
expect(domain[1] - domain[0]).toBeLessThan(1_199 / 40)
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('falls back to the compatibility limit for an invalid maximum scale', async () => {
|
|
197
|
-
const wrapper = await mountSizedChart(
|
|
198
|
-
{ kind: 'points', points: regularPoints },
|
|
199
|
-
{ maxZoomScale: 0 },
|
|
200
|
-
)
|
|
201
|
-
const { overlay, width, height } = prepareOverlay(
|
|
202
|
-
wrapper,
|
|
203
|
-
'.waveform-chart__overlay--independent',
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
207
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
208
|
-
expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 40)
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('uses the strictest configured zoom constraint', async () => {
|
|
212
|
-
const wrapper = await mountSizedChart(
|
|
213
|
-
{ kind: 'points', points: regularPoints },
|
|
214
|
-
{ minZoomSpan: 50, minVisiblePoints: 2, maxZoomScale: 40 },
|
|
215
|
-
)
|
|
216
|
-
const { overlay, width, height } = prepareOverlay(
|
|
217
|
-
wrapper,
|
|
218
|
-
'.waveform-chart__overlay--independent',
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
222
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
223
|
-
expect(domain[1] - domain[0]).toBeCloseTo(50)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
it('retains two endpoint samples while replacing an independent data window', async () => {
|
|
227
|
-
const sourcePoints = Array.from({ length: 1_000 }, (_, index) => ({
|
|
228
|
-
x: -5 + (index * 10) / 999,
|
|
229
|
-
y: index,
|
|
230
|
-
}))
|
|
231
|
-
const wrapper = await mountSizedChart(
|
|
232
|
-
{ kind: 'points', points: sourcePoints },
|
|
233
|
-
{ minVisiblePoints: 2, initialXDomain: [-5, 5] },
|
|
234
|
-
)
|
|
235
|
-
const { overlay, width, height } = prepareOverlay(
|
|
236
|
-
wrapper,
|
|
237
|
-
'.waveform-chart__overlay--independent',
|
|
238
|
-
)
|
|
239
|
-
for (let index = 0; index < 10; index += 1) {
|
|
240
|
-
const eventCount = wrapper.emitted('zoom-change')?.length ?? 0
|
|
241
|
-
overlay.element.dispatchEvent(
|
|
242
|
-
new WheelEvent('wheel', {
|
|
243
|
-
deltaY: -1_000,
|
|
244
|
-
clientX: width * 0.5032,
|
|
245
|
-
clientY: height / 2,
|
|
246
|
-
bubbles: true,
|
|
247
|
-
cancelable: true,
|
|
248
|
-
}),
|
|
249
|
-
)
|
|
250
|
-
flushAnimationFrames()
|
|
251
|
-
await flushPromises()
|
|
252
|
-
if ((wrapper.emitted('zoom-change')?.length ?? 0) === eventCount) break
|
|
253
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
254
|
-
const windowPoints = sourcePoints.filter(
|
|
255
|
-
(point) => point.x >= domain[0] && point.x <= domain[1],
|
|
256
|
-
)
|
|
257
|
-
await wrapper.setProps({ data: { kind: 'points', points: windowPoints } })
|
|
258
|
-
await flushPromises()
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const finalDomain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
262
|
-
const endpointDomain = wrapper
|
|
263
|
-
.findAll('.waveform-chart__axis-endpoint')
|
|
264
|
-
.slice(0, 2)
|
|
265
|
-
.map((endpoint) => Number(endpoint.text()) / 1_000) as [number, number]
|
|
266
|
-
expect(visibleXCount(finalDomain, sourcePoints)).toBe(2)
|
|
267
|
-
expect(visibleXCount(endpointDomain, sourcePoints)).toBe(2)
|
|
268
|
-
const eventCount = wrapper.emitted('zoom-change')?.length ?? 0
|
|
269
|
-
await dispatchWheel(overlay, width, height, -1_000)
|
|
270
|
-
expect(wrapper.emitted('zoom-change')?.length ?? 0).toBe(eventCount)
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('recomputes irregular point constraints for shared tracks after data replacement', async () => {
|
|
274
|
-
const createData = (offset: number) => ({
|
|
275
|
-
kind: 'series' as const,
|
|
276
|
-
series: [
|
|
277
|
-
{
|
|
278
|
-
id: 'first',
|
|
279
|
-
name: 'First',
|
|
280
|
-
data: {
|
|
281
|
-
kind: 'points' as const,
|
|
282
|
-
points: [0, 1, 4, 20].map((x) => ({ x: x + offset, y: x })),
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
id: 'second',
|
|
287
|
-
name: 'Second',
|
|
288
|
-
data: {
|
|
289
|
-
kind: 'points' as const,
|
|
290
|
-
points: [0, 3, 9, 20].map((x) => ({ x: x + offset, y: x })),
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
})
|
|
295
|
-
const wrapper = await mountSizedChart(createData(0), {
|
|
296
|
-
displayMode: 'separated',
|
|
297
|
-
minVisiblePoints: 2,
|
|
298
|
-
})
|
|
299
|
-
const { overlay, width, height } = prepareOverlay(wrapper, '.waveform-chart__overlay')
|
|
300
|
-
|
|
301
|
-
await dispatchWheel(overlay, width, height, -4_000)
|
|
302
|
-
await wrapper.setProps({ data: createData(100) })
|
|
303
|
-
await flushPromises()
|
|
304
|
-
await dispatchWheel(overlay, width, height, 4_000)
|
|
305
|
-
const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
|
|
306
|
-
expect(domain).toEqual([100, 120])
|
|
307
|
-
})
|
|
308
|
-
})
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import {
|
|
3
|
-
waveformLegendErrorBarPath,
|
|
4
|
-
waveformLegendLinePath,
|
|
5
|
-
waveformLineDasharray,
|
|
6
|
-
} from './rendering/seriesStyle'
|
|
7
|
-
describe('legend series geometry', () => {
|
|
8
|
-
it('keeps line samples centered and clamps error-bar caps to the swatch', () => {
|
|
9
|
-
expect(waveformLegendLinePath('linear')).toBe('M1 8H25')
|
|
10
|
-
expect(waveformLegendLinePath('step-start')).toBe('M1 8H25')
|
|
11
|
-
expect(waveformLegendLinePath('step-middle')).toBe('M1 8H25')
|
|
12
|
-
expect(waveformLegendLinePath('step-end')).toBe('M1 8H25')
|
|
13
|
-
expect(waveformLegendLinePath('step-after')).toBe('M1 8H25')
|
|
14
|
-
expect(waveformLegendLinePath('none')).toBeNull()
|
|
15
|
-
expect(waveformLegendErrorBarPath(10)).toBe('M8 2H18M13 2V14M8 14H18')
|
|
16
|
-
expect(waveformLegendErrorBarPath(100)).toBe('M1 2H25M13 2V14M1 14H25')
|
|
17
|
-
expect(waveformLineDasharray('solid')).toBeUndefined()
|
|
18
|
-
expect(waveformLineDasharray('dashed')).toBe('8 5')
|
|
19
|
-
expect(waveformLineDasharray('dash-dot')).toBe('8 5 1.5 5')
|
|
20
|
-
})
|
|
21
|
-
})
|