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,373 +0,0 @@
|
|
|
1
|
-
import { zoomIdentity } from 'd3'
|
|
2
|
-
import { describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { DEFAULT_WAVEFORM_RENDERING_OPTIONS } from '../../core'
|
|
5
|
-
import type { DisplaySeries, DisplayTrack } from './types'
|
|
6
|
-
import {
|
|
7
|
-
axisTextMetrics,
|
|
8
|
-
buildTrackLayouts,
|
|
9
|
-
buildYAxisSeriesGroups,
|
|
10
|
-
formatYAxisTickLabel,
|
|
11
|
-
findClosestTrackAtPointer,
|
|
12
|
-
MAX_MULTI_Y_AXIS_COUNT,
|
|
13
|
-
measureYAxisGroupClearance,
|
|
14
|
-
resolveYAxisTicks,
|
|
15
|
-
resolveYAxisTickCount,
|
|
16
|
-
} from './layout'
|
|
17
|
-
|
|
18
|
-
function series(id: string, minimum: number, maximum: number): DisplaySeries {
|
|
19
|
-
return {
|
|
20
|
-
id,
|
|
21
|
-
name: id,
|
|
22
|
-
color: '#1677ff',
|
|
23
|
-
lineType: 'linear',
|
|
24
|
-
lineStyle: 'solid',
|
|
25
|
-
pointType: 'none',
|
|
26
|
-
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
|
27
|
-
points: [
|
|
28
|
-
{ x: 0, y: minimum },
|
|
29
|
-
{ x: 1, y: maximum },
|
|
30
|
-
],
|
|
31
|
-
xDomain: [0, 1],
|
|
32
|
-
yDomain: [minimum, maximum],
|
|
33
|
-
hasErrorPoints: false,
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function track(seriesList: DisplaySeries[]): DisplayTrack {
|
|
38
|
-
return {
|
|
39
|
-
id: 'track',
|
|
40
|
-
series: seriesList,
|
|
41
|
-
visibleSeries: seriesList,
|
|
42
|
-
xDomain: [0, 1],
|
|
43
|
-
yDomain: [0, 50],
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function layoutForSeries(
|
|
48
|
-
sourceSeries: DisplaySeries,
|
|
49
|
-
rendering = DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
50
|
-
transform = zoomIdentity,
|
|
51
|
-
) {
|
|
52
|
-
const sourceTrack = track([sourceSeries])
|
|
53
|
-
sourceTrack.xDomain = sourceSeries.xDomain
|
|
54
|
-
sourceTrack.yDomain = sourceSeries.yDomain
|
|
55
|
-
return buildTrackLayouts({
|
|
56
|
-
cells: [
|
|
57
|
-
{
|
|
58
|
-
slotIndex: 0,
|
|
59
|
-
row: 0,
|
|
60
|
-
column: 0,
|
|
61
|
-
left: 0,
|
|
62
|
-
top: 0,
|
|
63
|
-
width: 120,
|
|
64
|
-
height: 100,
|
|
65
|
-
plotHeight: 100,
|
|
66
|
-
cellHeight: 130,
|
|
67
|
-
xAxisBand: 30,
|
|
68
|
-
series: sourceTrack,
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
grid: {
|
|
72
|
-
rowCount: 1,
|
|
73
|
-
columnCount: 1,
|
|
74
|
-
showPagination: false,
|
|
75
|
-
fillIncompleteLastRow: false,
|
|
76
|
-
trackLines: {},
|
|
77
|
-
},
|
|
78
|
-
displayMode: 'independent',
|
|
79
|
-
overlayMode: 'single-axis',
|
|
80
|
-
independentTransforms: [transform],
|
|
81
|
-
sharedZoomDomain: sourceSeries.xDomain,
|
|
82
|
-
yDomains: undefined,
|
|
83
|
-
timeUnit: 'ms',
|
|
84
|
-
rendering,
|
|
85
|
-
hideSecondaryLabels: false,
|
|
86
|
-
yAxisLabelX: -50,
|
|
87
|
-
showCompactEmptyTracks: false,
|
|
88
|
-
})[0]!.seriesPaths[0]!
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
describe('multi-value Y-axis grouping', () => {
|
|
92
|
-
it('rebuilds Y scales when the same track ID receives a different domain', () => {
|
|
93
|
-
const first = layoutForSeries(series('shared', 0, 1))
|
|
94
|
-
const second = layoutForSeries(series('shared', 10_000, 20_000))
|
|
95
|
-
|
|
96
|
-
expect(first.yScale.domain()).toEqual([0, 1])
|
|
97
|
-
expect(second.yScale.domain()).toEqual([10_000, 20_000])
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('uses a configured visible Y domain for axis and series scales', () => {
|
|
101
|
-
const source = series('a', 0, 100)
|
|
102
|
-
const sourceTrack = track([source])
|
|
103
|
-
const result = buildTrackLayouts({
|
|
104
|
-
cells: [
|
|
105
|
-
{
|
|
106
|
-
slotIndex: 0,
|
|
107
|
-
row: 0,
|
|
108
|
-
column: 0,
|
|
109
|
-
left: 0,
|
|
110
|
-
top: 0,
|
|
111
|
-
width: 120,
|
|
112
|
-
height: 100,
|
|
113
|
-
plotHeight: 100,
|
|
114
|
-
cellHeight: 130,
|
|
115
|
-
xAxisBand: 30,
|
|
116
|
-
series: sourceTrack,
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
grid: {
|
|
120
|
-
rowCount: 1,
|
|
121
|
-
columnCount: 1,
|
|
122
|
-
showPagination: false,
|
|
123
|
-
fillIncompleteLastRow: false,
|
|
124
|
-
trackLines: {},
|
|
125
|
-
},
|
|
126
|
-
displayMode: 'independent',
|
|
127
|
-
overlayMode: 'single-axis',
|
|
128
|
-
independentTransforms: [zoomIdentity],
|
|
129
|
-
sharedZoomDomain: [0, 1],
|
|
130
|
-
yDomains: { track: [25, 75] },
|
|
131
|
-
timeUnit: 'ms',
|
|
132
|
-
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
133
|
-
hideSecondaryLabels: false,
|
|
134
|
-
yAxisLabelX: -50,
|
|
135
|
-
showCompactEmptyTracks: false,
|
|
136
|
-
})[0]
|
|
137
|
-
|
|
138
|
-
expect(result?.yScale.domain()).toEqual([20, 80])
|
|
139
|
-
expect(result?.seriesPaths[0]?.yScale.domain()).toEqual([20, 80])
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('normalizes configured Y-axis split counts', () => {
|
|
143
|
-
expect(resolveYAxisTickCount(100, 0)).toBe(2)
|
|
144
|
-
expect(resolveYAxisTickCount(100, 4.8)).toBe(4)
|
|
145
|
-
expect(resolveYAxisTickCount(100, Number.NaN)).toBe(5)
|
|
146
|
-
expect(resolveYAxisTickCount(220)).toBe(5)
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it('measures the same final endpoint label that Y-axis rendering uses', () => {
|
|
150
|
-
const ticks = resolveYAxisTicks([-0.7434, -0.3434], 5, false)
|
|
151
|
-
const labels = ticks.values.map((value) =>
|
|
152
|
-
formatYAxisTickLabel(value, ticks.domain, ticks.values, 'V'),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
expect(labels).toEqual(['-0.7434', '-0.6434', '-0.5434', '-0.4434', '(V) -0.3434'])
|
|
156
|
-
expect(axisTextMetrics(ticks.domain, false, undefined, 'V', 5).tickTextWidth).toBe(77)
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
it('includes the compact-axis unit label after its top tick is hidden', () => {
|
|
160
|
-
const ticks = resolveYAxisTicks([0.12345, 100], 5, false)
|
|
161
|
-
const compactTicks = ticks.values.slice(0, -1)
|
|
162
|
-
|
|
163
|
-
expect(formatYAxisTickLabel(compactTicks.at(-1)!, ticks.domain, compactTicks, 'V')).toBe(
|
|
164
|
-
'(V) 75.031',
|
|
165
|
-
)
|
|
166
|
-
expect(axisTextMetrics(ticks.domain, false, undefined, 'V', 5).tickTextWidth).toBe(49)
|
|
167
|
-
expect(axisTextMetrics(ticks.domain, false, undefined, 'V', 5, true).tickTextWidth).toBe(70)
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
it('keeps every overlaid series on one axis in single-axis mode', () => {
|
|
171
|
-
const groups = buildYAxisSeriesGroups(
|
|
172
|
-
track([series('a', 0, 1), series('b', 10, 20)]),
|
|
173
|
-
'single-axis',
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
expect(groups).toHaveLength(1)
|
|
177
|
-
expect(groups[0]?.seriesList.map((item) => item.id)).toEqual(['a', 'b'])
|
|
178
|
-
expect(groups[0]?.domain).toEqual([0, 50])
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('uses the reference left-right axis order and merges overflow into axis four', () => {
|
|
182
|
-
const groups = buildYAxisSeriesGroups(
|
|
183
|
-
track([
|
|
184
|
-
series('a', 0, 1),
|
|
185
|
-
series('b', 10, 11),
|
|
186
|
-
series('c', 20, 21),
|
|
187
|
-
series('d', 30, 31),
|
|
188
|
-
series('e', 40, 50),
|
|
189
|
-
]),
|
|
190
|
-
'multi-axis',
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
expect(groups).toHaveLength(MAX_MULTI_Y_AXIS_COUNT)
|
|
194
|
-
expect(groups.map((group) => group.side)).toEqual(['left', 'left', 'right', 'right'])
|
|
195
|
-
expect(groups.map((group) => group.seriesList.map((item) => item.id))).toEqual([
|
|
196
|
-
['a'],
|
|
197
|
-
['b'],
|
|
198
|
-
['c'],
|
|
199
|
-
['d', 'e'],
|
|
200
|
-
])
|
|
201
|
-
expect(groups[3]?.domain[0]).toBeLessThanOrEqual(30)
|
|
202
|
-
expect(groups[3]?.domain[1]).toBeGreaterThanOrEqual(50)
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('derives merged multi-axis domains from precomputed series domains', () => {
|
|
206
|
-
const first = series('a', -20, -10)
|
|
207
|
-
const second = series('b', 40, 60)
|
|
208
|
-
first.points = [{ x: 0, y: -15 }]
|
|
209
|
-
second.points = [{ x: 0, y: 50 }]
|
|
210
|
-
|
|
211
|
-
const groups = buildYAxisSeriesGroups(
|
|
212
|
-
track([
|
|
213
|
-
series('left', 0, 1),
|
|
214
|
-
series('middle', 10, 11),
|
|
215
|
-
series('right', 20, 21),
|
|
216
|
-
first,
|
|
217
|
-
second,
|
|
218
|
-
]),
|
|
219
|
-
'multi-axis',
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
expect(groups[3]?.domain[0]).toBeLessThanOrEqual(-20)
|
|
223
|
-
expect(groups[3]?.domain[1]).toBeGreaterThanOrEqual(60)
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
it('places two and three axes on the expected sides', () => {
|
|
227
|
-
const source = [series('a', 0, 1), series('b', 10, 11), series('c', 20, 21)]
|
|
228
|
-
|
|
229
|
-
expect(
|
|
230
|
-
buildYAxisSeriesGroups(track(source.slice(0, 2)), 'multi-axis').map((g) => g.side),
|
|
231
|
-
).toEqual(['left', 'right'])
|
|
232
|
-
expect(buildYAxisSeriesGroups(track(source), 'multi-axis').map((g) => g.side)).toEqual([
|
|
233
|
-
'left',
|
|
234
|
-
'right',
|
|
235
|
-
'right',
|
|
236
|
-
])
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('reserves label clearance for exponent-prefixed ticks on both axis sides', () => {
|
|
240
|
-
const layout = buildTrackLayouts({
|
|
241
|
-
cells: [
|
|
242
|
-
{
|
|
243
|
-
slotIndex: 0,
|
|
244
|
-
row: 0,
|
|
245
|
-
column: 0,
|
|
246
|
-
left: 0,
|
|
247
|
-
top: 0,
|
|
248
|
-
width: 600,
|
|
249
|
-
height: 300,
|
|
250
|
-
plotHeight: 300,
|
|
251
|
-
cellHeight: 330,
|
|
252
|
-
xAxisBand: 30,
|
|
253
|
-
series: track([series('left', 1000, 3000), series('right', 1000, 3000)]),
|
|
254
|
-
},
|
|
255
|
-
],
|
|
256
|
-
grid: {
|
|
257
|
-
rowCount: 1,
|
|
258
|
-
columnCount: 1,
|
|
259
|
-
showPagination: false,
|
|
260
|
-
fillIncompleteLastRow: false,
|
|
261
|
-
trackLines: {},
|
|
262
|
-
},
|
|
263
|
-
displayMode: 'independent',
|
|
264
|
-
overlayMode: 'multi-axis',
|
|
265
|
-
independentTransforms: [zoomIdentity],
|
|
266
|
-
sharedZoomDomain: [0, 1],
|
|
267
|
-
timeUnit: 'ms',
|
|
268
|
-
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
269
|
-
hideSecondaryLabels: false,
|
|
270
|
-
yAxisLabelX: -50,
|
|
271
|
-
showCompactEmptyTracks: false,
|
|
272
|
-
})[0]
|
|
273
|
-
|
|
274
|
-
expect(
|
|
275
|
-
layout?.yAxes.map(({ side, x, labelX }) => ({
|
|
276
|
-
side,
|
|
277
|
-
labelOffset: Math.abs(labelX - x),
|
|
278
|
-
})),
|
|
279
|
-
).toEqual([
|
|
280
|
-
{ side: 'left', labelOffset: 55 },
|
|
281
|
-
{ side: 'right', labelOffset: 45 },
|
|
282
|
-
])
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('retains enough outer clearance for long scientific exponents', () => {
|
|
286
|
-
const [group] = buildYAxisSeriesGroups(track([series('long', -1e120, 1e120)]), 'multi-axis')
|
|
287
|
-
|
|
288
|
-
expect(group).toBeDefined()
|
|
289
|
-
expect(measureYAxisGroupClearance(group!)).toBe(72)
|
|
290
|
-
})
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
describe('track hit testing', () => {
|
|
294
|
-
const tracks = [
|
|
295
|
-
{ id: 'first', left: 0, top: 0, width: 100, height: 40 },
|
|
296
|
-
{ id: 'second', left: 0, top: 50, width: 100, height: 40 },
|
|
297
|
-
]
|
|
298
|
-
|
|
299
|
-
it('switches tracks immediately across a boundary less than five pixels apart', () => {
|
|
300
|
-
expect(findClosestTrackAtPointer(tracks, 50, 44)?.id).toBe('first')
|
|
301
|
-
expect(findClosestTrackAtPointer(tracks, 50, 46)?.id).toBe('second')
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
describe('decoration sampling', () => {
|
|
306
|
-
const denseSeries = (): DisplaySeries => ({
|
|
307
|
-
...series('dense', -1, 1),
|
|
308
|
-
pointType: 'circle',
|
|
309
|
-
errorBar: { visible: true, width: 1.5, capWidth: 8 },
|
|
310
|
-
points: Array.from({ length: 1_000 }, (_, index) => ({
|
|
311
|
-
x: index,
|
|
312
|
-
y: Math.sin(index / 20),
|
|
313
|
-
error: index % 200 === 1 ? 0.1 : 0,
|
|
314
|
-
})),
|
|
315
|
-
xDomain: [0, 999],
|
|
316
|
-
hasErrorPoints: true,
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('shares prioritized source points between dense symbols and error bars', () => {
|
|
320
|
-
const sourceSeries = denseSeries()
|
|
321
|
-
const path = layoutForSeries(sourceSeries, {
|
|
322
|
-
...DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
323
|
-
pointMinSpacing: 10,
|
|
324
|
-
errorBarMinSpacing: 12,
|
|
325
|
-
})
|
|
326
|
-
const sourceErrorPoints = sourceSeries.points.filter((point) => point.error !== 0)
|
|
327
|
-
|
|
328
|
-
expect(path.errorBarRenderPoints).toEqual(sourceErrorPoints)
|
|
329
|
-
expect(path.errorBarRenderPoints.every((point) => path.pointRenderPoints.includes(point))).toBe(
|
|
330
|
-
true,
|
|
331
|
-
)
|
|
332
|
-
expect(path.pointRenderPoints.length).toBeLessThanOrEqual(Math.ceil(120 / 12) + 2)
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
it('keeps standalone, zero-error, and non-downsampled decoration behavior', () => {
|
|
336
|
-
const noErrors = denseSeries()
|
|
337
|
-
noErrors.points = noErrors.points.map((point) => ({ x: point.x, y: point.y }))
|
|
338
|
-
noErrors.hasErrorPoints = false
|
|
339
|
-
const zeroErrorPath = layoutForSeries(noErrors)
|
|
340
|
-
expect(zeroErrorPath.errorBarRenderPoints).toEqual([])
|
|
341
|
-
expect(zeroErrorPath.pointRenderPoints.length).toBeLessThanOrEqual(Math.ceil(120 / 10) + 2)
|
|
342
|
-
|
|
343
|
-
const errorsOnly = denseSeries()
|
|
344
|
-
errorsOnly.pointType = 'none'
|
|
345
|
-
const errorsOnlyPath = layoutForSeries(errorsOnly)
|
|
346
|
-
expect(errorsOnlyPath.pointRenderPoints).toEqual([])
|
|
347
|
-
expect(errorsOnlyPath.errorBarRenderPoints.length).toBeLessThanOrEqual(Math.ceil(120 / 12) + 2)
|
|
348
|
-
|
|
349
|
-
const pointsOnly = denseSeries()
|
|
350
|
-
pointsOnly.errorBar.visible = false
|
|
351
|
-
const pointsOnlyPath = layoutForSeries(pointsOnly)
|
|
352
|
-
expect(pointsOnlyPath.errorBarRenderPoints).toEqual([])
|
|
353
|
-
expect(pointsOnlyPath.pointRenderPoints.length).toBeLessThanOrEqual(Math.ceil(120 / 10) + 2)
|
|
354
|
-
|
|
355
|
-
const completePath = layoutForSeries(denseSeries(), {
|
|
356
|
-
...DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
357
|
-
downsample: false,
|
|
358
|
-
})
|
|
359
|
-
expect(completePath.pointRenderPoints).toHaveLength(1_000)
|
|
360
|
-
expect(completePath.errorBarRenderPoints).toHaveLength(5)
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
it('restores every visible source decoration after zooming to sparse spacing', () => {
|
|
364
|
-
const path = layoutForSeries(
|
|
365
|
-
denseSeries(),
|
|
366
|
-
DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
367
|
-
zoomIdentity.scale(200),
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
expect(path.pointRenderPoints.map((point) => point.x)).toEqual([0, 1, 2, 3, 4])
|
|
371
|
-
expect(path.errorBarRenderPoints.map((point) => point.x)).toEqual([1])
|
|
372
|
-
})
|
|
373
|
-
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { calculateRotatedTitleLayout, TITLE_AREA_MAX_HEIGHT, TITLE_AREA_MIN_HEIGHT } from './title'
|
|
4
|
-
|
|
5
|
-
describe('calculateRotatedTitleLayout', () => {
|
|
6
|
-
it('reserves the available width for an unrotated title that fits', () => {
|
|
7
|
-
expect(
|
|
8
|
-
calculateRotatedTitleLayout({
|
|
9
|
-
naturalWidth: 200,
|
|
10
|
-
naturalHeight: 20,
|
|
11
|
-
availableWidth: 300,
|
|
12
|
-
rotation: 0,
|
|
13
|
-
}),
|
|
14
|
-
).toEqual({
|
|
15
|
-
textWidth: 300,
|
|
16
|
-
textHeight: 20,
|
|
17
|
-
visualWidth: 300,
|
|
18
|
-
visualHeight: 20,
|
|
19
|
-
areaHeight: TITLE_AREA_MIN_HEIGHT,
|
|
20
|
-
scale: 1,
|
|
21
|
-
wrapped: false,
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it.each([45, 90, -90, 180])('keeps a %s degree title inside the maximum area', (rotation) => {
|
|
26
|
-
const layout = calculateRotatedTitleLayout({
|
|
27
|
-
naturalWidth: 400,
|
|
28
|
-
naturalHeight: 20,
|
|
29
|
-
availableWidth: 600,
|
|
30
|
-
rotation,
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
expect(layout.areaHeight).toBeGreaterThanOrEqual(TITLE_AREA_MIN_HEIGHT)
|
|
34
|
-
expect(layout.areaHeight).toBeLessThanOrEqual(TITLE_AREA_MAX_HEIGHT)
|
|
35
|
-
expect(layout.visualHeight).toBeLessThanOrEqual(TITLE_AREA_MAX_HEIGHT)
|
|
36
|
-
expect(layout.textWidth).toBe(400)
|
|
37
|
-
expect(layout.scale).toBeLessThanOrEqual(1)
|
|
38
|
-
expect(layout.wrapped).toBe(false)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('wraps an unrotated title to the available width without scaling', () => {
|
|
42
|
-
const layout = calculateRotatedTitleLayout({
|
|
43
|
-
naturalWidth: 1_000,
|
|
44
|
-
naturalHeight: 20,
|
|
45
|
-
availableWidth: 120,
|
|
46
|
-
rotation: 0,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(layout.textWidth).toBe(120)
|
|
50
|
-
expect(layout.textHeight).toBe(180)
|
|
51
|
-
expect(layout.visualWidth).toBe(120)
|
|
52
|
-
expect(layout.visualHeight).toBe(180)
|
|
53
|
-
expect(layout.areaHeight).toBe(198)
|
|
54
|
-
expect(layout.scale).toBe(1)
|
|
55
|
-
expect(layout.wrapped).toBe(true)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('falls back safely for non-finite dimensions and rotation', () => {
|
|
59
|
-
const layout = calculateRotatedTitleLayout({
|
|
60
|
-
naturalWidth: Number.NaN,
|
|
61
|
-
naturalHeight: Number.POSITIVE_INFINITY,
|
|
62
|
-
availableWidth: Number.NaN,
|
|
63
|
-
rotation: Number.NaN,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
expect(layout).toEqual({
|
|
67
|
-
textWidth: 1,
|
|
68
|
-
textHeight: 1,
|
|
69
|
-
visualWidth: 1,
|
|
70
|
-
visualHeight: 1,
|
|
71
|
-
areaHeight: TITLE_AREA_MIN_HEIGHT,
|
|
72
|
-
scale: 1,
|
|
73
|
-
wrapped: false,
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
})
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { prepareWaveformSeries } from './useWaveformData'
|
|
4
|
-
|
|
5
|
-
describe('prepareWaveformSeries compact inputs', () => {
|
|
6
|
-
it('keeps dense typed samples as numeric columns until individual points are requested', () => {
|
|
7
|
-
const values = Float32Array.from({ length: 100_001 }, (_, index) => index / 10)
|
|
8
|
-
const snapshot = values.slice()
|
|
9
|
-
const [series] = prepareWaveformSeries({
|
|
10
|
-
kind: 'typed-samples',
|
|
11
|
-
values,
|
|
12
|
-
sampleRate: 1_000,
|
|
13
|
-
startTime: 2,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
expect(series?.source.isCompact).toBe(true)
|
|
17
|
-
expect(series?.points).toHaveLength(values.length)
|
|
18
|
-
expect(series?.xDomain[0]).toBeLessThanOrEqual(2)
|
|
19
|
-
expect(series?.xDomain[1]).toBeGreaterThanOrEqual(102)
|
|
20
|
-
expect(series?.source.pointAt(50_000)).toEqual({ x: 52, y: 5_000 })
|
|
21
|
-
expect(values).toEqual(snapshot)
|
|
22
|
-
|
|
23
|
-
const workerDataset = series?.source.toWorkerDataset()
|
|
24
|
-
expect(workerDataset).toMatchObject({
|
|
25
|
-
kind: 'samples',
|
|
26
|
-
sampleRate: 1_000,
|
|
27
|
-
startTime: 2,
|
|
28
|
-
})
|
|
29
|
-
if (workerDataset?.kind === 'samples') {
|
|
30
|
-
expect(workerDataset).not.toHaveProperty('sourceIndexes')
|
|
31
|
-
expect(workerDataset.values.buffer).not.toBe(values.buffer)
|
|
32
|
-
expect(workerDataset.values).toEqual(values)
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('preserves invalid-sample time gaps without allocating an X column', () => {
|
|
37
|
-
const [series] = prepareWaveformSeries({
|
|
38
|
-
kind: 'typed-samples',
|
|
39
|
-
values: new Float64Array([1, Number.NaN, 3]),
|
|
40
|
-
sampleRate: 2,
|
|
41
|
-
startTime: 10,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
expect(series?.source.pointsInRange()).toEqual([
|
|
45
|
-
{ x: 10, y: 1 },
|
|
46
|
-
{ x: 11, y: 3 },
|
|
47
|
-
])
|
|
48
|
-
expect(series?.source.toWorkerDataset()).toMatchObject({
|
|
49
|
-
kind: 'samples',
|
|
50
|
-
values: new Float64Array([1, 3]),
|
|
51
|
-
sourceIndexes: new Uint32Array([0, 2]),
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('filters and stably orders compact typed points without materializing source point objects', () => {
|
|
56
|
-
const [series] = prepareWaveformSeries({
|
|
57
|
-
kind: 'typed-points',
|
|
58
|
-
x: new Float64Array([2, 1, 1, Number.NaN]),
|
|
59
|
-
y: new Float32Array([20, 10, 11, 12]),
|
|
60
|
-
error: new Float32Array([1, -1, Number.NaN, 4]),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
expect(series?.source.isCompact).toBe(true)
|
|
64
|
-
expect(series?.source.pointsInRange()).toEqual([
|
|
65
|
-
{ x: 1, y: 10 },
|
|
66
|
-
{ x: 1, y: 11 },
|
|
67
|
-
{ x: 2, y: 20, error: 1 },
|
|
68
|
-
])
|
|
69
|
-
expect(series?.source.visibleRange([1, 1])).toEqual({ start: 0, end: 2 })
|
|
70
|
-
expect(series?.source.nearestPoint(-10)).toEqual({ x: 1, y: 10 })
|
|
71
|
-
expect(series?.source.nearestPoint(10)).toEqual({ x: 2, y: 20, error: 1 })
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
it('retains normal array access, iteration, slicing, mapping, and enumeration semantics', () => {
|
|
75
|
-
const [series] = prepareWaveformSeries({
|
|
76
|
-
kind: 'typed-samples',
|
|
77
|
-
values: new Float32Array([2, 4, 6]),
|
|
78
|
-
sampleRate: 2,
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
expect(series?.points[1]).toEqual({ x: 0.5, y: 4 })
|
|
82
|
-
expect(series?.points.at(-1)).toEqual({ x: 1, y: 6 })
|
|
83
|
-
expect([...(series?.points ?? [])]).toEqual([
|
|
84
|
-
{ x: 0, y: 2 },
|
|
85
|
-
{ x: 0.5, y: 4 },
|
|
86
|
-
{ x: 1, y: 6 },
|
|
87
|
-
])
|
|
88
|
-
expect(series?.points.slice(1)).toEqual([
|
|
89
|
-
{ x: 0.5, y: 4 },
|
|
90
|
-
{ x: 1, y: 6 },
|
|
91
|
-
])
|
|
92
|
-
expect(series?.points.map((point) => point.y)).toEqual([2, 4, 6])
|
|
93
|
-
expect(Object.keys(series?.points ?? [])).toEqual(['0', '1', '2'])
|
|
94
|
-
})
|
|
95
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { resolveAutoSelectedMode } from './latestTaskScheduler'
|
|
4
|
-
|
|
5
|
-
describe('resolveAutoSelectedMode', () => {
|
|
6
|
-
it('uses the exact threshold when no interaction selection is supplied', () => {
|
|
7
|
-
expect(resolveAutoSelectedMode(1_000, 1_000, 200)).toBe('raw')
|
|
8
|
-
expect(resolveAutoSelectedMode(1_001, 1_000, 200)).toBe('sampled')
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it('keeps the previous mode inside the configured interaction hysteresis band', () => {
|
|
12
|
-
expect(resolveAutoSelectedMode(1_100, 1_000, 200, 'raw')).toBe('raw')
|
|
13
|
-
expect(resolveAutoSelectedMode(1_201, 1_000, 200, 'raw')).toBe('sampled')
|
|
14
|
-
expect(resolveAutoSelectedMode(900, 1_000, 200, 'sampled')).toBe('sampled')
|
|
15
|
-
expect(resolveAutoSelectedMode(800, 1_000, 200, 'sampled')).toBe('raw')
|
|
16
|
-
})
|
|
17
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { applyXDomainStrategy } from './xDomain'
|
|
4
|
-
|
|
5
|
-
describe('applyXDomainStrategy', () => {
|
|
6
|
-
it('keeps the exact data domain by default', () => {
|
|
7
|
-
expect(applyXDomainStrategy([0, 4999.999], { type: 'data' })).toEqual([0, 4999.999])
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('expands both bounds to stable nice values', () => {
|
|
11
|
-
expect(applyXDomainStrategy([123, 456], { type: 'nice' })).toEqual([100, 500])
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('can expand only the end bound', () => {
|
|
15
|
-
expect(applyXDomainStrategy([123, 456], { type: 'nice', bounds: 'end' })).toEqual([123, 500])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('keeps explicit domains exact unless they are included', () => {
|
|
19
|
-
expect(applyXDomainStrategy([123, 456], { type: 'nice' }, true)).toEqual([123, 456])
|
|
20
|
-
expect(applyXDomainStrategy([123, 456], { type: 'nice', includeExplicit: true }, true)).toEqual(
|
|
21
|
-
[100, 500],
|
|
22
|
-
)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('falls back to the default tick count for invalid values', () => {
|
|
26
|
-
expect(applyXDomainStrategy([0, 4999.999], { type: 'nice', tickCount: 0 })).toEqual([0, 5000])
|
|
27
|
-
})
|
|
28
|
-
})
|