waveform-analysis 0.1.63 → 0.1.65

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 (97) hide show
  1. package/README.md +21 -5
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +986 -964
  4. package/dist/style.css +1 -1
  5. package/dist/types/components/core/constants.d.ts +1 -1
  6. package/dist/types/components/core/trackLayoutBuilder.d.ts +1 -0
  7. package/dist/types/components/core/yAxisConstants.d.ts +1 -1
  8. package/dist/types/types/chart.d.ts +6 -1
  9. package/package.json +1 -1
  10. package/src/App.vue +2 -2
  11. package/src/components/WaveformChart.css +4 -0
  12. package/src/components/WaveformChart.vue +2 -2
  13. package/src/components/core/constants.ts +3 -3
  14. package/src/components/core/layout.ts +1 -1
  15. package/src/components/core/trackLayoutBuilder.ts +42 -1
  16. package/src/components/core/useWaveformLayout.ts +2 -1
  17. package/src/components/core/useWaveformPresentation.ts +1 -1
  18. package/src/components/core/xDomain.ts +26 -2
  19. package/src/components/core/yAxisConstants.ts +1 -1
  20. package/src/components/rendering/WaveformLegend.vue +1 -1
  21. package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
  22. package/src/components/rendering/WaveformTrack.css +5 -2
  23. package/src/components/rendering/WaveformTrack.vue +7 -1
  24. package/src/components/rendering/WaveformTrackAxes.vue +8 -2
  25. package/src/demo/DemoChartHost.vue +1 -0
  26. package/src/types/chart.ts +6 -1
  27. package/src/App.test.ts +0 -400
  28. package/src/components/annotation/components.test.ts +0 -372
  29. package/src/components/annotation/markup.test.ts +0 -371
  30. package/src/components/annotation/serialization.test.ts +0 -114
  31. package/src/components/core/constants.test.ts +0 -29
  32. package/src/components/core/fixedYDomainLayout.test.ts +0 -204
  33. package/src/components/core/grid.test.ts +0 -197
  34. package/src/components/core/latestTaskScheduler.test.ts +0 -51
  35. package/src/components/core/layout.test.ts +0 -373
  36. package/src/components/core/title.test.ts +0 -76
  37. package/src/components/core/useWaveformData.test.ts +0 -95
  38. package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
  39. package/src/components/core/xDomain.test.ts +0 -28
  40. package/src/components/core/yAxisNice.test.ts +0 -174
  41. package/src/components/core/yAxisSlotLayout.test.ts +0 -165
  42. package/src/components/core/yDomain.test.ts +0 -56
  43. package/src/components/interaction/WaveformTooltip.test.ts +0 -276
  44. package/src/components/interaction/viewportInteractionState.test.ts +0 -101
  45. package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
  46. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
  47. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
  48. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
  49. package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
  50. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
  51. package/src/components/waveformChartCases/gridFill.test.ts +0 -95
  52. package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
  53. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
  54. package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
  55. package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
  56. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
  57. package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
  58. package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
  59. package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
  60. package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
  61. package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
  62. package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
  63. package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
  64. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
  65. package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
  66. package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
  67. package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
  68. package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
  69. package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
  70. package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
  71. package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
  72. package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
  73. package/src/components/waveformLegendGeometry.test.ts +0 -21
  74. package/src/components/waveformNormalization.test.ts +0 -170
  75. package/src/core/data.test.ts +0 -241
  76. package/src/core/rendering.test.ts +0 -267
  77. package/src/core/renderingStrategies.test.ts +0 -90
  78. package/src/core/samplingReference.test.ts +0 -124
  79. package/src/core/wasmSampling.test.ts +0 -205
  80. package/src/core/workerSampling/multiresolution.test.ts +0 -69
  81. package/src/core/workerSampling/protocol.test.ts +0 -34
  82. package/src/core/workerSampling/repository.test.ts +0 -400
  83. package/src/core/workerSampling/repositoryCache.test.ts +0 -163
  84. package/src/core/workerSampling/repositorySampling.test.ts +0 -106
  85. package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
  86. package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
  87. package/src/data/simulatedWaveforms.test.ts +0 -60
  88. package/src/demo/plotMarginControls.test.ts +0 -29
  89. package/src/demo/xAxisLabelControls.test.ts +0 -86
  90. package/src/test/setup.ts +0 -82
  91. package/src/test/waveformChart.ts +0 -75
  92. package/src/utils/domain.test.ts +0 -28
  93. package/src/utils/formatters.test.ts +0 -131
  94. package/src/utils/sampling.test.ts +0 -226
  95. package/src/views/FixedYDomainDemo.test.ts +0 -44
  96. package/src/views/WasmSamplingDemo.test.ts +0 -13
  97. 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
- })