waveform-analysis 0.1.53 → 0.1.54

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 CHANGED
@@ -855,7 +855,8 @@ Worker 的 JavaScript 回退后端和 Rust/WASM 后端都按需建立多分辨
855
855
  同步释放其索引与缓存项。`sampling-complete.cacheHit` 只在 Worker 实际返回该缓存项时为 `true`。
856
856
 
857
857
  演示可在 `#/wasm-sampling` 打开。该路由生成 10 条各 100,000 点的 `Float32Array` 通道,可切换
858
- `auto` / `wasm` / `raw`,所有采样策略、自动阈值和每条线最大渲染点数;右侧表格显示每个系列的真实后端、
858
+ `auto` / `wasm` / `raw`,所有采样策略与自动阈值;Peak 类策略按每像素点数采样,LTTB、Average、
859
+ Sum 按每条线的目标渲染点数采样。右侧表格显示每个系列的真实后端、
859
860
  源点/可见点/渲染点、耗时和缓存命中状态。
860
861
 
861
862
  库产物会把 WASM 数据内联到 Worker 运行时,消费者无需额外复制 `.wasm` 文件。严格 CSP 部署
@@ -0,0 +1,6 @@
1
+ import type { WaveformSamplingOptions, WaveformSamplingStrategy } from '../types';
2
+ type SamplingSizeOptions = Pick<WaveformSamplingOptions, 'maxPointCount' | 'maxPointsPerPixel'>;
3
+ export declare function usesFixedPointCount(strategy: WaveformSamplingStrategy): boolean;
4
+ export declare function showsSamplingSize(strategy: WaveformSamplingStrategy): strategy is "auto" | "peak" | "lttb" | "average" | "min" | "max" | "minmax" | "sum";
5
+ export declare function samplingSizeOptions(strategy: WaveformSamplingStrategy, maxPointsPerPixel: number, maxPointCount: number): SamplingSizeOptions;
6
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -49,4 +49,58 @@ describe('WorkerSamplingRepository sampling targets', () => {
49
49
  if (response.type !== 'sample-viewport-response') return
50
50
  expect(response.results[0]?.diagnostics.renderedPointCount).toBeLessThanOrEqual(4)
51
51
  })
52
+
53
+ it.each(['lttb', 'average', 'sum'] as const)(
54
+ 'returns exactly the fixed target for %s when enough points are visible',
55
+ (strategy) => {
56
+ const source = Array.from({ length: 100_000 }, (_, index) => ({
57
+ x: index,
58
+ y: Math.sin(index / 10),
59
+ }))
60
+ const repository = new WorkerSamplingRepository()
61
+ repository.handle({
62
+ type: 'register-dataset',
63
+ requestId: 1,
64
+ datasetId: strategy,
65
+ revision: 0,
66
+ points: source,
67
+ })
68
+ const response = repository.handle({
69
+ type: 'sample-viewport',
70
+ requestId: 2,
71
+ series: [
72
+ {
73
+ ...sampleSeries(strategy),
74
+ xDomain: [0, 99_999],
75
+ strategy,
76
+ maxPointCount: 1_000,
77
+ },
78
+ ],
79
+ })
80
+
81
+ expect(response).toMatchObject({
82
+ results: [{ diagnostics: { visiblePointCount: 100_000, renderedPointCount: 1_000 } }],
83
+ })
84
+ },
85
+ )
86
+
87
+ it('does not add points when the visible source has fewer than the fixed target', () => {
88
+ const repository = new WorkerSamplingRepository()
89
+ register(repository, 'small-target')
90
+ const response = repository.handle({
91
+ type: 'sample-viewport',
92
+ requestId: 3,
93
+ series: [
94
+ {
95
+ ...sampleSeries('small-target'),
96
+ strategy: 'lttb',
97
+ maxPointCount: 100,
98
+ },
99
+ ],
100
+ })
101
+
102
+ expect(response).toMatchObject({
103
+ results: [{ diagnostics: { visiblePointCount: 12, renderedPointCount: 12 } }],
104
+ })
105
+ })
52
106
  })
@@ -11,6 +11,11 @@ import type {
11
11
  WaveformSamplingMode,
12
12
  WaveformSamplingStrategy,
13
13
  } from '../types'
14
+ import {
15
+ samplingSizeOptions,
16
+ showsSamplingSize,
17
+ usesFixedPointCount,
18
+ } from './wasmSamplingDemoSampling'
14
19
 
15
20
  const SERIES_COUNT = 10
16
21
  const POINTS_PER_SERIES = 100_000
@@ -68,6 +73,7 @@ const data = createDenseData()
68
73
  const mode = ref<WaveformSamplingMode>('auto')
69
74
  const strategy = ref<WaveformSamplingStrategy>('peak')
70
75
  const autoThreshold = ref(1_000)
76
+ const maxPointsPerPixel = ref(3)
71
77
  const maxPointCount = ref(1_000)
72
78
  const minVisiblePoints = ref(2)
73
79
  const annotations = ref<WaveformAnnotation[]>([])
@@ -78,7 +84,7 @@ const rendering = computed<WaveformRenderingOptions>(() => ({
78
84
  mode: mode.value,
79
85
  strategy: strategy.value,
80
86
  autoThreshold: autoThreshold.value,
81
- maxPointCount: maxPointCount.value,
87
+ ...samplingSizeOptions(strategy.value, maxPointsPerPixel.value, maxPointCount.value),
82
88
  rawPointLimit: 100_000,
83
89
  wasmFailureFallback: 'javascript',
84
90
  },
@@ -128,8 +134,8 @@ function pointCount(value: number | undefined) {
128
134
  size="small"
129
135
  />
130
136
  </label>
131
- <label>
132
- <span>最多渲染点</span>
137
+ <label v-if="usesFixedPointCount(strategy)">
138
+ <span>目标渲染点数</span>
133
139
  <InputNumber
134
140
  v-model:value="maxPointCount"
135
141
  :min="1"
@@ -138,6 +144,17 @@ function pointCount(value: number | undefined) {
138
144
  size="small"
139
145
  />
140
146
  </label>
147
+ <label v-else-if="showsSamplingSize(strategy)">
148
+ <span>每像素点数</span>
149
+ <InputNumber
150
+ v-model:value="maxPointsPerPixel"
151
+ :min="0.1"
152
+ :max="10"
153
+ :step="0.1"
154
+ size="small"
155
+ />
156
+ </label>
157
+ <span v-else class="sampling-demo__sampling-disabled">当前策略不执行降采样</span>
141
158
  <label>
142
159
  <span>最少可见点</span>
143
160
  <InputNumber v-model:value="minVisiblePoints" :min="2" :max="1000" :step="1" size="small" />
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import {
4
+ samplingSizeOptions,
5
+ showsSamplingSize,
6
+ usesFixedPointCount,
7
+ } from './wasmSamplingDemoSampling'
8
+
9
+ describe('WASM sampling demo sizing', () => {
10
+ it('uses pixel density for peak-oriented and extrema strategies', () => {
11
+ expect(samplingSizeOptions('auto', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
12
+ expect(samplingSizeOptions('peak', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
13
+ expect(samplingSizeOptions('minmax', 3, 1_000)).toEqual({ maxPointsPerPixel: 3 })
14
+ })
15
+
16
+ it('uses a fixed point count for LTTB and aggregate strategies', () => {
17
+ for (const strategy of ['lttb', 'average', 'sum'] as const) {
18
+ expect(usesFixedPointCount(strategy)).toBe(true)
19
+ expect(samplingSizeOptions(strategy, 3, 1_000)).toEqual({ maxPointCount: 1_000 })
20
+ }
21
+ })
22
+
23
+ it('does not show a sampling-size control or pass a size for none', () => {
24
+ expect(showsSamplingSize('none')).toBe(false)
25
+ expect(samplingSizeOptions('none', 3, 1_000)).toEqual({})
26
+ })
27
+ })
@@ -0,0 +1,23 @@
1
+ import type { WaveformSamplingOptions, WaveformSamplingStrategy } from '../types'
2
+
3
+ type SamplingSizeOptions = Pick<WaveformSamplingOptions, 'maxPointCount' | 'maxPointsPerPixel'>
4
+
5
+ const fixedPointCountStrategies: readonly WaveformSamplingStrategy[] = ['lttb', 'average', 'sum']
6
+
7
+ export function usesFixedPointCount(strategy: WaveformSamplingStrategy) {
8
+ return fixedPointCountStrategies.includes(strategy)
9
+ }
10
+
11
+ export function showsSamplingSize(strategy: WaveformSamplingStrategy) {
12
+ return strategy !== 'none'
13
+ }
14
+
15
+ export function samplingSizeOptions(
16
+ strategy: WaveformSamplingStrategy,
17
+ maxPointsPerPixel: number,
18
+ maxPointCount: number,
19
+ ): SamplingSizeOptions {
20
+ if (usesFixedPointCount(strategy)) return { maxPointCount }
21
+ if (!showsSamplingSize(strategy)) return {}
22
+ return { maxPointsPerPixel }
23
+ }