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,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 数据抽样算法测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect } from 'vitest'
|
|
6
|
-
import {
|
|
7
|
-
downsampleLTTB,
|
|
8
|
-
downsampleMinMax,
|
|
9
|
-
adaptiveSampling,
|
|
10
|
-
calculateSamplingThreshold,
|
|
11
|
-
} from './sampling'
|
|
12
|
-
import type { WaveformPoint } from '../types'
|
|
13
|
-
|
|
14
|
-
describe('downsampleLTTB', () => {
|
|
15
|
-
it('returns empty array for empty input', () => {
|
|
16
|
-
expect(downsampleLTTB([], 100)).toEqual([])
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('returns original data when threshold >= data length', () => {
|
|
20
|
-
const data: WaveformPoint[] = [
|
|
21
|
-
{ x: 0, y: 0 },
|
|
22
|
-
{ x: 1, y: 1 },
|
|
23
|
-
{ x: 2, y: 2 },
|
|
24
|
-
]
|
|
25
|
-
expect(downsampleLTTB(data, 5)).toEqual(data)
|
|
26
|
-
expect(downsampleLTTB(data, 3)).toEqual(data)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('preserves first and last points', () => {
|
|
30
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
31
|
-
x: i,
|
|
32
|
-
y: Math.sin(i / 100),
|
|
33
|
-
}))
|
|
34
|
-
const sampled = downsampleLTTB(data, 50)
|
|
35
|
-
|
|
36
|
-
expect(sampled[0]).toEqual(data[0])
|
|
37
|
-
expect(sampled[sampled.length - 1]).toEqual(data[data.length - 1])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('reduces data to approximately threshold length', () => {
|
|
41
|
-
const data: WaveformPoint[] = Array.from({ length: 10000 }, (_, i) => ({
|
|
42
|
-
x: i,
|
|
43
|
-
y: Math.sin(i / 100),
|
|
44
|
-
}))
|
|
45
|
-
const threshold = 500
|
|
46
|
-
const sampled = downsampleLTTB(data, threshold)
|
|
47
|
-
|
|
48
|
-
expect(sampled.length).toBe(threshold)
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('maintains sorted order', () => {
|
|
52
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
53
|
-
x: i,
|
|
54
|
-
y: Math.random(),
|
|
55
|
-
}))
|
|
56
|
-
const sampled = downsampleLTTB(data, 100)
|
|
57
|
-
|
|
58
|
-
for (let i = 1; i < sampled.length; i++) {
|
|
59
|
-
expect(sampled[i]!.x).toBeGreaterThan(sampled[i - 1]!.x)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('handles minimum threshold of 3', () => {
|
|
64
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
65
|
-
x: i,
|
|
66
|
-
y: i,
|
|
67
|
-
}))
|
|
68
|
-
const sampled = downsampleLTTB(data, 2)
|
|
69
|
-
|
|
70
|
-
expect(sampled.length).toBeGreaterThanOrEqual(2)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
it('preserves peaks in sine wave', () => {
|
|
74
|
-
// 生成包含明确峰值的正弦波
|
|
75
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
76
|
-
x: i,
|
|
77
|
-
y: Math.sin((i / 1000) * Math.PI * 4), // 4个周期
|
|
78
|
-
}))
|
|
79
|
-
const sampled = downsampleLTTB(data, 100)
|
|
80
|
-
|
|
81
|
-
// 检查是否保留了接近峰值的点
|
|
82
|
-
const maxY = Math.max(...sampled.map((p) => p.y))
|
|
83
|
-
const minY = Math.min(...sampled.map((p) => p.y))
|
|
84
|
-
|
|
85
|
-
expect(maxY).toBeGreaterThan(0.9) // 接近1
|
|
86
|
-
expect(minY).toBeLessThan(-0.9) // 接近-1
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('downsampleMinMax', () => {
|
|
91
|
-
it('returns empty array for empty input', () => {
|
|
92
|
-
expect(downsampleMinMax([], 100)).toEqual([])
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('returns original data when threshold >= data length', () => {
|
|
96
|
-
const data: WaveformPoint[] = [
|
|
97
|
-
{ x: 0, y: 0 },
|
|
98
|
-
{ x: 1, y: 1 },
|
|
99
|
-
{ x: 2, y: 2 },
|
|
100
|
-
]
|
|
101
|
-
expect(downsampleMinMax(data, 5)).toEqual(data)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('captures min and max values in each bucket', () => {
|
|
105
|
-
const data: WaveformPoint[] = [
|
|
106
|
-
{ x: 0, y: 5 },
|
|
107
|
-
{ x: 1, y: 1 }, // min
|
|
108
|
-
{ x: 2, y: 10 }, // max
|
|
109
|
-
{ x: 3, y: 3 },
|
|
110
|
-
{ x: 4, y: 7 },
|
|
111
|
-
]
|
|
112
|
-
const sampled = downsampleMinMax(data, 2)
|
|
113
|
-
|
|
114
|
-
// 应该包含最小值和最大值
|
|
115
|
-
const yValues = sampled.map((p) => p.y)
|
|
116
|
-
expect(yValues).toContain(1)
|
|
117
|
-
expect(yValues).toContain(10)
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('maintains sorted order', () => {
|
|
121
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
122
|
-
x: i,
|
|
123
|
-
y: Math.random(),
|
|
124
|
-
}))
|
|
125
|
-
const sampled = downsampleMinMax(data, 100)
|
|
126
|
-
|
|
127
|
-
for (let i = 1; i < sampled.length; i++) {
|
|
128
|
-
expect(sampled[i]!.x).toBeGreaterThanOrEqual(sampled[i - 1]!.x)
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('preserves overall range of data', () => {
|
|
133
|
-
const data: WaveformPoint[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
134
|
-
x: i,
|
|
135
|
-
y: Math.sin(i / 100) * 100,
|
|
136
|
-
}))
|
|
137
|
-
const sampled = downsampleMinMax(data, 50)
|
|
138
|
-
|
|
139
|
-
const originalMax = Math.max(...data.map((p) => p.y))
|
|
140
|
-
const originalMin = Math.min(...data.map((p) => p.y))
|
|
141
|
-
const sampledMax = Math.max(...sampled.map((p) => p.y))
|
|
142
|
-
const sampledMin = Math.min(...sampled.map((p) => p.y))
|
|
143
|
-
|
|
144
|
-
expect(Math.abs(sampledMax - originalMax)).toBeLessThan(1)
|
|
145
|
-
expect(Math.abs(sampledMin - originalMin)).toBeLessThan(1)
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
describe('adaptiveSampling', () => {
|
|
150
|
-
it('returns original data when below threshold', () => {
|
|
151
|
-
const data: WaveformPoint[] = Array.from({ length: 100 }, (_, i) => ({
|
|
152
|
-
x: i,
|
|
153
|
-
y: i,
|
|
154
|
-
}))
|
|
155
|
-
const result = adaptiveSampling(data, 500)
|
|
156
|
-
|
|
157
|
-
expect(result.points).toEqual(data)
|
|
158
|
-
expect(result.algorithm).toBe('none')
|
|
159
|
-
expect(result.originalCount).toBe(100)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('uses LTTB for moderate data sets', () => {
|
|
163
|
-
const data: WaveformPoint[] = Array.from({ length: 10000 }, (_, i) => ({
|
|
164
|
-
x: i,
|
|
165
|
-
y: Math.sin(i / 100),
|
|
166
|
-
}))
|
|
167
|
-
const result = adaptiveSampling(data, 1000)
|
|
168
|
-
|
|
169
|
-
expect(result.points.length).toBeLessThanOrEqual(1000)
|
|
170
|
-
expect(result.algorithm).toBe('lttb')
|
|
171
|
-
expect(result.originalCount).toBe(10000)
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('uses MinMax for very large data sets', () => {
|
|
175
|
-
const data: WaveformPoint[] = Array.from({ length: 100000 }, (_, i) => ({
|
|
176
|
-
x: i,
|
|
177
|
-
y: Math.sin(i / 100),
|
|
178
|
-
}))
|
|
179
|
-
const result = adaptiveSampling(data, 1000)
|
|
180
|
-
|
|
181
|
-
expect(result.points.length).toBeGreaterThan(0)
|
|
182
|
-
expect(result.algorithm).toBe('minmax')
|
|
183
|
-
expect(result.originalCount).toBe(100000)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('respects custom maxPoints parameter', () => {
|
|
187
|
-
const data: WaveformPoint[] = Array.from({ length: 10000 }, (_, i) => ({
|
|
188
|
-
x: i,
|
|
189
|
-
y: i,
|
|
190
|
-
}))
|
|
191
|
-
const result = adaptiveSampling(data, 200)
|
|
192
|
-
|
|
193
|
-
expect(result.points.length).toBeLessThanOrEqual(200)
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
describe('calculateSamplingThreshold', () => {
|
|
198
|
-
it('returns reasonable threshold for typical viewport', () => {
|
|
199
|
-
const threshold = calculateSamplingThreshold(1000, 1, 2)
|
|
200
|
-
expect(threshold).toBe(2000)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('scales with pixel ratio', () => {
|
|
204
|
-
const threshold1x = calculateSamplingThreshold(1000, 1, 2)
|
|
205
|
-
const threshold2x = calculateSamplingThreshold(1000, 2, 2)
|
|
206
|
-
|
|
207
|
-
expect(threshold2x).toBe(threshold1x * 2)
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('scales with points per pixel', () => {
|
|
211
|
-
const threshold2pp = calculateSamplingThreshold(1000, 1, 2)
|
|
212
|
-
const threshold4pp = calculateSamplingThreshold(1000, 1, 4)
|
|
213
|
-
|
|
214
|
-
expect(threshold4pp).toBe(threshold2pp * 2)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('returns minimum of 100 points', () => {
|
|
218
|
-
const threshold = calculateSamplingThreshold(10, 1, 1)
|
|
219
|
-
expect(threshold).toBeGreaterThanOrEqual(100)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('handles high DPI displays', () => {
|
|
223
|
-
const threshold = calculateSamplingThreshold(1920, 2, 2)
|
|
224
|
-
expect(threshold).toBe(7680)
|
|
225
|
-
})
|
|
226
|
-
})
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { WaveformChart } from '../components'
|
|
5
|
-
import { routes } from '../router'
|
|
6
|
-
import FixedYDomainDemo from './FixedYDomainDemo.vue'
|
|
7
|
-
|
|
8
|
-
describe('fixed Y-domain demo route', () => {
|
|
9
|
-
it('registers the example route', () => {
|
|
10
|
-
expect(routes).toEqual(
|
|
11
|
-
expect.arrayContaining([
|
|
12
|
-
expect.objectContaining({
|
|
13
|
-
path: '/fixed-y-domain',
|
|
14
|
-
name: 'fixed-y-domain',
|
|
15
|
-
}),
|
|
16
|
-
]),
|
|
17
|
-
)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('switches between global, automatic, and per-channel ranges', async () => {
|
|
21
|
-
const wrapper = mount(FixedYDomainDemo)
|
|
22
|
-
await flushPromises()
|
|
23
|
-
const chart = () => wrapper.getComponent(WaveformChart)
|
|
24
|
-
const modeInputs = wrapper.get('[aria-label="Y 轴范围模式"]').findAll('input[type="radio"]')
|
|
25
|
-
|
|
26
|
-
expect(chart().props('yDomain')).toEqual([-80, 80])
|
|
27
|
-
expect(chart().props('yDomains')).toBeUndefined()
|
|
28
|
-
|
|
29
|
-
await modeInputs[0]?.setValue(true)
|
|
30
|
-
await flushPromises()
|
|
31
|
-
expect(chart().props('yDomain')).toBeUndefined()
|
|
32
|
-
expect(chart().props('yDomains')).toBeUndefined()
|
|
33
|
-
|
|
34
|
-
await modeInputs[2]?.setValue(true)
|
|
35
|
-
await flushPromises()
|
|
36
|
-
expect(chart().props('yDomain')).toBeUndefined()
|
|
37
|
-
expect(chart().props('yDomains')).toEqual({
|
|
38
|
-
voltage: [-65, 65],
|
|
39
|
-
current: [-260, 260],
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
wrapper.unmount()
|
|
43
|
-
})
|
|
44
|
-
})
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { routes } from '../router'
|
|
4
|
-
|
|
5
|
-
describe('WASM sampling demo route', () => {
|
|
6
|
-
it('registers the hash-demo route', () => {
|
|
7
|
-
expect(routes).toEqual(
|
|
8
|
-
expect.arrayContaining([
|
|
9
|
-
expect.objectContaining({ path: '/wasm-sampling', name: 'wasm-sampling' }),
|
|
10
|
-
]),
|
|
11
|
-
)
|
|
12
|
-
})
|
|
13
|
-
})
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
samplingSizeOptions,
|
|
5
|
-
showsSamplingSize,
|
|
6
|
-
usesFixedPointCount,
|
|
7
|
-
} from './wasmSamplingDemoSampling'
|
|
8
|
-
|
|
9
|
-
describe('WASM sampling demo sizing', () => {
|
|
10
|
-
it('uses pixel density for peak-oriented and extrema strategies', () => {
|
|
11
|
-
expect(samplingSizeOptions('auto', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
|
|
12
|
-
expect(samplingSizeOptions('peak', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
|
|
13
|
-
expect(samplingSizeOptions('minmax', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('uses a fixed point count for LTTB and aggregate strategies', () => {
|
|
17
|
-
for (const strategy of ['lttb', 'average', 'sum'] as const) {
|
|
18
|
-
expect(usesFixedPointCount(strategy)).toBe(true)
|
|
19
|
-
expect(samplingSizeOptions(strategy, 3, 1_000)).toEqual({ maxPointCount: 1_000 })
|
|
20
|
-
}
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it('does not show a sampling-size control or pass a size for none', () => {
|
|
24
|
-
expect(showsSamplingSize('none')).toBe(false)
|
|
25
|
-
expect(samplingSizeOptions('none', 3, 1_000)).toEqual({})
|
|
26
|
-
})
|
|
27
|
-
})
|