waveform-analysis 0.1.62 → 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,170 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { prepareWaveformSeries } from './core/useWaveformData'
|
|
3
|
-
import { normalizeWaveformData, normalizeWaveformSeries } from './waveform'
|
|
4
|
-
describe('normalizeWaveformData', () => {
|
|
5
|
-
it('converts samples into time-based points', () => {
|
|
6
|
-
expect(
|
|
7
|
-
normalizeWaveformData({
|
|
8
|
-
kind: 'samples',
|
|
9
|
-
values: [2, 4, 6],
|
|
10
|
-
sampleRate: 2,
|
|
11
|
-
startTime: 1,
|
|
12
|
-
}),
|
|
13
|
-
).toEqual([
|
|
14
|
-
{ x: 1, y: 2 },
|
|
15
|
-
{ x: 1.5, y: 4 },
|
|
16
|
-
{ x: 2, y: 6 },
|
|
17
|
-
])
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('sorts explicit points and filters non-finite values', () => {
|
|
21
|
-
expect(
|
|
22
|
-
normalizeWaveformData({
|
|
23
|
-
kind: 'points',
|
|
24
|
-
points: [
|
|
25
|
-
{ x: 2, y: 4 },
|
|
26
|
-
{ x: Number.NaN, y: 3 },
|
|
27
|
-
{ x: 1, y: 2 },
|
|
28
|
-
],
|
|
29
|
-
}),
|
|
30
|
-
).toEqual([
|
|
31
|
-
{ x: 1, y: 2 },
|
|
32
|
-
{ x: 2, y: 4 },
|
|
33
|
-
])
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('returns no points for an invalid sample rate', () => {
|
|
37
|
-
expect(normalizeWaveformData({ kind: 'samples', values: [1, 2], sampleRate: 0 })).toEqual([])
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('keeps large-data error extrema in the prepared Y domain', () => {
|
|
41
|
-
const points = Array.from({ length: 10_001 }, (_, index) => ({
|
|
42
|
-
x: index,
|
|
43
|
-
y: 0,
|
|
44
|
-
...(index === 5_555 ? { error: 10_000 } : {}),
|
|
45
|
-
}))
|
|
46
|
-
const [series] = prepareWaveformSeries({
|
|
47
|
-
kind: 'series',
|
|
48
|
-
series: [
|
|
49
|
-
{
|
|
50
|
-
name: 'errors',
|
|
51
|
-
errorBar: { visible: true },
|
|
52
|
-
data: { kind: 'points', points },
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
expect(series?.points).toHaveLength(points.length)
|
|
58
|
-
expect(series?.hasErrorPoints).toBe(true)
|
|
59
|
-
expect(series?.yDomain[0]).toBeLessThanOrEqual(-10_000)
|
|
60
|
-
expect(series?.yDomain[1]).toBeGreaterThanOrEqual(10_000)
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('normalizes errors and preserves a pure error-bar series', () => {
|
|
64
|
-
const [series] = normalizeWaveformSeries({
|
|
65
|
-
kind: 'series',
|
|
66
|
-
series: [
|
|
67
|
-
{
|
|
68
|
-
name: 'styled',
|
|
69
|
-
lineType: 'none',
|
|
70
|
-
pointType: 'none',
|
|
71
|
-
errorBar: { visible: true, width: -1, capWidth: Number.NaN },
|
|
72
|
-
data: {
|
|
73
|
-
kind: 'points',
|
|
74
|
-
points: [
|
|
75
|
-
{ x: 0, y: 2, error: 1, lowerError: -1, upperError: 2 },
|
|
76
|
-
{ x: 1, y: 3, error: Number.NaN },
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
expect(series).toMatchObject({
|
|
84
|
-
lineType: 'none',
|
|
85
|
-
pointType: 'none',
|
|
86
|
-
errorBar: { visible: true, width: 1.5, capWidth: 8 },
|
|
87
|
-
points: [
|
|
88
|
-
{ x: 0, y: 2, error: 1, upperError: 2 },
|
|
89
|
-
{ x: 1, y: 3 },
|
|
90
|
-
],
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('falls back to a line only when every series visual is disabled', () => {
|
|
95
|
-
const [series] = normalizeWaveformSeries({
|
|
96
|
-
kind: 'series',
|
|
97
|
-
series: [
|
|
98
|
-
{
|
|
99
|
-
name: 'invisible',
|
|
100
|
-
lineType: 'none',
|
|
101
|
-
pointType: 'none',
|
|
102
|
-
errorBar: { visible: false },
|
|
103
|
-
data: { kind: 'points', points: [{ x: 0, y: 1 }] },
|
|
104
|
-
},
|
|
105
|
-
],
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
expect(series).toMatchObject({
|
|
109
|
-
lineType: 'linear',
|
|
110
|
-
pointType: 'none',
|
|
111
|
-
errorBar: { visible: false },
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
it('includes visible error bounds in the prepared Y domain', () => {
|
|
116
|
-
const [series] = prepareWaveformSeries({
|
|
117
|
-
kind: 'series',
|
|
118
|
-
series: [
|
|
119
|
-
{
|
|
120
|
-
name: 'errors',
|
|
121
|
-
errorBar: { visible: true },
|
|
122
|
-
data: {
|
|
123
|
-
kind: 'points',
|
|
124
|
-
points: [
|
|
125
|
-
{ x: 0, y: 2, lowerError: 3, upperError: 4 },
|
|
126
|
-
{ x: 1, y: 3 },
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
expect(series?.yDomain[0]).toBeLessThanOrEqual(-1)
|
|
134
|
-
expect(series?.yDomain[1]).toBeGreaterThanOrEqual(6)
|
|
135
|
-
expect(series?.hasErrorPoints).toBe(true)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('normalizes multiple named series and removes empty series', () => {
|
|
139
|
-
expect(
|
|
140
|
-
normalizeWaveformSeries({
|
|
141
|
-
kind: 'series',
|
|
142
|
-
series: [
|
|
143
|
-
{
|
|
144
|
-
trackId: 'comparison-track',
|
|
145
|
-
name: 'BT2_2M',
|
|
146
|
-
unit: 'T',
|
|
147
|
-
data: { kind: 'points', points: [{ x: 1, y: 2 }] },
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: 'empty',
|
|
151
|
-
data: { kind: 'samples', values: [1], sampleRate: 0 },
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
}),
|
|
155
|
-
).toEqual([
|
|
156
|
-
{
|
|
157
|
-
id: 'series-0',
|
|
158
|
-
trackId: 'comparison-track',
|
|
159
|
-
name: 'BT2_2M',
|
|
160
|
-
unit: 'T',
|
|
161
|
-
color: undefined,
|
|
162
|
-
lineType: 'linear',
|
|
163
|
-
lineStyle: 'solid',
|
|
164
|
-
pointType: 'none',
|
|
165
|
-
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
|
166
|
-
points: [{ x: 1, y: 2 }],
|
|
167
|
-
},
|
|
168
|
-
])
|
|
169
|
-
})
|
|
170
|
-
})
|
package/src/core/data.test.ts
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { normalizeWaveformData, normalizeWaveformSeries } from './data'
|
|
4
|
-
|
|
5
|
-
describe('waveform data normalization', () => {
|
|
6
|
-
it('builds sample points in one pass while preserving source indexes', () => {
|
|
7
|
-
expect(
|
|
8
|
-
normalizeWaveformData({
|
|
9
|
-
kind: 'samples',
|
|
10
|
-
values: [1, Number.NaN, 3],
|
|
11
|
-
sampleRate: 2,
|
|
12
|
-
startTime: 1,
|
|
13
|
-
}),
|
|
14
|
-
).toEqual([
|
|
15
|
-
{ x: 1, y: 1 },
|
|
16
|
-
{ x: 2, y: 3 },
|
|
17
|
-
])
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('normalizes typed samples without mutating their values or buffer', () => {
|
|
21
|
-
const values = new Float32Array([1, Number.NaN, 3])
|
|
22
|
-
const snapshot = values.slice()
|
|
23
|
-
|
|
24
|
-
expect(
|
|
25
|
-
normalizeWaveformData({
|
|
26
|
-
kind: 'typed-samples',
|
|
27
|
-
values,
|
|
28
|
-
sampleRate: 2,
|
|
29
|
-
startTime: 1,
|
|
30
|
-
}),
|
|
31
|
-
).toEqual([
|
|
32
|
-
{ x: 1, y: 1 },
|
|
33
|
-
{ x: 2, y: 3 },
|
|
34
|
-
])
|
|
35
|
-
expect(values).toEqual(snapshot)
|
|
36
|
-
expect(values.buffer.byteLength).toBe(snapshot.buffer.byteLength)
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('rejects malformed typed sample input without coercing numeric fields', () => {
|
|
40
|
-
expect(
|
|
41
|
-
normalizeWaveformData({
|
|
42
|
-
kind: 'typed-samples',
|
|
43
|
-
values: new Float64Array([1]),
|
|
44
|
-
sampleRate: Number.POSITIVE_INFINITY,
|
|
45
|
-
}),
|
|
46
|
-
).toEqual([])
|
|
47
|
-
expect(
|
|
48
|
-
normalizeWaveformData({
|
|
49
|
-
kind: 'typed-samples',
|
|
50
|
-
values: new Float64Array([1]),
|
|
51
|
-
sampleRate: 1,
|
|
52
|
-
startTime: Number.NaN,
|
|
53
|
-
}),
|
|
54
|
-
).toEqual([])
|
|
55
|
-
expect(
|
|
56
|
-
normalizeWaveformData({
|
|
57
|
-
kind: 'typed-samples',
|
|
58
|
-
values: new Int32Array([1]),
|
|
59
|
-
sampleRate: 1,
|
|
60
|
-
} as unknown as Parameters<typeof normalizeWaveformData>[0]),
|
|
61
|
-
).toEqual([])
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('normalizes typed points, errors, and unordered coordinates without mutation', () => {
|
|
65
|
-
const x = new Float64Array([2, 1, 1, Number.NaN])
|
|
66
|
-
const y = new Float32Array([20, 10, 11, 12])
|
|
67
|
-
const error = new Float64Array([1, -1, Number.NaN, 4])
|
|
68
|
-
const lowerError = new Float32Array([2, 3, 4, 5])
|
|
69
|
-
const upperError = new Float64Array([3, 4, 5, 6])
|
|
70
|
-
const snapshots = [x.slice(), y.slice(), error.slice(), lowerError.slice(), upperError.slice()]
|
|
71
|
-
|
|
72
|
-
expect(
|
|
73
|
-
normalizeWaveformData({ kind: 'typed-points', x, y, error, lowerError, upperError }),
|
|
74
|
-
).toEqual([
|
|
75
|
-
{ x: 1, y: 10, lowerError: 3, upperError: 4 },
|
|
76
|
-
{ x: 1, y: 11, lowerError: 4, upperError: 5 },
|
|
77
|
-
{ x: 2, y: 20, error: 1, lowerError: 2, upperError: 3 },
|
|
78
|
-
])
|
|
79
|
-
expect(x).toEqual(snapshots[0])
|
|
80
|
-
expect(y).toEqual(snapshots[1])
|
|
81
|
-
expect(error).toEqual(snapshots[2])
|
|
82
|
-
expect(lowerError).toEqual(snapshots[3])
|
|
83
|
-
expect(upperError).toEqual(snapshots[4])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('rejects typed points with unsupported arrays or mismatched field lengths', () => {
|
|
87
|
-
expect(
|
|
88
|
-
normalizeWaveformData({
|
|
89
|
-
kind: 'typed-points',
|
|
90
|
-
x: new Float64Array([0, 1]),
|
|
91
|
-
y: new Float32Array([1]),
|
|
92
|
-
}),
|
|
93
|
-
).toEqual([])
|
|
94
|
-
expect(
|
|
95
|
-
normalizeWaveformData({
|
|
96
|
-
kind: 'typed-points',
|
|
97
|
-
x: new Float64Array([0]),
|
|
98
|
-
y: new Float64Array([1]),
|
|
99
|
-
upperError: new Float32Array([1, 2]),
|
|
100
|
-
}),
|
|
101
|
-
).toEqual([])
|
|
102
|
-
expect(
|
|
103
|
-
normalizeWaveformData({
|
|
104
|
-
kind: 'typed-points',
|
|
105
|
-
x: new Float32Array([0]),
|
|
106
|
-
y: new Float32Array([1]),
|
|
107
|
-
} as unknown as Parameters<typeof normalizeWaveformData>[0]),
|
|
108
|
-
).toEqual([])
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('skips sorting already ordered points and normalizes errors', () => {
|
|
112
|
-
const sortSpy = vi.spyOn(Array.prototype, 'sort')
|
|
113
|
-
try {
|
|
114
|
-
const result = normalizeWaveformData({
|
|
115
|
-
kind: 'points',
|
|
116
|
-
points: [
|
|
117
|
-
{ x: 0, y: 1, error: -1, upperError: 2 },
|
|
118
|
-
{ x: 1, y: 2, lowerError: 3 },
|
|
119
|
-
],
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
expect(sortSpy).not.toHaveBeenCalled()
|
|
123
|
-
expect(result).toEqual([
|
|
124
|
-
{ x: 0, y: 1, upperError: 2 },
|
|
125
|
-
{ x: 1, y: 2, lowerError: 3 },
|
|
126
|
-
])
|
|
127
|
-
} finally {
|
|
128
|
-
sortSpy.mockRestore()
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('sorts only unordered points and preserves duplicate-x order', () => {
|
|
133
|
-
expect(
|
|
134
|
-
normalizeWaveformData({
|
|
135
|
-
kind: 'points',
|
|
136
|
-
points: [
|
|
137
|
-
{ x: 2, y: 20 },
|
|
138
|
-
{ x: 1, y: 10 },
|
|
139
|
-
{ x: 1, y: 11 },
|
|
140
|
-
{ x: Number.NaN, y: 12 },
|
|
141
|
-
],
|
|
142
|
-
}),
|
|
143
|
-
).toEqual([
|
|
144
|
-
{ x: 1, y: 10 },
|
|
145
|
-
{ x: 1, y: 11 },
|
|
146
|
-
{ x: 2, y: 20 },
|
|
147
|
-
])
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('preserves every valid point in large data sets', () => {
|
|
151
|
-
const points = Array.from({ length: 10_001 }, (_, index) => ({
|
|
152
|
-
x: index,
|
|
153
|
-
y: index === 5_555 ? 1 : 0,
|
|
154
|
-
...(index === 5_555 ? { error: 10_000 } : {}),
|
|
155
|
-
}))
|
|
156
|
-
|
|
157
|
-
const result = normalizeWaveformData({ kind: 'points', points })
|
|
158
|
-
|
|
159
|
-
expect(result).toHaveLength(points.length)
|
|
160
|
-
expect(result[5_555]).toEqual({ x: 5_555, y: 1, error: 10_000 })
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
it('defaults and normalizes per-series line styles', () => {
|
|
164
|
-
const data = {
|
|
165
|
-
kind: 'series' as const,
|
|
166
|
-
series: [
|
|
167
|
-
{ id: 'solid', name: 'Solid', data: { kind: 'points' as const, points: [{ x: 0, y: 1 }] } },
|
|
168
|
-
{
|
|
169
|
-
id: 'dashed',
|
|
170
|
-
name: 'Dashed',
|
|
171
|
-
lineStyle: 'dashed' as const,
|
|
172
|
-
data: { kind: 'points' as const, points: [{ x: 0, y: 1 }] },
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: 'dash-dot',
|
|
176
|
-
name: 'Dash dot',
|
|
177
|
-
lineStyle: 'dash-dot' as const,
|
|
178
|
-
data: { kind: 'points' as const, points: [{ x: 0, y: 1 }] },
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
id: 'invalid',
|
|
182
|
-
name: 'Invalid',
|
|
183
|
-
lineStyle: 'zigzag' as never,
|
|
184
|
-
data: { kind: 'points' as const, points: [{ x: 0, y: 1 }] },
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
expect(normalizeWaveformSeries(data).map((series) => series.lineStyle)).toEqual([
|
|
190
|
-
'solid',
|
|
191
|
-
'dashed',
|
|
192
|
-
'dash-dot',
|
|
193
|
-
'solid',
|
|
194
|
-
])
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('preserves trimmed shot numbers and omits blank values', () => {
|
|
198
|
-
const result = normalizeWaveformSeries({
|
|
199
|
-
kind: 'series',
|
|
200
|
-
series: [
|
|
201
|
-
{
|
|
202
|
-
id: 'with-shot',
|
|
203
|
-
shotNo: ' 13300 ',
|
|
204
|
-
name: '通道 A',
|
|
205
|
-
data: { kind: 'points', points: [{ x: 0, y: 1 }] },
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
id: 'without-shot',
|
|
209
|
-
shotNo: ' ',
|
|
210
|
-
name: '通道 B',
|
|
211
|
-
data: { kind: 'points', points: [{ x: 0, y: 2 }] },
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
expect(result.map((series) => series.shotNo)).toEqual(['13300', undefined])
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
it('accepts compact inputs inside regular series containers', () => {
|
|
220
|
-
const result = normalizeWaveformSeries({
|
|
221
|
-
kind: 'series',
|
|
222
|
-
series: [
|
|
223
|
-
{
|
|
224
|
-
id: 'typed',
|
|
225
|
-
name: 'Typed',
|
|
226
|
-
data: {
|
|
227
|
-
kind: 'typed-points',
|
|
228
|
-
x: new Float64Array([0, 1]),
|
|
229
|
-
y: new Float32Array([2, 3]),
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
],
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
expect(result).toHaveLength(1)
|
|
236
|
-
expect(result[0]?.points).toEqual([
|
|
237
|
-
{ x: 0, y: 2 },
|
|
238
|
-
{ x: 1, y: 3 },
|
|
239
|
-
])
|
|
240
|
-
})
|
|
241
|
-
})
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type { WaveformPoint, WaveformRenderingOptions } from '../types'
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
6
|
-
hasMinimumVisibleXValues,
|
|
7
|
-
resolveWaveformRenderingOptions,
|
|
8
|
-
selectDecorationPoints,
|
|
9
|
-
selectRenderablePoints,
|
|
10
|
-
selectSeriesRenderPoints,
|
|
11
|
-
} from './rendering'
|
|
12
|
-
|
|
13
|
-
describe('waveform rendering selection', () => {
|
|
14
|
-
const points = Array.from({ length: 10_000 }, (_, index): WaveformPoint => ({
|
|
15
|
-
x: index,
|
|
16
|
-
y: index === 5_001 ? 10_000 : Math.sin(index / 20),
|
|
17
|
-
}))
|
|
18
|
-
|
|
19
|
-
it('clips to the visible domain and retains one continuity point on each side', () => {
|
|
20
|
-
const selected = selectRenderablePoints(
|
|
21
|
-
points,
|
|
22
|
-
[4_000, 4_100],
|
|
23
|
-
500,
|
|
24
|
-
resolveWaveformRenderingOptions({ downsample: false }),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
expect(selected[0].x).toBe(3_999)
|
|
28
|
-
expect(selected.at(-1)?.x).toBe(4_101)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('bounds path density while preserving narrow extrema', () => {
|
|
32
|
-
const selected = selectRenderablePoints(
|
|
33
|
-
points,
|
|
34
|
-
[0, 9_999],
|
|
35
|
-
100,
|
|
36
|
-
resolveWaveformRenderingOptions({ downsampleThreshold: 100, maxPointsPerPixel: 4 }),
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
expect(selected.length).toBeLessThanOrEqual(402)
|
|
40
|
-
expect(selected).toContain(points[5_001])
|
|
41
|
-
expect(selected[0]).toBe(points[0])
|
|
42
|
-
expect(selected.at(-1)).toBe(points.at(-1))
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('normalizes invalid rendering options to stable defaults', () => {
|
|
46
|
-
expect(
|
|
47
|
-
resolveWaveformRenderingOptions({
|
|
48
|
-
downsampleThreshold: -1,
|
|
49
|
-
maxPointsPerPixel: 0,
|
|
50
|
-
pointMinSpacing: -1,
|
|
51
|
-
errorBarMinSpacing: Number.POSITIVE_INFINITY,
|
|
52
|
-
}),
|
|
53
|
-
).toEqual(DEFAULT_WAVEFORM_RENDERING_OPTIONS)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('accepts custom decoration spacing and uses zero to disable it', () => {
|
|
57
|
-
expect(resolveWaveformRenderingOptions({ pointMinSpacing: 6, errorBarMinSpacing: 0 })).toEqual({
|
|
58
|
-
...DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
59
|
-
pointMinSpacing: 6,
|
|
60
|
-
errorBarMinSpacing: 0,
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('normalizes sampling options with strict runtime validation', () => {
|
|
65
|
-
const options: WaveformRenderingOptions = {
|
|
66
|
-
sampling: {
|
|
67
|
-
mode: 'wasm',
|
|
68
|
-
autoThreshold: 1_001.9,
|
|
69
|
-
autoHysteresis: 20.8,
|
|
70
|
-
strategy: 'minmax',
|
|
71
|
-
maxPointsPerPixel: 2.5,
|
|
72
|
-
maxPointCount: 1_000.9,
|
|
73
|
-
rawPointLimit: 50_000.2,
|
|
74
|
-
wasmFailureFallback: 'javascript',
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
expect(resolveWaveformRenderingOptions(options)).toMatchObject({
|
|
79
|
-
downsample: true,
|
|
80
|
-
maxPointsPerPixel: 2.5,
|
|
81
|
-
sampling: {
|
|
82
|
-
mode: 'wasm',
|
|
83
|
-
autoThreshold: 1_001,
|
|
84
|
-
autoHysteresis: 20,
|
|
85
|
-
strategy: 'minmax',
|
|
86
|
-
maxPointsPerPixel: 2.5,
|
|
87
|
-
maxPointCount: 1_000,
|
|
88
|
-
rawPointLimit: 50_000,
|
|
89
|
-
wasmFailureFallback: 'javascript',
|
|
90
|
-
},
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
expect(
|
|
94
|
-
resolveWaveformRenderingOptions({
|
|
95
|
-
sampling: {
|
|
96
|
-
mode: 'invalid',
|
|
97
|
-
autoThreshold: 0,
|
|
98
|
-
autoHysteresis: -1,
|
|
99
|
-
strategy: 'invalid',
|
|
100
|
-
maxPointsPerPixel: Number.POSITIVE_INFINITY,
|
|
101
|
-
maxPointCount: 0,
|
|
102
|
-
rawPointLimit: 0,
|
|
103
|
-
wasmFailureFallback: 'fallback',
|
|
104
|
-
},
|
|
105
|
-
} as unknown as WaveformRenderingOptions),
|
|
106
|
-
).toMatchObject({ sampling: DEFAULT_WAVEFORM_RENDERING_OPTIONS.sampling })
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('maps legacy downsample flags and gives explicit sampling mode precedence', () => {
|
|
110
|
-
expect(resolveWaveformRenderingOptions({ downsample: false }).sampling.mode).toBe('raw')
|
|
111
|
-
expect(resolveWaveformRenderingOptions({ downsample: true }).sampling.mode).toBe('auto')
|
|
112
|
-
expect(
|
|
113
|
-
resolveWaveformRenderingOptions({ downsample: false, sampling: { mode: 'wasm' } }),
|
|
114
|
-
).toMatchObject({ downsample: true, sampling: { mode: 'wasm' } })
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('prefers nested sampling density over the legacy rendering field', () => {
|
|
118
|
-
const legacy = resolveWaveformRenderingOptions({ maxPointsPerPixel: 2 })
|
|
119
|
-
const nested = resolveWaveformRenderingOptions({
|
|
120
|
-
maxPointsPerPixel: 2,
|
|
121
|
-
sampling: { maxPointsPerPixel: 3 },
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
expect(legacy.maxPointsPerPixel).toBe(2)
|
|
125
|
-
expect(legacy.sampling.maxPointsPerPixel).toBe(2)
|
|
126
|
-
expect(nested.maxPointsPerPixel).toBe(3)
|
|
127
|
-
expect(nested.sampling.maxPointsPerPixel).toBe(3)
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('selects evenly distributed source points for dense decorations', () => {
|
|
131
|
-
const selected = selectDecorationPoints(points, [0, 9_999], 100, 10, true)
|
|
132
|
-
|
|
133
|
-
expect(selected.length).toBeLessThanOrEqual(12)
|
|
134
|
-
expect(selected[0]).toBe(points[0])
|
|
135
|
-
expect(selected.at(-1)).toBe(points.at(-1))
|
|
136
|
-
expect(selected.every((point) => points.includes(point))).toBe(true)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('clips decorations exactly to the visible domain and preserves sparse points', () => {
|
|
140
|
-
const sparsePoints = [
|
|
141
|
-
{ x: 0, y: 0 },
|
|
142
|
-
{ x: 40, y: 1 },
|
|
143
|
-
{ x: 80, y: 2 },
|
|
144
|
-
{ x: 120, y: 3 },
|
|
145
|
-
]
|
|
146
|
-
|
|
147
|
-
expect(selectDecorationPoints(sparsePoints, [40, 80], 100, 10, true)).toEqual([
|
|
148
|
-
sparsePoints[1],
|
|
149
|
-
sparsePoints[2],
|
|
150
|
-
])
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
it('supports filtering decoration candidates and disabling sampling', () => {
|
|
154
|
-
const errorPoints = Array.from({ length: 100 }, (_, index) => ({
|
|
155
|
-
x: index,
|
|
156
|
-
y: index,
|
|
157
|
-
error: index % 10 === 0 ? 1 : 0,
|
|
158
|
-
}))
|
|
159
|
-
const hasError = (point: WaveformPoint) => (point.error ?? 0) > 0
|
|
160
|
-
|
|
161
|
-
expect(selectDecorationPoints(errorPoints, [0, 99], 100, 12, true, hasError)).toHaveLength(10)
|
|
162
|
-
expect(selectDecorationPoints(errorPoints, [0, 99], 100, 12, false)).toHaveLength(100)
|
|
163
|
-
expect(selectDecorationPoints(errorPoints, [0, 99], 100, 0, true)).toHaveLength(100)
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('prefers priority candidates within dense decoration buckets', () => {
|
|
167
|
-
const priorityPoints = Array.from({ length: 100 }, (_, index) => ({
|
|
168
|
-
x: index,
|
|
169
|
-
y: index,
|
|
170
|
-
error: index % 20 === 1 ? 1 : 0,
|
|
171
|
-
}))
|
|
172
|
-
const hasError = (point: WaveformPoint) => (point.error ?? 0) > 0
|
|
173
|
-
const selected = selectDecorationPoints(
|
|
174
|
-
priorityPoints,
|
|
175
|
-
[0, 99],
|
|
176
|
-
100,
|
|
177
|
-
20,
|
|
178
|
-
true,
|
|
179
|
-
undefined,
|
|
180
|
-
hasError,
|
|
181
|
-
)
|
|
182
|
-
|
|
183
|
-
expect(selected.filter(hasError)).toEqual(priorityPoints.filter(hasError))
|
|
184
|
-
expect(selected.length).toBeLessThanOrEqual(Math.ceil(100 / 20) + 2)
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('shares visible-range selection for a dense 100k-point series', () => {
|
|
188
|
-
const densePoints = Array.from({ length: 100_000 }, (_, index): WaveformPoint => ({
|
|
189
|
-
x: index,
|
|
190
|
-
y: index === 50_001 ? 10_000 : Math.sin(index / 20),
|
|
191
|
-
error: index % 1_000 === 0 ? 1 : undefined,
|
|
192
|
-
}))
|
|
193
|
-
const selected = selectSeriesRenderPoints(
|
|
194
|
-
densePoints,
|
|
195
|
-
[99_999, 0],
|
|
196
|
-
500,
|
|
197
|
-
resolveWaveformRenderingOptions({
|
|
198
|
-
downsampleThreshold: 100,
|
|
199
|
-
sampling: { autoThreshold: 200_000 },
|
|
200
|
-
}),
|
|
201
|
-
{
|
|
202
|
-
lineVisible: true,
|
|
203
|
-
pointVisible: true,
|
|
204
|
-
errorBarVisible: true,
|
|
205
|
-
hasErrorPoints: true,
|
|
206
|
-
},
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
expect(selected.linePoints.length).toBeLessThanOrEqual(2_002)
|
|
210
|
-
expect(selected.linePoints).toContain(densePoints[50_001])
|
|
211
|
-
expect(selected.linePoints[0]).toBe(densePoints[0])
|
|
212
|
-
expect(selected.linePoints.at(-1)).toBe(densePoints.at(-1))
|
|
213
|
-
expect(selected.pointRenderPoints.length).toBeLessThanOrEqual(52)
|
|
214
|
-
expect(selected.errorBarRenderPoints.every((point) => (point.error ?? 0) > 0)).toBe(true)
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
it('uses a width-bounded placeholder while large-series Worker sampling is pending', () => {
|
|
218
|
-
const rendering = resolveWaveformRenderingOptions({
|
|
219
|
-
sampling: { mode: 'auto', autoThreshold: 1_000, maxPointsPerPixel: 2 },
|
|
220
|
-
})
|
|
221
|
-
const selected = selectSeriesRenderPoints(points, [0, 9_999], 100, rendering, {
|
|
222
|
-
lineVisible: true,
|
|
223
|
-
pointVisible: false,
|
|
224
|
-
errorBarVisible: false,
|
|
225
|
-
hasErrorPoints: false,
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
expect(selected.linePoints).toHaveLength(200)
|
|
229
|
-
expect(selected.linePoints[0]).toBe(points[0])
|
|
230
|
-
expect(selected.linePoints.at(-1)).toBe(points.at(-1))
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('counts unique visible x values across series and reversed domains', () => {
|
|
234
|
-
const first = {
|
|
235
|
-
points: [
|
|
236
|
-
{ x: 0, y: 0 },
|
|
237
|
-
{ x: 1, y: 1 },
|
|
238
|
-
],
|
|
239
|
-
}
|
|
240
|
-
const second = {
|
|
241
|
-
points: [
|
|
242
|
-
{ x: 0, y: 2 },
|
|
243
|
-
{ x: 1, y: 3 },
|
|
244
|
-
{ x: 2, y: 4 },
|
|
245
|
-
],
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
expect(hasMinimumVisibleXValues([first, second], [2, 0], 3)).toBe(true)
|
|
249
|
-
expect(hasMinimumVisibleXValues([first, second], [0, 2], 4)).toBe(false)
|
|
250
|
-
expect(hasMinimumVisibleXValues([first, second], [1, 1], 1)).toBe(true)
|
|
251
|
-
expect(hasMinimumVisibleXValues([first, second], [3, 4], 1)).toBe(false)
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('stops scanning a 100k-point series after reaching the minimum', () => {
|
|
255
|
-
const source = Array.from({ length: 100_000 }, (_, index) => ({ x: index, y: index }))
|
|
256
|
-
let pointReads = 0
|
|
257
|
-
const points = new Proxy(source, {
|
|
258
|
-
get(target, property, receiver) {
|
|
259
|
-
if (typeof property === 'string' && /^\d+$/.test(property)) pointReads += 1
|
|
260
|
-
return Reflect.get(target, property, receiver)
|
|
261
|
-
},
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
expect(hasMinimumVisibleXValues([{ points }], [0, 99_999], 5)).toBe(true)
|
|
265
|
-
expect(pointReads).toBeLessThan(100)
|
|
266
|
-
})
|
|
267
|
-
})
|