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,89 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { WorkerSamplingRepository } from './repository'
|
|
4
|
-
|
|
5
|
-
describe('WorkerSamplingRepository typed datasets', () => {
|
|
6
|
-
it('copies, filters, and stably orders typed columns without materializing point objects', () => {
|
|
7
|
-
const repository = new WorkerSamplingRepository()
|
|
8
|
-
const x = Float64Array.from([2, Number.NaN, 1, 1, 3])
|
|
9
|
-
const y = Float32Array.from([20, 99, 10, 11, Number.POSITIVE_INFINITY])
|
|
10
|
-
repository.handle({
|
|
11
|
-
type: 'register-dataset',
|
|
12
|
-
requestId: 80,
|
|
13
|
-
datasetId: 'typed',
|
|
14
|
-
revision: 0,
|
|
15
|
-
dataset: { kind: 'typed', x, y },
|
|
16
|
-
})
|
|
17
|
-
x[0] = 99
|
|
18
|
-
y[2] = 99
|
|
19
|
-
|
|
20
|
-
const sampled = repository.handle({
|
|
21
|
-
type: 'sample-viewport',
|
|
22
|
-
requestId: 81,
|
|
23
|
-
series: [
|
|
24
|
-
{
|
|
25
|
-
seriesId: 'typed',
|
|
26
|
-
datasetId: 'typed',
|
|
27
|
-
revision: 1,
|
|
28
|
-
xDomain: [1, 2],
|
|
29
|
-
plotWidth: 10,
|
|
30
|
-
mode: 'raw',
|
|
31
|
-
strategy: 'peak',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
})
|
|
35
|
-
const belowRange = repository.handle({
|
|
36
|
-
type: 'find-nearest-point',
|
|
37
|
-
requestId: 82,
|
|
38
|
-
datasetId: 'typed',
|
|
39
|
-
revision: 1,
|
|
40
|
-
x: 0,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
expect(sampled).toMatchObject({
|
|
44
|
-
results: [
|
|
45
|
-
{
|
|
46
|
-
output: { kind: 'source-indexes', sourceIndexes: Uint32Array.from([0, 1, 2]) },
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
})
|
|
50
|
-
expect(belowRange).toMatchObject({ status: 'ok', point: undefined })
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('derives sample X values from compact metadata and preserves source-time gaps', () => {
|
|
54
|
-
const repository = new WorkerSamplingRepository()
|
|
55
|
-
const values = new Float32Array([2, 6])
|
|
56
|
-
repository.handle({
|
|
57
|
-
type: 'register-dataset',
|
|
58
|
-
requestId: 90,
|
|
59
|
-
datasetId: 'samples',
|
|
60
|
-
revision: 0,
|
|
61
|
-
dataset: {
|
|
62
|
-
kind: 'samples',
|
|
63
|
-
values,
|
|
64
|
-
sampleRate: 2,
|
|
65
|
-
startTime: 10,
|
|
66
|
-
sourceIndexes: new Uint32Array([0, 2]),
|
|
67
|
-
},
|
|
68
|
-
})
|
|
69
|
-
values[0] = 99
|
|
70
|
-
|
|
71
|
-
expect(
|
|
72
|
-
repository.handle({
|
|
73
|
-
type: 'find-nearest-point',
|
|
74
|
-
requestId: 91,
|
|
75
|
-
datasetId: 'samples',
|
|
76
|
-
revision: 1,
|
|
77
|
-
x: 10.9,
|
|
78
|
-
}),
|
|
79
|
-
).toMatchObject({ point: { x: 11, y: 6 } })
|
|
80
|
-
expect(
|
|
81
|
-
repository.handle({
|
|
82
|
-
type: 'get-dataset-metrics',
|
|
83
|
-
requestId: 92,
|
|
84
|
-
datasetId: 'samples',
|
|
85
|
-
revision: 1,
|
|
86
|
-
}),
|
|
87
|
-
).toMatchObject({ metrics: { validPointCount: 2, xDomain: [10, 11], yDomain: [2, 6] } })
|
|
88
|
-
})
|
|
89
|
-
})
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
2
|
-
import { resolve } from 'node:path'
|
|
3
|
-
|
|
4
|
-
import { beforeAll, describe, expect, it } from 'vitest'
|
|
5
|
-
|
|
6
|
-
import { createInitializedWasmSamplingBackend, initializeWasmSampling } from '../wasmSampling'
|
|
7
|
-
import { WorkerSamplingRepository } from './repository'
|
|
8
|
-
|
|
9
|
-
describe('WorkerSamplingRepository WASM datasets', () => {
|
|
10
|
-
beforeAll(async () => {
|
|
11
|
-
await initializeWasmSampling(
|
|
12
|
-
await readFile(resolve(process.cwd(), 'wasm/pkg/waveform_sampling_wasm_bg.wasm')),
|
|
13
|
-
)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('registers typed columns in WASM, reports lazy index bytes, and releases handles on replace', () => {
|
|
17
|
-
const repository = new WorkerSamplingRepository({
|
|
18
|
-
wasmBackend: createInitializedWasmSamplingBackend(),
|
|
19
|
-
indexMaxBytes: 1024 * 1024,
|
|
20
|
-
})
|
|
21
|
-
const register = repository.handle({
|
|
22
|
-
type: 'register-dataset',
|
|
23
|
-
requestId: 1,
|
|
24
|
-
datasetId: 'typed',
|
|
25
|
-
revision: 0,
|
|
26
|
-
dataset: {
|
|
27
|
-
kind: 'typed',
|
|
28
|
-
x: Float64Array.from({ length: 65 }, (_, index) => index / 100),
|
|
29
|
-
y: Float32Array.from({ length: 65 }, (_, index) => (index === 31 ? 50 : index)),
|
|
30
|
-
},
|
|
31
|
-
})
|
|
32
|
-
const sampled = repository.handle({
|
|
33
|
-
type: 'sample-viewport',
|
|
34
|
-
requestId: 2,
|
|
35
|
-
series: [
|
|
36
|
-
{
|
|
37
|
-
seriesId: 'typed',
|
|
38
|
-
datasetId: 'typed',
|
|
39
|
-
revision: 1,
|
|
40
|
-
xDomain: [0.03, 0.61],
|
|
41
|
-
plotWidth: 4,
|
|
42
|
-
mode: 'wasm',
|
|
43
|
-
strategy: 'peak',
|
|
44
|
-
maxPointsPerPixel: 2,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(register).toMatchObject({ status: 'ok', metrics: { indexBytes: 0 } })
|
|
50
|
-
expect(sampled).toMatchObject({ results: [{ diagnostics: { backend: 'wasm' } }] })
|
|
51
|
-
expect(repository.resourceMetrics.indexBytes).toBeGreaterThan(0)
|
|
52
|
-
|
|
53
|
-
const replaced = repository.handle({
|
|
54
|
-
type: 'replace-dataset',
|
|
55
|
-
requestId: 3,
|
|
56
|
-
datasetId: 'typed',
|
|
57
|
-
revision: 1,
|
|
58
|
-
dataset: {
|
|
59
|
-
kind: 'typed',
|
|
60
|
-
x: Float64Array.from([0, 1]),
|
|
61
|
-
y: Float32Array.from([1, 2]),
|
|
62
|
-
},
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
expect(replaced).toMatchObject({ status: 'ok', revision: 2, metrics: { indexBytes: 0 } })
|
|
66
|
-
expect(repository.resourceMetrics.indexBytes).toBe(0)
|
|
67
|
-
repository.handle({ type: 'dispose-all', requestId: 4 })
|
|
68
|
-
expect(repository.resourceMetrics).toMatchObject({ datasetCount: 0, indexBytes: 0 })
|
|
69
|
-
})
|
|
70
|
-
})
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { createSimulatedWaveformData } from './simulatedWaveforms'
|
|
4
|
-
|
|
5
|
-
describe('simulated waveform data', () => {
|
|
6
|
-
it('creates deterministic, finite eight-channel data with a three-series second frame', () => {
|
|
7
|
-
const first = createSimulatedWaveformData()
|
|
8
|
-
const second = createSimulatedWaveformData()
|
|
9
|
-
|
|
10
|
-
expect(first).toEqual(second)
|
|
11
|
-
expect(first.kind).toBe('series')
|
|
12
|
-
if (first.kind !== 'series') return
|
|
13
|
-
|
|
14
|
-
expect(first.series).toHaveLength(8)
|
|
15
|
-
expect(new Set(first.series.map((series) => series.id)).size).toBe(8)
|
|
16
|
-
const secondFrame = first.series.filter(
|
|
17
|
-
(series) => series.trackId === 'simulated-harmonic-frame',
|
|
18
|
-
)
|
|
19
|
-
expect(secondFrame).toHaveLength(3)
|
|
20
|
-
expect(new Set(first.series.map((series) => series.shotNo))).toEqual(new Set(['13300']))
|
|
21
|
-
const firstSeries = first.series[0]
|
|
22
|
-
expect(firstSeries).toMatchObject({
|
|
23
|
-
id: 'simulated-sine',
|
|
24
|
-
lineType: 'linear',
|
|
25
|
-
pointType: 'circle',
|
|
26
|
-
errorBar: { visible: true, width: 1.5, capWidth: 10 },
|
|
27
|
-
})
|
|
28
|
-
if (firstSeries?.data.kind === 'points') {
|
|
29
|
-
const lowerErrors = firstSeries.data.points.map((point) => point.lowerError)
|
|
30
|
-
const upperErrors = firstSeries.data.points.map((point) => point.upperError)
|
|
31
|
-
expect(
|
|
32
|
-
lowerErrors.every((error) => error !== undefined && error >= 0.05 && error < 0.5),
|
|
33
|
-
).toBe(true)
|
|
34
|
-
expect(
|
|
35
|
-
upperErrors.every((error) => error !== undefined && error >= 0.05 && error < 0.5),
|
|
36
|
-
).toBe(true)
|
|
37
|
-
expect(new Set(lowerErrors).size).toBeGreaterThan(900)
|
|
38
|
-
expect(new Set(upperErrors).size).toBeGreaterThan(900)
|
|
39
|
-
}
|
|
40
|
-
first.series.forEach((series) => {
|
|
41
|
-
expect(series.data.kind).toBe('points')
|
|
42
|
-
if (series.data.kind !== 'points') return
|
|
43
|
-
if (series.id === 'simulated-step') {
|
|
44
|
-
expect(series.data.points).toHaveLength(500)
|
|
45
|
-
expect(series.data.points.every((point) => point.x >= 0)).toBe(true)
|
|
46
|
-
} else {
|
|
47
|
-
expect(series.data.points).toHaveLength(1_000)
|
|
48
|
-
expect(series.data.points[0]?.x).toBe(-5)
|
|
49
|
-
}
|
|
50
|
-
expect(series.data.points.at(-1)?.x).toBe(5)
|
|
51
|
-
series.data.points.forEach((point) => {
|
|
52
|
-
expect(Number.isFinite(point.x)).toBe(true)
|
|
53
|
-
expect(Number.isFinite(point.y)).toBe(true)
|
|
54
|
-
if (point.error !== undefined) expect(point.error).toBeGreaterThanOrEqual(0)
|
|
55
|
-
if (point.lowerError !== undefined) expect(point.lowerError).toBeGreaterThanOrEqual(0)
|
|
56
|
-
if (point.upperError !== undefined) expect(point.upperError).toBeGreaterThanOrEqual(0)
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
-
import { InputNumber } from 'ant-design-vue'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import App from '../App.vue'
|
|
6
|
-
import { WaveformChart } from '../components'
|
|
7
|
-
|
|
8
|
-
describe('plot margin demo controls', () => {
|
|
9
|
-
it('updates the chart top and bottom margins from the sidebar', async () => {
|
|
10
|
-
const wrapper = mount(App)
|
|
11
|
-
await flushPromises()
|
|
12
|
-
const chart = wrapper.getComponent(WaveformChart)
|
|
13
|
-
const controls = wrapper.get('.plot-margin-controls').findAllComponents(InputNumber)
|
|
14
|
-
|
|
15
|
-
expect(controls).toHaveLength(2)
|
|
16
|
-
expect(controls[0]?.props('value')).toBe(18)
|
|
17
|
-
expect(controls[1]?.props('value')).toBe(52)
|
|
18
|
-
expect(chart.props('plotMargin')).toEqual({ top: 18, bottom: 52 })
|
|
19
|
-
|
|
20
|
-
controls[0]?.vm.$emit('update:value', 30)
|
|
21
|
-
controls[1]?.vm.$emit('update:value', 70)
|
|
22
|
-
await flushPromises()
|
|
23
|
-
|
|
24
|
-
expect(chart.props('plotMargin')).toEqual({ top: 30, bottom: 70 })
|
|
25
|
-
expect(wrapper.get('.waveform-chart').attributes('data-plot-margin-top')).toBe('30')
|
|
26
|
-
expect(wrapper.get('.waveform-chart').attributes('data-plot-margin-bottom')).toBe('70')
|
|
27
|
-
wrapper.unmount()
|
|
28
|
-
})
|
|
29
|
-
})
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
-
import { InputNumber, Select } from 'ant-design-vue'
|
|
3
|
-
import { describe, expect, it } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import App from '../App.vue'
|
|
6
|
-
import { WaveformChart } from '../components'
|
|
7
|
-
import { formatDemoTimestamp } from './useDemoXAxisLabelControls'
|
|
8
|
-
|
|
9
|
-
describe('X-axis label demo controls', () => {
|
|
10
|
-
it('formats timestamps in fixed UTC and China-standard-time formats', () => {
|
|
11
|
-
expect(formatDemoTimestamp(0, 'UTC', false)).toBe('1970-01-01 00:00:00')
|
|
12
|
-
expect(formatDemoTimestamp(123, 'UTC', true)).toBe('1970-01-01 00:00:00.123')
|
|
13
|
-
expect(formatDemoTimestamp(0, 'Asia/Shanghai', false)).toBe('1970-01-01 08:00:00')
|
|
14
|
-
expect(formatDemoTimestamp(Number.NaN, 'UTC', false)).toBe('NaN')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('configures numeric and timestamp labels from the sidebar', async () => {
|
|
18
|
-
const wrapper = mount(App)
|
|
19
|
-
await flushPromises()
|
|
20
|
-
const chart = wrapper.getComponent(WaveformChart)
|
|
21
|
-
|
|
22
|
-
expect(chart.props('axes')).toEqual({
|
|
23
|
-
x: { lineVisible: false },
|
|
24
|
-
y: { lineVisible: false },
|
|
25
|
-
})
|
|
26
|
-
expect(wrapper.find('[aria-label="自定义 X 轴 Label"]').exists()).toBe(true)
|
|
27
|
-
expect(wrapper.find('[aria-label="X 轴 Label 运算倍率"]').exists()).toBe(false)
|
|
28
|
-
expect(wrapper.find('[aria-label="X 轴 Label 小数位数"]').exists()).toBe(false)
|
|
29
|
-
expect(wrapper.find('[aria-label="X 轴 Label 格式类型"]').exists()).toBe(false)
|
|
30
|
-
|
|
31
|
-
await wrapper.get('[aria-label="自定义 X 轴 Label"]').trigger('click')
|
|
32
|
-
await flushPromises()
|
|
33
|
-
const inputNumbers = wrapper.findAllComponents(InputNumber)
|
|
34
|
-
const multiplierInput = inputNumbers.find((input) =>
|
|
35
|
-
input.find('[aria-label="X 轴 Label 运算倍率"]').exists(),
|
|
36
|
-
)
|
|
37
|
-
const fractionDigitsInput = inputNumbers.find((input) =>
|
|
38
|
-
input.find('[aria-label="X 轴 Label 小数位数"]').exists(),
|
|
39
|
-
)
|
|
40
|
-
expect(multiplierInput).toBeDefined()
|
|
41
|
-
expect(fractionDigitsInput).toBeDefined()
|
|
42
|
-
expect(multiplierInput?.props('value')).toBe(1)
|
|
43
|
-
expect(fractionDigitsInput?.props('value')).toBe(3)
|
|
44
|
-
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toMatch(/\.000$/)
|
|
45
|
-
|
|
46
|
-
multiplierInput?.vm.$emit('update:value', 2)
|
|
47
|
-
fractionDigitsInput?.vm.$emit('update:value', 2)
|
|
48
|
-
await flushPromises()
|
|
49
|
-
const formatter = chart.props('axes')?.x?.labelFormatter
|
|
50
|
-
expect(formatter).toBeTypeOf('function')
|
|
51
|
-
expect(formatter?.(1.234, {} as never)).toBe('2.47')
|
|
52
|
-
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toMatch(/\.00$/)
|
|
53
|
-
|
|
54
|
-
const formatSelect = wrapper
|
|
55
|
-
.findAllComponents(Select)
|
|
56
|
-
.find((select) => select.find('[aria-label="X 轴 Label 格式类型"]').exists())
|
|
57
|
-
expect(formatSelect?.props('value')).toBe('number')
|
|
58
|
-
formatSelect?.vm.$emit('update:value', 'datetime')
|
|
59
|
-
await flushPromises()
|
|
60
|
-
expect(wrapper.find('[aria-label="X 轴 Label 运算倍率"]').exists()).toBe(false)
|
|
61
|
-
expect(wrapper.find('[aria-label="X 轴 Label 小数位数"]').exists()).toBe(false)
|
|
62
|
-
expect(wrapper.find('[aria-label="X 轴 Label 时区"]').exists()).toBe(true)
|
|
63
|
-
expect(wrapper.find('[aria-label="X 轴 Label 显示毫秒"]').exists()).toBe(true)
|
|
64
|
-
expect(chart.props('axes')?.x?.labelFormatter?.(0, {} as never)).toBe('1970-01-01 08:00:00')
|
|
65
|
-
|
|
66
|
-
await wrapper.get('[aria-label="X 轴 Label 显示毫秒"]').trigger('click')
|
|
67
|
-
await flushPromises()
|
|
68
|
-
expect(chart.props('axes')?.x?.labelFormatter?.(123, {} as never)).toBe(
|
|
69
|
-
'1970-01-01 08:00:00.123',
|
|
70
|
-
)
|
|
71
|
-
const timeZoneSelect = wrapper
|
|
72
|
-
.findAllComponents(Select)
|
|
73
|
-
.find((select) => select.find('[aria-label="X 轴 Label 时区"]').exists())
|
|
74
|
-
timeZoneSelect?.vm.$emit('update:value', 'UTC')
|
|
75
|
-
await flushPromises()
|
|
76
|
-
expect(chart.props('axes')?.x?.labelFormatter?.(123, {} as never)).toBe(
|
|
77
|
-
'1970-01-01 00:00:00.123',
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
await wrapper.get('[aria-label="自定义 X 轴 Label"]').trigger('click')
|
|
81
|
-
await flushPromises()
|
|
82
|
-
expect(chart.props('axes')?.x?.labelFormatter).toBeUndefined()
|
|
83
|
-
expect(wrapper.find('[aria-label="X 轴 Label 运算倍率"]').exists()).toBe(false)
|
|
84
|
-
wrapper.unmount()
|
|
85
|
-
}, 10_000)
|
|
86
|
-
})
|
package/src/test/setup.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { beforeEach, vi } from 'vitest'
|
|
2
|
-
|
|
3
|
-
interface ResizeObserverEntryMock {
|
|
4
|
-
target: Element
|
|
5
|
-
contentRect: DOMRectReadOnly
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
type ResizeCallback = (entries: ResizeObserverEntryMock[]) => void
|
|
9
|
-
|
|
10
|
-
export const resizeObservers: ResizeObserverMock[] = []
|
|
11
|
-
const animationFrameCallbacks = new Map<number, FrameRequestCallback>()
|
|
12
|
-
let animationFrameId = 0
|
|
13
|
-
|
|
14
|
-
export class ResizeObserverMock {
|
|
15
|
-
private target?: Element
|
|
16
|
-
|
|
17
|
-
constructor(private readonly callback: ResizeCallback) {
|
|
18
|
-
resizeObservers.push(this)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
observe = vi.fn((target: Element) => {
|
|
22
|
-
this.target = target
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
unobserve: () => void = vi.fn()
|
|
26
|
-
disconnect: () => void = vi.fn()
|
|
27
|
-
|
|
28
|
-
resize(width: number, height = 400) {
|
|
29
|
-
if (!this.target) return
|
|
30
|
-
this.callback([
|
|
31
|
-
{
|
|
32
|
-
target: this.target,
|
|
33
|
-
contentRect: { width, height } as DOMRectReadOnly,
|
|
34
|
-
},
|
|
35
|
-
])
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
vi.stubGlobal('ResizeObserver', ResizeObserverMock)
|
|
40
|
-
vi.stubGlobal(
|
|
41
|
-
'requestAnimationFrame',
|
|
42
|
-
vi.fn((callback: FrameRequestCallback) => {
|
|
43
|
-
animationFrameId += 1
|
|
44
|
-
animationFrameCallbacks.set(animationFrameId, callback)
|
|
45
|
-
return animationFrameId
|
|
46
|
-
}),
|
|
47
|
-
)
|
|
48
|
-
vi.stubGlobal(
|
|
49
|
-
'cancelAnimationFrame',
|
|
50
|
-
vi.fn((id: number) => {
|
|
51
|
-
animationFrameCallbacks.delete(id)
|
|
52
|
-
}),
|
|
53
|
-
)
|
|
54
|
-
vi.stubGlobal(
|
|
55
|
-
'matchMedia',
|
|
56
|
-
vi.fn((query: string): MediaQueryList => ({
|
|
57
|
-
matches: false,
|
|
58
|
-
media: query,
|
|
59
|
-
onchange: null,
|
|
60
|
-
addListener: vi.fn(),
|
|
61
|
-
removeListener: vi.fn(),
|
|
62
|
-
addEventListener: vi.fn(),
|
|
63
|
-
removeEventListener: vi.fn(),
|
|
64
|
-
dispatchEvent: vi.fn(),
|
|
65
|
-
})),
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
beforeEach(() => {
|
|
69
|
-
resizeObservers.length = 0
|
|
70
|
-
animationFrameCallbacks.clear()
|
|
71
|
-
animationFrameId = 0
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
export function flushAnimationFrames(timestamp = 0) {
|
|
75
|
-
const callbacks = Array.from(animationFrameCallbacks.values())
|
|
76
|
-
animationFrameCallbacks.clear()
|
|
77
|
-
callbacks.forEach((callback) => callback(timestamp))
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function pendingAnimationFrameCount(): number {
|
|
81
|
-
return animationFrameCallbacks.size
|
|
82
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
-
|
|
3
|
-
import WaveformChart from '../components/WaveformChart.vue'
|
|
4
|
-
import type { WaveformData } from '../components/waveform'
|
|
5
|
-
import { resizeObservers } from './setup'
|
|
6
|
-
|
|
7
|
-
export async function mountSizedChart(data: WaveformData, extraProps = {}) {
|
|
8
|
-
const wrapper = mount(WaveformChart, {
|
|
9
|
-
props: { data, ...extraProps },
|
|
10
|
-
})
|
|
11
|
-
resizeObservers.at(-1)?.resize(800, 360)
|
|
12
|
-
await flushPromises()
|
|
13
|
-
return wrapper
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function gridSeries(count: number): WaveformData {
|
|
17
|
-
return {
|
|
18
|
-
kind: 'series',
|
|
19
|
-
series: Array.from({ length: count }, (_, index) => ({
|
|
20
|
-
id: `channel-${index}`,
|
|
21
|
-
name: `通道 ${index + 1}`,
|
|
22
|
-
data: {
|
|
23
|
-
kind: 'points',
|
|
24
|
-
points: [
|
|
25
|
-
{ x: 0, y: index },
|
|
26
|
-
{ x: 1, y: index + 1 },
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
})),
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function visibilitySeries(): WaveformData {
|
|
34
|
-
return {
|
|
35
|
-
kind: 'series',
|
|
36
|
-
series: [
|
|
37
|
-
{
|
|
38
|
-
id: 'low',
|
|
39
|
-
trackId: 'shared-frame',
|
|
40
|
-
name: '低量程',
|
|
41
|
-
data: {
|
|
42
|
-
kind: 'points',
|
|
43
|
-
points: [
|
|
44
|
-
{ x: 0, y: 0 },
|
|
45
|
-
{ x: 1, y: 10 },
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'high',
|
|
51
|
-
trackId: 'shared-frame',
|
|
52
|
-
name: '高量程',
|
|
53
|
-
data: {
|
|
54
|
-
kind: 'points',
|
|
55
|
-
points: [
|
|
56
|
-
{ x: 10, y: 1000 },
|
|
57
|
-
{ x: 20, y: 2000 },
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: 'mid',
|
|
63
|
-
trackId: 'shared-frame',
|
|
64
|
-
name: '中量程',
|
|
65
|
-
data: {
|
|
66
|
-
kind: 'points',
|
|
67
|
-
points: [
|
|
68
|
-
{ x: 0, y: 100 },
|
|
69
|
-
{ x: 1, y: 200 },
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
],
|
|
74
|
-
}
|
|
75
|
-
}
|
package/src/utils/domain.test.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { buildMinorTicks } from './domain'
|
|
4
|
-
|
|
5
|
-
describe('buildMinorTicks', () => {
|
|
6
|
-
it('preserves the existing behavior when no domain is provided', () => {
|
|
7
|
-
expect(buildMinorTicks([0, 10, 20], 5)).toEqual([2, 4, 6, 8, 12, 14, 16, 18])
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
it('fills the partial interval after the final major tick', () => {
|
|
11
|
-
const majorTicks = Array.from({ length: 13 }, (_, index) => -8000 + index * 1000)
|
|
12
|
-
|
|
13
|
-
expect(buildMinorTicks(majorTicks, 5, [-8000, 4990.3]).slice(-4)).toEqual([
|
|
14
|
-
4200, 4400, 4600, 4800,
|
|
15
|
-
])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('fills both edge intervals and excludes the domain endpoints', () => {
|
|
19
|
-
const ticks = buildMinorTicks([-7000, -6000, -5000], 5, [-7950, -4100])
|
|
20
|
-
|
|
21
|
-
expect(ticks).toEqual([
|
|
22
|
-
-7800, -7600, -7400, -7200, -6800, -6600, -6400, -6200, -5800, -5600, -5400, -5200, -4800,
|
|
23
|
-
-4600, -4400, -4200,
|
|
24
|
-
])
|
|
25
|
-
expect(ticks).not.toContain(-7950)
|
|
26
|
-
expect(ticks).not.toContain(-4100)
|
|
27
|
-
})
|
|
28
|
-
})
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelFormatterContext } from '../types'
|
|
4
|
-
import {
|
|
5
|
-
formatAnnotationTime,
|
|
6
|
-
formatAxisTime,
|
|
7
|
-
formatEndpointTime,
|
|
8
|
-
formatPlainNumber,
|
|
9
|
-
formatScientificAxisExponent,
|
|
10
|
-
formatScientificAxisLabel,
|
|
11
|
-
formatTooltipNumber,
|
|
12
|
-
formatTooltipTime,
|
|
13
|
-
formatXAxisLabel,
|
|
14
|
-
resolveScientificAxisExponent,
|
|
15
|
-
shouldUseScientificAxisLabel,
|
|
16
|
-
} from './formatters'
|
|
17
|
-
|
|
18
|
-
describe('waveform number formatters', () => {
|
|
19
|
-
it('uses the reference Y-axis scientific notation boundaries', () => {
|
|
20
|
-
expect(shouldUseScientificAxisLabel(0)).toBe(false)
|
|
21
|
-
expect(shouldUseScientificAxisLabel(0.000999)).toBe(true)
|
|
22
|
-
expect(shouldUseScientificAxisLabel(0.001)).toBe(false)
|
|
23
|
-
expect(shouldUseScientificAxisLabel(999.999)).toBe(false)
|
|
24
|
-
expect(shouldUseScientificAxisLabel(1000)).toBe(true)
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('prefixes one shared exponent to the largest visible tick', () => {
|
|
28
|
-
const positiveAxis = { axisMin: 1000, axisMax: 3000 }
|
|
29
|
-
expect(formatScientificAxisLabel(1000, positiveAxis)).toBe('1')
|
|
30
|
-
expect(formatScientificAxisLabel(2000, positiveAxis)).toBe('2')
|
|
31
|
-
expect(formatScientificAxisLabel(3000, positiveAxis)).toBe('E+03 3')
|
|
32
|
-
expect(formatScientificAxisLabel(2000, { ...positiveAxis, topTickValue: 2000 })).toBe('E+03 2')
|
|
33
|
-
expect(formatScientificAxisExponent(1000, 3000)).toBe('E+03')
|
|
34
|
-
expect(formatScientificAxisExponent(0, 1e120)).toBe('E+120')
|
|
35
|
-
|
|
36
|
-
const tinyAxis = { axisMin: 0.0001, axisMax: 0.0003 }
|
|
37
|
-
expect(formatScientificAxisLabel(0.0001, tinyAxis)).toBe('1')
|
|
38
|
-
expect(formatScientificAxisLabel(0.0002, tinyAxis)).toBe('2')
|
|
39
|
-
expect(formatScientificAxisLabel(0.0003, tinyAxis)).toBe('E-04 3')
|
|
40
|
-
expect(formatScientificAxisExponent(0.0001, 0.0003)).toBe('E-04')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('appends a unit to the top tick with or without scientific notation', () => {
|
|
44
|
-
expect(formatScientificAxisLabel(3000, { axisMin: 1000, axisMax: 3000, unit: 'V' })).toBe(
|
|
45
|
-
'E+03 (V) 3',
|
|
46
|
-
)
|
|
47
|
-
expect(formatScientificAxisLabel(3, { axisMin: 0, axisMax: 3, unit: 'A' })).toBe('(A) 3')
|
|
48
|
-
expect(formatScientificAxisLabel(2, { axisMin: 0, axisMax: 3, unit: 'A' })).toBe('2')
|
|
49
|
-
expect(formatScientificAxisLabel(3, { axisMin: 0, axisMax: 3, unit: ' ' })).toBe('3')
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('derives the exponent from the largest absolute endpoint', () => {
|
|
53
|
-
expect(resolveScientificAxisExponent(-9000, -1000)).toBe(3)
|
|
54
|
-
expect(resolveScientificAxisExponent(-100_000, -3000)).toBe(5)
|
|
55
|
-
expect(resolveScientificAxisExponent(-10_000, 3000)).toBe(4)
|
|
56
|
-
expect(resolveScientificAxisExponent(-1000, 0)).toBe(3)
|
|
57
|
-
expect(resolveScientificAxisExponent(0, 0)).toBeNull()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('keeps five significant digits, removes trailing zeroes, and limits decimals to four', () => {
|
|
61
|
-
expect(formatScientificAxisLabel(123.456, { axisMin: 0, axisMax: 999 })).toBe('123.46')
|
|
62
|
-
expect(formatScientificAxisLabel(1234.56, { axisMin: 0, axisMax: 9999 })).toBe('1.2346')
|
|
63
|
-
expect(formatScientificAxisLabel(3000, { axisMin: 1000, axisMax: 3000 })).toBe('E+03 3')
|
|
64
|
-
expect(formatScientificAxisLabel(-0, { axisMin: -1, axisMax: 1 })).toBe('0')
|
|
65
|
-
expect(formatScientificAxisLabel(Number.NaN)).toBe('NaN')
|
|
66
|
-
expect(formatScientificAxisLabel(Number.POSITIVE_INFINITY)).toBe('Infinity')
|
|
67
|
-
expect(formatScientificAxisExponent(0, 0)).toBeNull()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('formats X-axis ticks and endpoints as complete plain values in the display unit', () => {
|
|
71
|
-
const domain: [number, number] = [0, 1]
|
|
72
|
-
expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500.4')
|
|
73
|
-
expect(formatEndpointTime(1, domain, 'ms')).toBe('1000')
|
|
74
|
-
expect(formatAxisTime(0.5, 's', domain)).toBe('0.5')
|
|
75
|
-
expect(formatEndpointTime(1, domain, 's')).toBe('1')
|
|
76
|
-
|
|
77
|
-
const tinyDomain: [number, number] = [0, 0.000001]
|
|
78
|
-
expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0.000001')
|
|
79
|
-
expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('-0.000001')
|
|
80
|
-
expect(formatAxisTime(-0, 's')).toBe('0')
|
|
81
|
-
expect(formatAxisTime(1e21, 's')).toBe('1000000000000000000000')
|
|
82
|
-
expect(formatAxisTime(Number.POSITIVE_INFINITY, 's')).toBe('Infinity')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('passes display values and complete source context to X-axis label formatters', () => {
|
|
86
|
-
const domain: [number, number] = [0.125, 1.875]
|
|
87
|
-
const formatter: WaveformXAxisLabelFormatter = (value, context) =>
|
|
88
|
-
`${context.kind}:${value / 10}`
|
|
89
|
-
|
|
90
|
-
expect(formatXAxisLabel(0.5, domain, 'ms', 'tick', formatter)).toBe('tick:50')
|
|
91
|
-
expect(formatXAxisLabel(domain[0], domain, 'ms', 'start', formatter)).toBe('start:12.5')
|
|
92
|
-
expect(formatXAxisLabel(domain[1], domain, 'ms', 'end', formatter)).toBe('end:187.5')
|
|
93
|
-
|
|
94
|
-
let receivedContext: WaveformXAxisLabelFormatterContext | undefined
|
|
95
|
-
formatXAxisLabel(0.5, domain, 'ms', 'tick', (value, context) => {
|
|
96
|
-
receivedContext = context
|
|
97
|
-
return String(value)
|
|
98
|
-
})
|
|
99
|
-
expect(receivedContext).toEqual({
|
|
100
|
-
kind: 'tick',
|
|
101
|
-
rawValue: 0.5,
|
|
102
|
-
timeUnit: 'ms',
|
|
103
|
-
domain: [0.125, 1.875],
|
|
104
|
-
displayDomain: [125, 1875],
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
expect(() =>
|
|
108
|
-
formatXAxisLabel(0.5, domain, 's', 'tick', () => {
|
|
109
|
-
throw new Error('formatter failed')
|
|
110
|
-
}),
|
|
111
|
-
).toThrow('formatter failed')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('formats tooltip and raw values for their display contexts', () => {
|
|
115
|
-
expect(formatTooltipNumber(12345.67891)).toBe('12,345.6789')
|
|
116
|
-
expect(formatTooltipNumber(-0)).toBe('0')
|
|
117
|
-
expect(formatTooltipNumber(Number.POSITIVE_INFINITY)).toBe('Infinity')
|
|
118
|
-
expect(formatTooltipTime(1.234567, 's')).toBe('1.2346')
|
|
119
|
-
expect(formatTooltipTime(1, 'ms')).toBe('1,000')
|
|
120
|
-
expect(formatPlainNumber(0.0000001)).toBe('0.0000001')
|
|
121
|
-
expect(formatPlainNumber(1e21)).toBe('1000000000000000000000')
|
|
122
|
-
expect(formatPlainNumber(-0)).toBe('0')
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
it('formats annotation time in the selected unit without changing source seconds', () => {
|
|
126
|
-
expect(formatAnnotationTime(1, 'ms')).toBe('1000.000')
|
|
127
|
-
expect(formatAnnotationTime(1, 's')).toBe('1.000')
|
|
128
|
-
expect(formatAnnotationTime(-0, 'ms')).toBe('0.000')
|
|
129
|
-
expect(formatAnnotationTime(Number.NaN, 's')).toBe('NaN')
|
|
130
|
-
})
|
|
131
|
-
})
|