waveform-analysis 0.1.48 → 0.1.51

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 (117) hide show
  1. package/README.md +120 -5
  2. package/dist/assets/workerRuntime-Dt94jbqq.js +1 -0
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.js +2683 -1153
  5. package/dist/style.css +1 -1
  6. package/dist/types/components/WaveformChart.vue.d.ts +14 -0
  7. package/dist/types/components/annotation/WaveformAnnotationEditor.vue.d.ts +0 -1
  8. package/dist/types/components/annotation/markup.d.ts +5 -6
  9. package/dist/types/components/annotation/types.d.ts +2 -0
  10. package/dist/types/components/core/latestTaskScheduler.d.ts +19 -0
  11. package/dist/types/components/core/trackLayoutBuilder.d.ts +3 -1
  12. package/dist/types/components/core/types.d.ts +2 -0
  13. package/dist/types/components/core/useWaveformChartController.d.ts +1 -0
  14. package/dist/types/components/core/useWaveformData.d.ts +2 -0
  15. package/dist/types/components/core/useWaveformLayout.d.ts +2 -0
  16. package/dist/types/components/core/useWaveformRenderSampling.d.ts +19 -0
  17. package/dist/types/components/core/waveformChartTypes.d.ts +8 -1
  18. package/dist/types/components/core/workerSamplingClient.d.ts +8 -0
  19. package/dist/types/components/data/types.d.ts +1 -1
  20. package/dist/types/components/interaction/zoomConstraints.d.ts +2 -0
  21. package/dist/types/components/waveform.d.ts +1 -1
  22. package/dist/types/core/compactSamplePointSource.d.ts +3 -0
  23. package/dist/types/core/data.d.ts +7 -1
  24. package/dist/types/core/index.d.ts +2 -1
  25. package/dist/types/core/rendering.d.ts +3 -1
  26. package/dist/types/core/renderingOptions.d.ts +11 -1
  27. package/dist/types/core/samplingReference.d.ts +36 -0
  28. package/dist/types/core/samplingReferenceValues.d.ts +4 -0
  29. package/dist/types/core/samplingReferenceVectors.d.ts +17 -0
  30. package/dist/types/core/wasmSampling.d.ts +48 -0
  31. package/dist/types/core/waveformPointSource.d.ts +53 -0
  32. package/dist/types/core/workerSampling/index.d.ts +5 -0
  33. package/dist/types/core/workerSampling/javascriptBackend.d.ts +3 -0
  34. package/dist/types/core/workerSampling/multiresolution.d.ts +29 -0
  35. package/dist/types/core/workerSampling/protocol.d.ts +153 -0
  36. package/dist/types/core/workerSampling/repository.d.ts +36 -0
  37. package/dist/types/core/workerSampling/repositoryDataset.d.ts +29 -0
  38. package/dist/types/core/workerSampling/repositorySupport.d.ts +27 -0
  39. package/dist/types/core/workerSampling/samplingCache.d.ts +24 -0
  40. package/dist/types/core/workerSampling/workerRuntime.d.ts +1 -0
  41. package/dist/types/index.d.ts +2 -2
  42. package/dist/types/types/chart.d.ts +56 -0
  43. package/dist/types/types/data.d.ts +20 -2
  44. package/dist/types/types/index.d.ts +2 -2
  45. package/dist/types/views/WasmSamplingDemo.vue.d.ts +3 -0
  46. package/package.json +5 -4
  47. package/src/App.test.ts +11 -7
  48. package/src/App.vue +2 -2
  49. package/src/DemoRouterApp.vue +1 -0
  50. package/src/components/WaveformChart.css +2 -0
  51. package/src/components/annotation/WaveformAnnotationEditor.css +2 -2
  52. package/src/components/annotation/WaveformAnnotationEditor.vue +8 -4
  53. package/src/components/annotation/markup.ts +45 -26
  54. package/src/components/annotation/types.ts +2 -0
  55. package/src/components/annotation/useWaveformChartAnnotations.ts +12 -15
  56. package/src/components/core/latestTaskScheduler.test.ts +51 -0
  57. package/src/components/core/latestTaskScheduler.ts +75 -0
  58. package/src/components/core/trackLayoutBuilder.ts +3 -0
  59. package/src/components/core/types.ts +2 -0
  60. package/src/components/core/useWaveformChartController.ts +14 -0
  61. package/src/components/core/useWaveformData.test.ts +95 -0
  62. package/src/components/core/useWaveformData.ts +23 -32
  63. package/src/components/core/useWaveformLayout.ts +4 -4
  64. package/src/components/core/useWaveformRenderSampling.test.ts +17 -0
  65. package/src/components/core/useWaveformRenderSampling.ts +394 -0
  66. package/src/components/core/waveformChartTypes.ts +13 -0
  67. package/src/components/core/workerSamplingClient.ts +148 -0
  68. package/src/components/data/types.ts +9 -0
  69. package/src/components/interaction/WaveformTooltip.test.ts +1 -3
  70. package/src/components/interaction/WaveformTooltip.vue +13 -12
  71. package/src/components/interaction/useWaveformHover.ts +7 -7
  72. package/src/components/interaction/zoomConstraints.ts +5 -1
  73. package/src/components/waveform.ts +9 -0
  74. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +15 -4
  75. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +11 -6
  76. package/src/components/waveformChartCases/dataLifecycle.test.ts +1 -3
  77. package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +117 -0
  78. package/src/components/waveformChartCases/xDomainStrategy.test.ts +14 -2
  79. package/src/core/compactSamplePointSource.ts +76 -0
  80. package/src/core/data.test.ts +114 -0
  81. package/src/core/data.ts +177 -75
  82. package/src/core/index.ts +11 -0
  83. package/src/core/rendering.test.ts +87 -12
  84. package/src/core/rendering.ts +35 -1
  85. package/src/core/renderingOptions.ts +124 -15
  86. package/src/core/samplingReference.test.ts +124 -0
  87. package/src/core/samplingReference.ts +243 -0
  88. package/src/core/samplingReferenceValues.ts +169 -0
  89. package/src/core/samplingReferenceVectors.ts +46 -0
  90. package/src/core/wasmSampling.test.ts +205 -0
  91. package/src/core/wasmSampling.ts +230 -0
  92. package/src/core/waveformPointSource.ts +395 -0
  93. package/src/core/workerSampling/index.ts +30 -0
  94. package/src/core/workerSampling/javascriptBackend.ts +15 -0
  95. package/src/core/workerSampling/multiresolution.test.ts +69 -0
  96. package/src/core/workerSampling/multiresolution.ts +300 -0
  97. package/src/core/workerSampling/protocol.test.ts +34 -0
  98. package/src/core/workerSampling/protocol.ts +207 -0
  99. package/src/core/workerSampling/repository.test.ts +400 -0
  100. package/src/core/workerSampling/repository.ts +400 -0
  101. package/src/core/workerSampling/repositoryCache.test.ts +163 -0
  102. package/src/core/workerSampling/repositoryDataset.ts +194 -0
  103. package/src/core/workerSampling/repositorySupport.ts +225 -0
  104. package/src/core/workerSampling/repositoryTypedDataset.test.ts +89 -0
  105. package/src/core/workerSampling/repositoryWasmDataset.test.ts +70 -0
  106. package/src/core/workerSampling/samplingCache.ts +94 -0
  107. package/src/core/workerSampling/workerRuntime.ts +40 -0
  108. package/src/data/simulatedWaveforms.test.ts +19 -0
  109. package/src/data/simulatedWaveforms.ts +11 -8
  110. package/src/demoRoutes.css +177 -0
  111. package/src/index.ts +18 -0
  112. package/src/router.ts +5 -0
  113. package/src/types/chart.ts +63 -0
  114. package/src/types/data.ts +24 -1
  115. package/src/types/index.ts +9 -0
  116. package/src/views/WasmSamplingDemo.test.ts +13 -0
  117. package/src/views/WasmSamplingDemo.vue +180 -0
@@ -0,0 +1,205 @@
1
+ /// <reference types="node" />
2
+
3
+ import { readFile } from 'node:fs/promises'
4
+ import { resolve } from 'node:path'
5
+
6
+ import { beforeAll, describe, expect, it } from 'vitest'
7
+
8
+ import { sampleWaveformReference } from './samplingReference'
9
+ import {
10
+ REFERENCE_AGGREGATE_VECTORS,
11
+ REFERENCE_SAMPLING_POINTS,
12
+ REFERENCE_SOURCE_INDEX_VECTORS,
13
+ } from './samplingReferenceVectors'
14
+ import {
15
+ calculateWasmRange,
16
+ createInitializedWasmSamplingBackend,
17
+ findWasmVisibleRange,
18
+ initializeWasmSampling,
19
+ sampleWaveformWasm,
20
+ } from './wasmSampling'
21
+
22
+ const x = Float64Array.from(REFERENCE_SAMPLING_POINTS, (point) => point.x)
23
+ const y = Float64Array.from(REFERENCE_SAMPLING_POINTS, (point) => point.y)
24
+
25
+ describe('sampleWaveformWasm', () => {
26
+ beforeAll(async () => {
27
+ await initializeWasmSampling(
28
+ await readFile(resolve(process.cwd(), 'wasm/pkg/waveform_sampling_wasm_bg.wasm')),
29
+ )
30
+ })
31
+
32
+ it('matches the shared source-index vectors and TypeScript reference', async () => {
33
+ for (const vector of REFERENCE_SOURCE_INDEX_VECTORS) {
34
+ const wasm = await sampleWaveformWasm({ x, y, ...vector })
35
+ const reference = sampleWaveformReference({
36
+ points: REFERENCE_SAMPLING_POINTS,
37
+ strategy: vector.strategy,
38
+ targetPointCount: vector.targetPointCount,
39
+ })
40
+
41
+ expect(wasm.kind).toBe('source-indexes')
42
+ expect(reference.kind).toBe('source-indexes')
43
+ if (wasm.kind === 'source-indexes' && reference.kind === 'source-indexes') {
44
+ expect(Array.from(wasm.sourceIndexes)).toEqual(vector.expectedSourceIndexes)
45
+ expect(Array.from(wasm.sourceIndexes)).toEqual(Array.from(reference.sourceIndexes))
46
+ }
47
+ }
48
+ })
49
+
50
+ it('matches the shared aggregate vectors and TypeScript reference', async () => {
51
+ for (const vector of REFERENCE_AGGREGATE_VECTORS) {
52
+ const wasm = await sampleWaveformWasm({ x, y, ...vector })
53
+ const reference = sampleWaveformReference({
54
+ points: REFERENCE_SAMPLING_POINTS,
55
+ strategy: vector.strategy,
56
+ targetPointCount: vector.targetPointCount,
57
+ })
58
+
59
+ expect(wasm.kind).toBe('aggregates')
60
+ expect(reference.kind).toBe('aggregates')
61
+ if (wasm.kind === 'aggregates' && reference.kind === 'aggregates') {
62
+ expect(Array.from(wasm.x)).toEqual(vector.expectedX)
63
+ expect(Array.from(wasm.y)).toEqual(vector.expectedY)
64
+ expect(Array.from(wasm.x)).toEqual(Array.from(reference.x))
65
+ expect(Array.from(wasm.y)).toEqual(Array.from(reference.y))
66
+ }
67
+ }
68
+ })
69
+
70
+ it('preserves reference filtering, stable sorting, and original source indexes', async () => {
71
+ const wasm = await sampleWaveformWasm({
72
+ x: Float64Array.from([2, Number.NaN, 1, 1, 3]),
73
+ y: Float64Array.from([20, 99, 10, 11, Number.POSITIVE_INFINITY]),
74
+ strategy: 'none',
75
+ targetPointCount: 1,
76
+ })
77
+
78
+ expect(wasm).toEqual({ kind: 'source-indexes', sourceIndexes: Uint32Array.from([2, 3, 0]) })
79
+ })
80
+
81
+ it('calculates full domains and normalized visible ranges', async () => {
82
+ const range = await calculateWasmRange(
83
+ Float64Array.from([2, Number.NaN, 1, 1, 3]),
84
+ Float64Array.from([20, 99, 10, 11, Number.POSITIVE_INFINITY]),
85
+ )
86
+ const visible = await findWasmVisibleRange(
87
+ Float64Array.from([2, Number.NaN, 1, 1, 3]),
88
+ Float64Array.from([20, 99, 10, 11, Number.POSITIVE_INFINITY]),
89
+ [1, 1],
90
+ )
91
+
92
+ expect(range).toEqual({ x: [1, 2], y: [10, 20] })
93
+ expect(visible).toEqual([0, 2])
94
+ await expect(calculateWasmRange(Float64Array.of(0), new Float64Array())).rejects.toThrow(
95
+ 'x and y must have the same length',
96
+ )
97
+ })
98
+
99
+ it('matches the reference across strategy and target boundary cases', async () => {
100
+ const points = [
101
+ { x: 3, y: 3 },
102
+ { x: 1, y: -2 },
103
+ { x: 1, y: 4 },
104
+ { x: Number.NaN, y: 6 },
105
+ { x: 2, y: -1 },
106
+ { x: 4, y: Number.POSITIVE_INFINITY },
107
+ { x: 5, y: 0 },
108
+ ]
109
+ const caseX = Float64Array.from(points, (point) => point.x)
110
+ const caseY = Float64Array.from(points, (point) => point.y)
111
+ const strategies = ['none', 'peak', 'lttb', 'average', 'min', 'max', 'minmax', 'sum'] as const
112
+ const targets = [0, 1, 2, 3, 6, 20, Number.POSITIVE_INFINITY]
113
+
114
+ for (const strategy of strategies) {
115
+ for (const targetPointCount of targets) {
116
+ const wasm = await sampleWaveformWasm({
117
+ x: caseX,
118
+ y: caseY,
119
+ strategy,
120
+ targetPointCount,
121
+ })
122
+ const reference = sampleWaveformReference({ points, strategy, targetPointCount })
123
+
124
+ expect(wasm.kind).toBe(reference.kind)
125
+ if (wasm.kind === 'source-indexes' && reference.kind === 'source-indexes') {
126
+ expect(Array.from(wasm.sourceIndexes)).toEqual(Array.from(reference.sourceIndexes))
127
+ }
128
+ if (wasm.kind === 'aggregates' && reference.kind === 'aggregates') {
129
+ expect(Array.from(wasm.x)).toEqual(Array.from(reference.x))
130
+ expect(Array.from(wasm.y)).toEqual(Array.from(reference.y))
131
+ }
132
+ }
133
+ }
134
+ })
135
+
136
+ it('rejects coordinate arrays with unequal lengths', async () => {
137
+ await expect(
138
+ sampleWaveformWasm({
139
+ x: Float64Array.of(0),
140
+ y: new Float64Array(),
141
+ strategy: 'none',
142
+ targetPointCount: 1,
143
+ }),
144
+ ).rejects.toThrow('x and y must have the same length')
145
+ })
146
+
147
+ it('keeps a bounded dataset index in WASM for unaligned viewport queries and releases it', () => {
148
+ const backend = createInitializedWasmSamplingBackend()
149
+ const handle = backend.registerDataset(x, y, 1024 * 1024)
150
+ const visible = REFERENCE_SAMPLING_POINTS.slice(2, 9)
151
+
152
+ for (const strategy of ['peak', 'min', 'max', 'minmax'] as const) {
153
+ const actual = backend.sampleDataset(handle, 2, 9, strategy, 6)
154
+ const expected = sampleWaveformReference({
155
+ points: visible,
156
+ strategy,
157
+ targetPointCount: 6,
158
+ })
159
+ expect(actual.kind).toBe('source-indexes')
160
+ expect(expected.kind).toBe('source-indexes')
161
+ if (actual.kind === 'source-indexes' && expected.kind === 'source-indexes') {
162
+ expect(Array.from(actual.sourceIndexes)).toEqual(
163
+ Array.from(expected.sourceIndexes, (index) => index + 2),
164
+ )
165
+ }
166
+ }
167
+
168
+ const aggregates = backend.sampleDataset(handle, 2, 9, 'average', 4)
169
+ const expectedAggregates = sampleWaveformReference({
170
+ points: visible,
171
+ strategy: 'average',
172
+ targetPointCount: 4,
173
+ })
174
+ expect(aggregates.kind).toBe('aggregates')
175
+ expect(expectedAggregates.kind).toBe('aggregates')
176
+ if (aggregates.kind === 'aggregates' && expectedAggregates.kind === 'aggregates') {
177
+ aggregates.x.forEach((value, index) =>
178
+ expect(value).toBeCloseTo(expectedAggregates.x[index]!, 12),
179
+ )
180
+ aggregates.y.forEach((value, index) =>
181
+ expect(value).toBeCloseTo(expectedAggregates.y[index]!, 12),
182
+ )
183
+ }
184
+ expect(backend.datasetIndexBytes(handle)).toBeGreaterThan(0)
185
+ backend.disposeDataset(handle)
186
+ expect(backend.datasetIndexBytes(handle)).toBe(0)
187
+ })
188
+
189
+ it('registers evenly sampled values without an explicit X column', () => {
190
+ const backend = createInitializedWasmSamplingBackend()
191
+ const handle = backend.registerSamples(
192
+ new Float64Array([1, 9, 2]),
193
+ new Uint32Array([0, 2, 3]),
194
+ 10,
195
+ 2,
196
+ 1024 * 1024,
197
+ )
198
+
199
+ expect(backend.sampleDataset(handle, 0, 3, 'max', 3)).toMatchObject({
200
+ kind: 'source-indexes',
201
+ sourceIndexes: Uint32Array.from([0, 1, 2]),
202
+ })
203
+ backend.disposeDataset(handle)
204
+ })
205
+ })
@@ -0,0 +1,230 @@
1
+ import initWasm, {
2
+ calculate_range,
3
+ dataset_index_bytes,
4
+ dispose_all_datasets,
5
+ dispose_dataset,
6
+ find_visible_range,
7
+ register_dataset,
8
+ register_sample_dataset,
9
+ sample_aggregates,
10
+ sample_dataset_aggregates,
11
+ sample_dataset_indexes,
12
+ sample_indexes,
13
+ } from '../../wasm/pkg/waveform_sampling_wasm'
14
+ import type { InitInput } from '../../wasm/pkg/waveform_sampling_wasm'
15
+
16
+ import type { ReferenceSamplingStrategy } from './samplingReference'
17
+ import type { WorkerSamplingBackend, WorkerSamplingOutput } from './workerSampling/protocol'
18
+
19
+ export type WasmSamplingStrategy = ReferenceSamplingStrategy
20
+
21
+ export interface WasmSamplingRequest {
22
+ x: Float64Array
23
+ y: Float64Array
24
+ strategy: WasmSamplingStrategy
25
+ targetPointCount: number
26
+ }
27
+
28
+ export interface WasmSourceIndexSamplingResult {
29
+ kind: 'source-indexes'
30
+ sourceIndexes: Uint32Array
31
+ }
32
+
33
+ export interface WasmAggregateSamplingResult {
34
+ kind: 'aggregates'
35
+ x: Float64Array
36
+ y: Float64Array
37
+ }
38
+
39
+ export type WasmSamplingResult = WasmSourceIndexSamplingResult | WasmAggregateSamplingResult
40
+
41
+ export interface WasmDatasetSamplingBackend extends WorkerSamplingBackend {
42
+ registerDataset(x: Float64Array, y: Float64Array, indexMaxBytes?: number): number
43
+ registerSamples(
44
+ y: Float64Array,
45
+ sourceIndexes: Uint32Array | undefined,
46
+ startTime: number,
47
+ sampleRate: number,
48
+ indexMaxBytes?: number,
49
+ ): number
50
+ disposeDataset(handle: number): void
51
+ disposeAllDatasets(): void
52
+ datasetIndexBytes(handle: number): number
53
+ sampleDataset(
54
+ handle: number,
55
+ start: number,
56
+ end: number,
57
+ strategy: WasmSamplingStrategy,
58
+ targetPointCount: number,
59
+ ): WorkerSamplingOutput
60
+ }
61
+
62
+ export interface WasmRangeMetrics {
63
+ x: [number, number]
64
+ y: [number, number]
65
+ }
66
+
67
+ const strategyCodes: Record<WasmSamplingStrategy, number> = {
68
+ none: 0,
69
+ peak: 1,
70
+ lttb: 2,
71
+ average: 3,
72
+ min: 4,
73
+ max: 5,
74
+ minmax: 6,
75
+ sum: 7,
76
+ }
77
+
78
+ let initialization: Promise<void> | undefined
79
+
80
+ function targetPointCountForWasm(value: number, pointCount: number) {
81
+ if (pointCount === 0) return 0
82
+ if (!Number.isFinite(value)) return pointCount
83
+ return Math.min(pointCount, Math.max(1, Math.floor(value)))
84
+ }
85
+
86
+ function validateRequest(request: WasmSamplingRequest) {
87
+ if (request.x.length !== request.y.length) {
88
+ throw new Error('x and y must have the same length')
89
+ }
90
+ }
91
+
92
+ /** Initializes the bundled WebAssembly module once for the current JavaScript realm. */
93
+ export function initializeWasmSampling(input?: InitInput | Promise<InitInput>): Promise<void> {
94
+ initialization ??= Promise.resolve(initWasm(input)).then(() => undefined)
95
+ return initialization
96
+ }
97
+
98
+ /**
99
+ * Runs the pure numeric sampler on flat Float64Array coordinates. The caller owns source-data
100
+ * normalization and receives either original input indexes or newly allocated aggregate arrays.
101
+ */
102
+ export async function sampleWaveformWasm(
103
+ request: WasmSamplingRequest,
104
+ ): Promise<WasmSamplingResult> {
105
+ validateRequest(request)
106
+ await initializeWasmSampling()
107
+ const target = targetPointCountForWasm(request.targetPointCount, request.x.length)
108
+ const strategy = strategyCodes[request.strategy]
109
+ if (request.strategy !== 'average' && request.strategy !== 'sum') {
110
+ return {
111
+ kind: 'source-indexes',
112
+ sourceIndexes: sample_indexes(request.x, request.y, strategy, target),
113
+ }
114
+ }
115
+
116
+ const values = sample_aggregates(request.x, request.y, strategy, target)
117
+ const x = new Float64Array(values.length / 2)
118
+ const y = new Float64Array(values.length / 2)
119
+ for (let index = 0; index < x.length; index += 1) {
120
+ x[index] = values[index * 2]!
121
+ y[index] = values[index * 2 + 1]!
122
+ }
123
+ return { kind: 'aggregates', x, y }
124
+ }
125
+
126
+ /**
127
+ * Creates the synchronous backend used only after this realm has finished WASM initialization.
128
+ * Worker code calls `initializeWasmSampling` before exposing this backend to its repository.
129
+ */
130
+ export function createInitializedWasmSamplingBackend(): WasmDatasetSamplingBackend {
131
+ const aggregateOutput = (values: Float64Array): WasmAggregateSamplingResult => {
132
+ const x = new Float64Array(values.length / 2)
133
+ const y = new Float64Array(values.length / 2)
134
+ for (let index = 0; index < x.length; index += 1) {
135
+ x[index] = values[index * 2]!
136
+ y[index] = values[index * 2 + 1]!
137
+ }
138
+ return { kind: 'aggregates', x, y }
139
+ }
140
+ const backend: WasmDatasetSamplingBackend = {
141
+ kind: 'wasm',
142
+ sample({ x, y, strategy, targetPointCount }) {
143
+ const request = { x, y, strategy, targetPointCount }
144
+ validateRequest(request)
145
+ const target = targetPointCountForWasm(request.targetPointCount, request.x.length)
146
+ if (request.strategy !== 'average' && request.strategy !== 'sum') {
147
+ return {
148
+ kind: 'source-indexes' as const,
149
+ sourceIndexes: sample_indexes(
150
+ request.x,
151
+ request.y,
152
+ strategyCodes[request.strategy],
153
+ target,
154
+ ),
155
+ }
156
+ }
157
+ const values = sample_aggregates(
158
+ request.x,
159
+ request.y,
160
+ strategyCodes[request.strategy],
161
+ target,
162
+ )
163
+ return aggregateOutput(values)
164
+ },
165
+ registerDataset(x, y, indexMaxBytes) {
166
+ validateRequest({ x, y, strategy: 'peak', targetPointCount: x.length })
167
+ return register_dataset(
168
+ x,
169
+ y,
170
+ Math.max(0, Math.min(0xffffffff, Math.floor(indexMaxBytes ?? 8 * 1024 * 1024))),
171
+ )
172
+ },
173
+ registerSamples(y, sourceIndexes, startTime, sampleRate, indexMaxBytes) {
174
+ return register_sample_dataset(
175
+ y,
176
+ sourceIndexes ?? new Uint32Array(),
177
+ startTime,
178
+ sampleRate,
179
+ Math.max(0, Math.min(0xffffffff, Math.floor(indexMaxBytes ?? 8 * 1024 * 1024))),
180
+ )
181
+ },
182
+ disposeDataset(handle) {
183
+ dispose_dataset(handle)
184
+ },
185
+ disposeAllDatasets() {
186
+ dispose_all_datasets()
187
+ },
188
+ datasetIndexBytes(handle) {
189
+ return dataset_index_bytes(handle)
190
+ },
191
+ sampleDataset(handle, start, end, strategy, targetPointCount) {
192
+ const target = Math.max(0, Math.floor(targetPointCount))
193
+ if (strategy === 'average' || strategy === 'sum') {
194
+ return aggregateOutput(
195
+ sample_dataset_aggregates(handle, start, end, strategyCodes[strategy], target),
196
+ )
197
+ }
198
+ return {
199
+ kind: 'source-indexes',
200
+ sourceIndexes: sample_dataset_indexes(handle, start, end, strategyCodes[strategy], target),
201
+ }
202
+ },
203
+ }
204
+ return backend
205
+ }
206
+
207
+ /** Calculates domains from valid points without changing the input arrays. */
208
+ export async function calculateWasmRange(
209
+ x: Float64Array,
210
+ y: Float64Array,
211
+ ): Promise<WasmRangeMetrics | undefined> {
212
+ if (x.length !== y.length) throw new Error('x and y must have the same length')
213
+ await initializeWasmSampling()
214
+ const result = calculate_range(x, y)
215
+ return result.length === 4
216
+ ? { x: [result[0]!, result[1]!], y: [result[2]!, result[3]!] }
217
+ : undefined
218
+ }
219
+
220
+ /** Locates a half-open visible range in the filtered, stable ascending-X point sequence. */
221
+ export async function findWasmVisibleRange(
222
+ x: Float64Array,
223
+ y: Float64Array,
224
+ domain: [number, number],
225
+ ): Promise<[number, number]> {
226
+ if (x.length !== y.length) throw new Error('x and y must have the same length')
227
+ await initializeWasmSampling()
228
+ const result = find_visible_range(x, y, domain[0], domain[1])
229
+ return [result[0]!, result[1]!]
230
+ }