waveform-analysis 0.1.63 → 0.1.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +9 -2
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +953 -946
  4. package/dist/style.css +1 -1
  5. package/dist/types/types/chart.d.ts +2 -1
  6. package/package.json +1 -1
  7. package/src/components/WaveformChart.vue +1 -1
  8. package/src/components/core/xDomain.ts +26 -2
  9. package/src/components/rendering/WaveformLegend.vue +1 -1
  10. package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
  11. package/src/demo/DemoChartHost.vue +1 -0
  12. package/src/types/chart.ts +2 -1
  13. package/src/App.test.ts +0 -400
  14. package/src/components/annotation/components.test.ts +0 -372
  15. package/src/components/annotation/markup.test.ts +0 -371
  16. package/src/components/annotation/serialization.test.ts +0 -114
  17. package/src/components/core/constants.test.ts +0 -29
  18. package/src/components/core/fixedYDomainLayout.test.ts +0 -204
  19. package/src/components/core/grid.test.ts +0 -197
  20. package/src/components/core/latestTaskScheduler.test.ts +0 -51
  21. package/src/components/core/layout.test.ts +0 -373
  22. package/src/components/core/title.test.ts +0 -76
  23. package/src/components/core/useWaveformData.test.ts +0 -95
  24. package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
  25. package/src/components/core/xDomain.test.ts +0 -28
  26. package/src/components/core/yAxisNice.test.ts +0 -174
  27. package/src/components/core/yAxisSlotLayout.test.ts +0 -165
  28. package/src/components/core/yDomain.test.ts +0 -56
  29. package/src/components/interaction/WaveformTooltip.test.ts +0 -276
  30. package/src/components/interaction/viewportInteractionState.test.ts +0 -101
  31. package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
  32. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
  33. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
  34. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
  35. package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
  36. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
  37. package/src/components/waveformChartCases/gridFill.test.ts +0 -95
  38. package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
  39. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
  40. package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
  41. package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
  42. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
  43. package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
  44. package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
  45. package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
  46. package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
  47. package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
  48. package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
  49. package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
  50. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
  51. package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
  52. package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
  53. package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
  54. package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
  55. package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
  56. package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
  57. package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
  58. package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
  59. package/src/components/waveformLegendGeometry.test.ts +0 -21
  60. package/src/components/waveformNormalization.test.ts +0 -170
  61. package/src/core/data.test.ts +0 -241
  62. package/src/core/rendering.test.ts +0 -267
  63. package/src/core/renderingStrategies.test.ts +0 -90
  64. package/src/core/samplingReference.test.ts +0 -124
  65. package/src/core/wasmSampling.test.ts +0 -205
  66. package/src/core/workerSampling/multiresolution.test.ts +0 -69
  67. package/src/core/workerSampling/protocol.test.ts +0 -34
  68. package/src/core/workerSampling/repository.test.ts +0 -400
  69. package/src/core/workerSampling/repositoryCache.test.ts +0 -163
  70. package/src/core/workerSampling/repositorySampling.test.ts +0 -106
  71. package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
  72. package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
  73. package/src/data/simulatedWaveforms.test.ts +0 -60
  74. package/src/demo/plotMarginControls.test.ts +0 -29
  75. package/src/demo/xAxisLabelControls.test.ts +0 -86
  76. package/src/test/setup.ts +0 -82
  77. package/src/test/waveformChart.ts +0 -75
  78. package/src/utils/domain.test.ts +0 -28
  79. package/src/utils/formatters.test.ts +0 -131
  80. package/src/utils/sampling.test.ts +0 -226
  81. package/src/views/FixedYDomainDemo.test.ts +0 -44
  82. package/src/views/WasmSamplingDemo.test.ts +0 -13
  83. package/src/views/wasmSamplingDemoSampling.test.ts +0 -27
@@ -1,204 +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 { buildTrackLayouts, resolveYAxisSeriesGroups } from './layout'
7
-
8
- function series(id: string, minimum: number, maximum: number): DisplaySeries {
9
- return {
10
- id,
11
- name: id,
12
- color: '#1677ff',
13
- lineType: 'linear',
14
- lineStyle: 'solid',
15
- pointType: 'none',
16
- errorBar: { visible: false, width: 1.5, capWidth: 8 },
17
- points: [
18
- { x: 0, y: minimum },
19
- { x: 1, y: maximum },
20
- ],
21
- xDomain: [0, 1],
22
- yDomain: [minimum, maximum],
23
- hasErrorPoints: false,
24
- }
25
- }
26
-
27
- function track(seriesList: DisplaySeries[]): DisplayTrack {
28
- return {
29
- id: 'track',
30
- series: seriesList,
31
- visibleSeries: seriesList,
32
- xDomain: [0, 1],
33
- yDomain: [0, 100],
34
- }
35
- }
36
-
37
- describe('fixed Y-domain layout', () => {
38
- it('expands a global fixed domain to nice equal intervals', () => {
39
- const sourceTrack = track([series('a', 0, 100)])
40
- const result = buildTrackLayouts({
41
- cells: [
42
- {
43
- slotIndex: 0,
44
- row: 0,
45
- column: 0,
46
- left: 0,
47
- top: 0,
48
- width: 120,
49
- height: 100,
50
- plotHeight: 100,
51
- cellHeight: 130,
52
- xAxisBand: 30,
53
- series: sourceTrack,
54
- },
55
- ],
56
- grid: {
57
- rowCount: 1,
58
- columnCount: 1,
59
- showPagination: false,
60
- fillIncompleteLastRow: false,
61
- trackLines: {},
62
- },
63
- displayMode: 'independent',
64
- overlayMode: 'single-axis',
65
- independentTransforms: [zoomIdentity],
66
- sharedZoomDomain: [0, 1],
67
- fixedYDomain: [3, 97],
68
- timeUnit: 'ms',
69
- rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
70
- hideSecondaryLabels: false,
71
- yAxisLabelX: -50,
72
- showCompactEmptyTracks: false,
73
- })[0]
74
-
75
- expect(result?.yScale.domain()).toEqual([0, 100])
76
- expect(result?.yAxes[0]?.tickValues).toContain(0)
77
- expect(result?.yAxes[0]?.tickValues).toContain(100)
78
- expect(result?.seriesPaths[0]?.yScale.domain()).toEqual([0, 100])
79
- })
80
-
81
- it('uses the configured split number for fixed domains', () => {
82
- const result = buildTrackLayouts({
83
- cells: [
84
- {
85
- slotIndex: 0,
86
- row: 0,
87
- column: 0,
88
- left: 0,
89
- top: 0,
90
- width: 120,
91
- height: 100,
92
- plotHeight: 100,
93
- cellHeight: 130,
94
- xAxisBand: 30,
95
- series: track([series('a', 0, 100)]),
96
- },
97
- ],
98
- grid: {
99
- rowCount: 1,
100
- columnCount: 1,
101
- showPagination: false,
102
- fillIncompleteLastRow: false,
103
- trackLines: {},
104
- },
105
- displayMode: 'independent',
106
- overlayMode: 'single-axis',
107
- independentTransforms: [zoomIdentity],
108
- sharedZoomDomain: [0, 1],
109
- fixedYDomain: [3, 97],
110
- yAxisSplitNumber: 5,
111
- timeUnit: 'ms',
112
- rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
113
- hideSecondaryLabels: false,
114
- yAxisLabelX: -50,
115
- showCompactEmptyTracks: false,
116
- })[0]
117
-
118
- expect(result?.yAxes[0]?.majorTicks).toEqual([0, 25, 50, 75, 100])
119
- })
120
-
121
- it('defaults to five ticks and supports a two-tick axis', () => {
122
- const build = (splitNumber?: number) =>
123
- buildTrackLayouts({
124
- cells: [
125
- {
126
- slotIndex: 0,
127
- row: 0,
128
- column: 0,
129
- left: 0,
130
- top: 0,
131
- width: 120,
132
- height: 100,
133
- plotHeight: 100,
134
- cellHeight: 130,
135
- xAxisBand: 30,
136
- series: track([series('a', 3, 97)]),
137
- },
138
- ],
139
- grid: {
140
- rowCount: 1,
141
- columnCount: 1,
142
- showPagination: false,
143
- fillIncompleteLastRow: false,
144
- trackLines: {},
145
- },
146
- displayMode: 'independent',
147
- overlayMode: 'single-axis',
148
- independentTransforms: [zoomIdentity],
149
- sharedZoomDomain: [0, 1],
150
- fixedYDomain: [3, 97],
151
- yAxisSplitNumber: splitNumber,
152
- timeUnit: 'ms',
153
- rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
154
- hideSecondaryLabels: false,
155
- yAxisLabelX: -50,
156
- showCompactEmptyTracks: false,
157
- })[0]?.yAxes[0]?.majorTicks
158
-
159
- expect(build()).toHaveLength(5)
160
- expect(build(2)).toEqual([0, 100])
161
- })
162
-
163
- it('resolves track, series, and global fixed-domain precedence', () => {
164
- const sourceTrack = track([series('a', 0, 10), series('b', 20, 30)])
165
-
166
- expect(
167
- resolveYAxisSeriesGroups(sourceTrack, 'single-axis', [-5, 5], {
168
- a: [-10, 10],
169
- track: [300, 100],
170
- })[0]?.domain,
171
- ).toEqual([100, 300])
172
-
173
- expect(
174
- resolveYAxisSeriesGroups(sourceTrack, 'single-axis', [-5, 5], {
175
- a: [-10, 10],
176
- })[0]?.domain,
177
- ).toEqual([-10, 10])
178
- })
179
-
180
- it('keeps per-series fixed domains on separate axes and merges axis overflow', () => {
181
- const sourceTrack = track([
182
- series('a', 0, 1),
183
- series('b', 10, 11),
184
- series('c', 20, 21),
185
- series('d', 30, 31),
186
- series('e', 40, 41),
187
- ])
188
- const groups = resolveYAxisSeriesGroups(sourceTrack, 'multi-axis', undefined, {
189
- a: [-1, 1],
190
- b: [-2, 2],
191
- c: [-3, 3],
192
- d: [-4, 4],
193
- e: [-5, 5],
194
- })
195
-
196
- expect(groups.map((group) => group.domain)).toEqual([
197
- [-1, 1],
198
- [-2, 2],
199
- [-3, 3],
200
- [-5, 5],
201
- ])
202
- expect(groups.every((group) => group.fixed)).toBe(true)
203
- })
204
- })
@@ -1,197 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import {
4
- getBottomRowCellIndexes,
5
- getPageCount,
6
- normalizeGridOptions,
7
- paginateSeries,
8
- resolveGridCellGeometry,
9
- X_AXIS_BAND,
10
- } from './grid'
11
-
12
- describe('waveform grid helpers', () => {
13
- it('normalizes grid counts and uses a two by one default', () => {
14
- expect(normalizeGridOptions()).toEqual({
15
- rowCount: 2,
16
- columnCount: 1,
17
- showPagination: true,
18
- fillIncompleteLastRow: false,
19
- trackOrder: [],
20
- hideEmptyTracks: false,
21
- trackLines: {},
22
- })
23
- expect(normalizeGridOptions({ rowCount: 0, columnCount: 99 })).toEqual({
24
- rowCount: 1,
25
- columnCount: 10,
26
- showPagination: true,
27
- fillIncompleteLastRow: false,
28
- trackOrder: [],
29
- hideEmptyTracks: false,
30
- trackLines: {},
31
- })
32
- expect(normalizeGridOptions({ hideEmptyTracks: true }).hideEmptyTracks).toBe(true)
33
- })
34
-
35
- it('normalizes track order by trimming, dropping blanks, and removing duplicates', () => {
36
- expect(
37
- normalizeGridOptions({ trackOrder: [' voltage ', '', 'current', 'voltage', ' '] }),
38
- ).toMatchObject({ trackOrder: ['voltage', 'current'] })
39
- })
40
-
41
- it('normalizes per-track grid line visibility with visible defaults', () => {
42
- expect(
43
- normalizeGridOptions({
44
- trackLines: {
45
- voltage: { horizontal: false },
46
- current: { vertical: false },
47
- },
48
- }).trackLines,
49
- ).toEqual({
50
- voltage: { horizontal: false, vertical: true },
51
- current: { horizontal: true, vertical: false },
52
- })
53
- })
54
-
55
- it('preserves optional per-direction grid colors and ignores blank values', () => {
56
- expect(
57
- normalizeGridOptions({
58
- trackLines: {
59
- voltage: {
60
- horizontalColor: '#ef4444',
61
- verticalColor: ' #2563eb ',
62
- },
63
- current: { horizontalColor: ' ' },
64
- },
65
- }).trackLines,
66
- ).toEqual({
67
- voltage: {
68
- horizontal: true,
69
- vertical: true,
70
- horizontalColor: '#ef4444',
71
- verticalColor: ' #2563eb ',
72
- },
73
- current: {
74
- horizontal: true,
75
- vertical: true,
76
- },
77
- })
78
- })
79
-
80
- it('falls back to visible grid lines for invalid runtime values', () => {
81
- const options = {
82
- trackLines: {
83
- voltage: { horizontal: 'invalid', vertical: null },
84
- },
85
- } as unknown as Parameters<typeof normalizeGridOptions>[0]
86
-
87
- expect(normalizeGridOptions(options).trackLines.voltage).toEqual({
88
- horizontal: true,
89
- vertical: true,
90
- })
91
- })
92
-
93
- it('paginates row-major slots and keeps at least one page for empty data', () => {
94
- const options = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
95
- expect(getPageCount([1, 2, 3, 4, 5].length, options)).toBe(2)
96
- expect(paginateSeries([1, 2, 3, 4, 5], 2, options)).toEqual([5])
97
- expect(getPageCount(0, options)).toBe(1)
98
- })
99
-
100
- it('resolves mode-specific gaps and bottom cells', () => {
101
- const options = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
102
- const separated = resolveGridCellGeometry(400, 200, options, 'separated', [
103
- true,
104
- true,
105
- true,
106
- true,
107
- ])
108
- const compact = resolveGridCellGeometry(400, 200, options, 'compact', [true, true, true, true])
109
- const independent = resolveGridCellGeometry(400, 200, options, 'independent', [
110
- true,
111
- true,
112
- false,
113
- false,
114
- ])
115
- expect(separated[1].left).toBeGreaterThan(separated[0].left + separated[0].width)
116
- expect(separated[2].top).toBe(separated[0].plotHeight + 16)
117
- expect(separated[2].xAxisBand).toBe(X_AXIS_BAND)
118
- expect(compact[2].top).toBe(compact[0].top + compact[0].plotHeight)
119
- expect(compact[2].xAxisBand).toBe(X_AXIS_BAND)
120
- expect(independent[2].top).toBe(
121
- independent[0].top + independent[0].plotHeight + X_AXIS_BAND + 14,
122
- )
123
- expect(independent[0].cellHeight).toBe(independent[0].plotHeight + X_AXIS_BAND)
124
- expect(
125
- getBottomRowCellIndexes(
126
- separated.map((cell, index) => ({ ...cell, hasSeries: index !== 3 })),
127
- 2,
128
- ),
129
- ).toEqual(new Set([2, 1]))
130
- })
131
-
132
- it('accepts an independent horizontal gap without changing vertical spacing', () => {
133
- const options = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
134
- const cells = resolveGridCellGeometry(
135
- 400,
136
- 200,
137
- options,
138
- 'independent',
139
- [true, true, true, true],
140
- 64,
141
- )
142
-
143
- expect(cells[0].width).toBe(168)
144
- expect(cells[1].left).toBe(232)
145
- expect(cells[2].top).toBe(cells[0].plotHeight + X_AXIS_BAND + 14)
146
- })
147
-
148
- it('fills incomplete final rows without changing page capacity', () => {
149
- const oneColumn = resolveGridCellGeometry(
150
- 400,
151
- 400,
152
- normalizeGridOptions({ rowCount: 4, columnCount: 1, fillIncompleteLastRow: true }),
153
- 'separated',
154
- [true, true, true],
155
- )
156
- expect(oneColumn).toHaveLength(3)
157
- expect(oneColumn.map((cell) => cell.width)).toEqual([400, 400, 400])
158
- expect(oneColumn.at(-1)?.isLastRow).toBe(true)
159
-
160
- const twoColumns = resolveGridCellGeometry(
161
- 400,
162
- 300,
163
- normalizeGridOptions({ rowCount: 2, columnCount: 2, fillIncompleteLastRow: true }),
164
- 'separated',
165
- [true, true, true],
166
- )
167
- expect(twoColumns).toHaveLength(3)
168
- expect(twoColumns[2]).toMatchObject({ row: 1, column: 0, left: 0, width: 400 })
169
-
170
- const threeColumns = resolveGridCellGeometry(
171
- 400,
172
- 300,
173
- normalizeGridOptions({ rowCount: 2, columnCount: 3, fillIncompleteLastRow: true }),
174
- 'separated',
175
- [true, true, true, true, true],
176
- )
177
- expect(threeColumns).toHaveLength(5)
178
- expect(threeColumns.slice(3).map((cell) => cell.width)).toEqual([192, 192])
179
- expect(threeColumns.slice(3).map((cell) => cell.left)).toEqual([0, 208])
180
- })
181
-
182
- it('keeps full pages and the disabled option byte-for-byte compatible', () => {
183
- const base = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
184
- const filled = normalizeGridOptions({
185
- rowCount: 2,
186
- columnCount: 2,
187
- fillIncompleteLastRow: true,
188
- })
189
- const fullSlots = [true, true, true, true]
190
- expect(resolveGridCellGeometry(400, 300, filled, 'independent', fullSlots)).toEqual(
191
- resolveGridCellGeometry(400, 300, base, 'independent', fullSlots),
192
- )
193
- expect(resolveGridCellGeometry(400, 300, base, 'independent', [true, true, true])).toHaveLength(
194
- 4,
195
- )
196
- })
197
- })
@@ -1,51 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest'
2
-
3
- import { createLatestTaskScheduler } from './latestTaskScheduler'
4
-
5
- function deferred() {
6
- let resolve!: () => void
7
- const promise = new Promise<void>((done) => {
8
- resolve = done
9
- })
10
- return { promise, resolve }
11
- }
12
-
13
- describe('createLatestTaskScheduler', () => {
14
- it('runs one task at a time and coalesces pending work to the latest task', async () => {
15
- const scheduler = createLatestTaskScheduler()
16
- const first = deferred()
17
- const calls: number[] = []
18
-
19
- scheduler.schedule(async (generation) => {
20
- calls.push(generation)
21
- await first.promise
22
- })
23
- scheduler.schedule(async (generation) => {
24
- calls.push(generation)
25
- })
26
- const latest = scheduler.schedule(async (generation) => {
27
- calls.push(generation)
28
- })
29
-
30
- expect(calls).toEqual([1])
31
- expect(scheduler.isCurrent(1)).toBe(false)
32
- expect(scheduler.isCurrent(latest)).toBe(true)
33
- first.resolve()
34
- await vi.waitFor(() => expect(calls).toEqual([1, 3]))
35
- expect(scheduler.metrics).toEqual({ scheduled: 3, coalesced: 1, maxPending: 1 })
36
- })
37
-
38
- it('invalidates running work and discards pending work on cancellation or disposal', async () => {
39
- const scheduler = createLatestTaskScheduler()
40
- const first = deferred()
41
- const firstGeneration = scheduler.schedule(async () => first.promise)
42
- scheduler.schedule(async () => undefined)
43
-
44
- scheduler.cancelPending()
45
- expect(scheduler.isCurrent(firstGeneration)).toBe(false)
46
- first.resolve()
47
- await first.promise
48
- scheduler.dispose()
49
- expect(scheduler.schedule(async () => undefined)).toBe(0)
50
- })
51
- })