waveform-analysis 0.1.51 → 0.1.53

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.
@@ -334,6 +334,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
334
334
  pannable: boolean;
335
335
  minZoomSpan?: number | undefined;
336
336
  minVisiblePoints: number;
337
+ maxZoomScale?: number | null | undefined;
337
338
  initialXDomains?: Record<string, [number, number]> | undefined;
338
339
  xDomainStrategy: import("../..").WaveformXDomainStrategy;
339
340
  yDomain?: [number, number] | undefined;
@@ -14,6 +14,7 @@ export interface WaveformChartProps {
14
14
  pannable?: boolean;
15
15
  minZoomSpan?: number;
16
16
  minVisiblePoints?: number;
17
+ maxZoomScale?: number | null;
17
18
  initialXDomain?: [number, number];
18
19
  initialXDomains?: Record<string, [number, number]>;
19
20
  xDomainStrategy?: WaveformXDomainStrategy;
@@ -9,6 +9,7 @@ export type ZoomSeriesGroup = readonly PointSeriesSource[];
9
9
  interface ZoomConstraintOptions {
10
10
  minZoomSpan?: number;
11
11
  minVisiblePoints?: number;
12
+ maxZoomScale?: number | null;
12
13
  }
13
14
  export declare function resolveMinimumZoomSpan(boundary: [number, number], groups: readonly ZoomSeriesGroup[], options: ZoomConstraintOptions): number;
14
15
  export declare function constrainZoomDomain(domain: [number, number], boundary: [number, number], groups: readonly ZoomSeriesGroup[], options: ZoomConstraintOptions): [number, number];
@@ -5,6 +5,7 @@ export interface ResolvedWaveformSamplingOptions {
5
5
  autoHysteresis: number;
6
6
  strategy: WaveformSamplingStrategy;
7
7
  maxPointsPerPixel: number;
8
+ maxPointCount: number | undefined;
8
9
  rawPointLimit: number;
9
10
  wasmFailureFallback: 'error' | 'javascript';
10
11
  }
@@ -70,6 +70,7 @@ export interface WorkerSamplingSeriesRequest {
70
70
  previousSelectedMode?: 'raw' | 'sampled';
71
71
  strategy: WaveformSamplingStrategy;
72
72
  maxPointsPerPixel?: number;
73
+ maxPointCount?: number;
73
74
  rawPointLimit?: number;
74
75
  wasmFailureFallback?: 'error' | 'javascript';
75
76
  /** Rendering inputs that affect the cache key, even when only line geometry is overridden. */
@@ -84,6 +84,8 @@ export interface WaveformSamplingOptions {
84
84
  strategy?: WaveformSamplingStrategy;
85
85
  /** Upper bound for rendered points per horizontal CSS pixel. */
86
86
  maxPointsPerPixel?: number;
87
+ /** Fixed per-series target point count. When set, this takes precedence over pixel density. */
88
+ maxPointCount?: number;
87
89
  /** Visible-point count at which raw rendering should report a diagnostic. */
88
90
  rawPointLimit?: number;
89
91
  /** Controls whether a forced WASM failure may temporarily show JavaScript results. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -213,6 +213,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
213
213
  isPresentationMode,
214
214
  () => props.minZoomSpan,
215
215
  () => props.minVisiblePoints,
216
+ () => props.maxZoomScale,
216
217
  () => props.initialXDomain,
217
218
  () => props.initialXDomains,
218
219
  () => props.xDomainStrategy,
@@ -75,11 +75,14 @@ function rawDiagnostics(target: SamplingTarget, requestId: number): WorkerSampli
75
75
  }
76
76
  }
77
77
 
78
- function fallbackPoints(target: SamplingTarget, maxPointsPerPixel: number) {
78
+ function fallbackPoints(target: SamplingTarget) {
79
79
  const { source } = target
80
80
  const { start, end } = target.visibleRange
81
81
  const count = end - start
82
- const targetCount = Math.max(1, Math.floor(target.request.plotWidth * maxPointsPerPixel))
82
+ const targetCount =
83
+ Number.isFinite(target.request.maxPointCount) && (target.request.maxPointCount ?? 0) >= 1
84
+ ? Math.floor(target.request.maxPointCount as number)
85
+ : Math.max(1, Math.floor(target.request.plotWidth * target.request.maxPointsPerPixel!))
83
86
  if (count <= targetCount) return source.pointsInRange(start, end)
84
87
  if (targetCount === 1) return source.pointAt(start) ? [source.pointAt(start)!] : []
85
88
  const points: WaveformPoint[] = []
@@ -141,6 +144,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
141
144
  autoHysteresis: sampling.autoHysteresis,
142
145
  strategy: sampling.strategy,
143
146
  maxPointsPerPixel: sampling.maxPointsPerPixel,
147
+ maxPointCount: sampling.maxPointCount,
144
148
  rawPointLimit: sampling.rawPointLimit,
145
149
  wasmFailureFallback: sampling.wasmFailureFallback,
146
150
  lineType: series.lineType,
@@ -159,7 +163,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
159
163
  `${dataEpoch.value}:${targets.value
160
164
  .map(
161
165
  (target) =>
162
- `${target.request.datasetId}:${target.request.xDomain.join(',')}:${target.request.plotWidth}:${target.visibleRange.start}:${target.visibleRange.end}:${target.request.mode}:${target.request.autoThreshold}:${target.request.autoHysteresis}:${target.request.strategy}:${target.request.maxPointsPerPixel}:${target.request.rawPointLimit}:${target.request.wasmFailureFallback}:${target.request.lineType}:${target.request.pointType}:${target.request.errorBarVisible}:${target.request.pointMinSpacing}:${target.request.errorBarMinSpacing}`,
166
+ `${target.request.datasetId}:${target.request.xDomain.join(',')}:${target.request.plotWidth}:${target.visibleRange.start}:${target.visibleRange.end}:${target.request.mode}:${target.request.autoThreshold}:${target.request.autoHysteresis}:${target.request.strategy}:${target.request.maxPointsPerPixel}:${target.request.maxPointCount}:${target.request.rawPointLimit}:${target.request.wasmFailureFallback}:${target.request.lineType}:${target.request.pointType}:${target.request.errorBarVisible}:${target.request.pointMinSpacing}:${target.request.errorBarMinSpacing}`,
163
167
  )
164
168
  .join('|')}`,
165
169
  )
@@ -253,10 +257,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
253
257
  diagnostics.push(rawDiagnostics(target, token))
254
258
  } else {
255
259
  if (!nextOverrides[target.series.id]?.length) {
256
- nextOverrides[target.series.id] = fallbackPoints(
257
- target,
258
- context.renderingOptions.value.sampling.maxPointsPerPixel,
259
- )
260
+ nextOverrides[target.series.id] = fallbackPoints(target)
260
261
  }
261
262
  sampledTargets.push({
262
263
  ...target,
@@ -35,6 +35,7 @@ export interface WaveformChartProps {
35
35
  pannable?: boolean
36
36
  minZoomSpan?: number
37
37
  minVisiblePoints?: number
38
+ maxZoomScale?: number | null
38
39
  initialXDomain?: [number, number]
39
40
  initialXDomains?: Record<string, [number, number]>
40
41
  xDomainStrategy?: WaveformXDomainStrategy
@@ -231,7 +231,7 @@ export function useWaveformZoom(context: ZoomContext) {
231
231
  const minimumSpan = resolveMinimumZoomSpan(domain, groups, props)
232
232
  return Math.max(
233
233
  ZOOM_CONSTRAINTS.MIN_SCALE,
234
- minimumSpan > 0 ? domainSpan / minimumSpan : ZOOM_CONSTRAINTS.DEFAULT_MAX_SCALE,
234
+ minimumSpan > 0 ? domainSpan / minimumSpan : Number.POSITIVE_INFINITY,
235
235
  )
236
236
  }
237
237
  const constrainTransform = (
@@ -14,6 +14,7 @@ export type ZoomSeriesGroup = readonly PointSeriesSource[]
14
14
  interface ZoomConstraintOptions {
15
15
  minZoomSpan?: number
16
16
  minVisiblePoints?: number
17
+ maxZoomScale?: number | null
17
18
  }
18
19
 
19
20
  const xValueCache = new WeakMap<object, Map<string, number[]>>()
@@ -80,10 +81,14 @@ export function resolveMinimumZoomSpan(
80
81
  : 0
81
82
  const required = resolveRequiredPointCount(options.minVisiblePoints)
82
83
  const pointSpan = minimumPointSpan(groups, normalized, required)
83
- if (configuredSpan > 0 || required > 0) {
84
- return Math.max(configuredSpan, pointSpan)
85
- }
86
- return boundarySpan / ZOOM_CONSTRAINTS.DEFAULT_MAX_SCALE
84
+ const configuredScale = Number(options.maxZoomScale)
85
+ const scaleSpan =
86
+ Number.isFinite(configuredScale) && configuredScale >= ZOOM_CONSTRAINTS.MIN_SCALE
87
+ ? boundarySpan / configuredScale
88
+ : options.maxZoomScale !== null && configuredSpan === 0 && required === 0
89
+ ? boundarySpan / ZOOM_CONSTRAINTS.DEFAULT_MAX_SCALE
90
+ : 0
91
+ return Math.max(configuredSpan, pointSpan, scaleSpan)
87
92
  }
88
93
 
89
94
  function expandToMinimumPoints(
@@ -65,10 +65,10 @@ function dispatchBox(
65
65
  }
66
66
 
67
67
  describe('WaveformChart point-aware zoom constraints', () => {
68
- it('wheel-zooms 1,200 samples to two distinct X values, stops, and still zooms out', async () => {
68
+ it('zooms to two distinct X values when the scale limit is disabled', async () => {
69
69
  const wrapper = await mountSizedChart(
70
70
  { kind: 'points', points: regularPoints },
71
- { minVisiblePoints: 2 },
71
+ { minVisiblePoints: 2, maxZoomScale: null },
72
72
  )
73
73
  const { overlay, width, height } = prepareOverlay(
74
74
  wrapper,
@@ -151,7 +151,7 @@ describe('WaveformChart point-aware zoom constraints', () => {
151
151
  expect(visibleXCount(constrainedDomain)).toBe(2)
152
152
  })
153
153
 
154
- it('retains the default 40x maximum when no explicit limit is configured', async () => {
154
+ it('retains the backwards-compatible default maximum zoom scale', async () => {
155
155
  const wrapper = await mountSizedChart({ kind: 'points', points: regularPoints })
156
156
  const { overlay, width, height } = prepareOverlay(
157
157
  wrapper,
@@ -163,6 +163,66 @@ describe('WaveformChart point-aware zoom constraints', () => {
163
163
  expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 40)
164
164
  })
165
165
 
166
+ it('honors an explicit maximum zoom scale', async () => {
167
+ const wrapper = await mountSizedChart(
168
+ { kind: 'points', points: regularPoints },
169
+ { maxZoomScale: 20 },
170
+ )
171
+ const { overlay, width, height } = prepareOverlay(
172
+ wrapper,
173
+ '.waveform-chart__overlay--independent',
174
+ )
175
+
176
+ await dispatchWheel(overlay, width, height, -4_000)
177
+ const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
178
+ expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 20)
179
+ })
180
+
181
+ it('disables the compatibility scale limit when configured with null', async () => {
182
+ const wrapper = await mountSizedChart(
183
+ { kind: 'points', points: regularPoints },
184
+ { maxZoomScale: null },
185
+ )
186
+ const { overlay, width, height } = prepareOverlay(
187
+ wrapper,
188
+ '.waveform-chart__overlay--independent',
189
+ )
190
+
191
+ await dispatchWheel(overlay, width, height, -4_000)
192
+ const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
193
+ expect(domain[1] - domain[0]).toBeLessThan(1_199 / 40)
194
+ })
195
+
196
+ it('falls back to the compatibility limit for an invalid maximum scale', async () => {
197
+ const wrapper = await mountSizedChart(
198
+ { kind: 'points', points: regularPoints },
199
+ { maxZoomScale: 0 },
200
+ )
201
+ const { overlay, width, height } = prepareOverlay(
202
+ wrapper,
203
+ '.waveform-chart__overlay--independent',
204
+ )
205
+
206
+ await dispatchWheel(overlay, width, height, -4_000)
207
+ const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
208
+ expect(domain[1] - domain[0]).toBeCloseTo(1_199 / 40)
209
+ })
210
+
211
+ it('uses the strictest configured zoom constraint', async () => {
212
+ const wrapper = await mountSizedChart(
213
+ { kind: 'points', points: regularPoints },
214
+ { minZoomSpan: 50, minVisiblePoints: 2, maxZoomScale: 40 },
215
+ )
216
+ const { overlay, width, height } = prepareOverlay(
217
+ wrapper,
218
+ '.waveform-chart__overlay--independent',
219
+ )
220
+
221
+ await dispatchWheel(overlay, width, height, -4_000)
222
+ const domain = wrapper.emitted('zoom-change')?.at(-1)?.[0] as [number, number]
223
+ expect(domain[1] - domain[0]).toBeCloseTo(50)
224
+ })
225
+
166
226
  it('retains two endpoint samples while replacing an independent data window', async () => {
167
227
  const sourcePoints = Array.from({ length: 1_000 }, (_, index) => ({
168
228
  x: -5 + (index * 10) / 999,
@@ -69,6 +69,7 @@ describe('waveform rendering selection', () => {
69
69
  autoHysteresis: 20.8,
70
70
  strategy: 'minmax',
71
71
  maxPointsPerPixel: 2.5,
72
+ maxPointCount: 1_000.9,
72
73
  rawPointLimit: 50_000.2,
73
74
  wasmFailureFallback: 'javascript',
74
75
  },
@@ -83,6 +84,7 @@ describe('waveform rendering selection', () => {
83
84
  autoHysteresis: 20,
84
85
  strategy: 'minmax',
85
86
  maxPointsPerPixel: 2.5,
87
+ maxPointCount: 1_000,
86
88
  rawPointLimit: 50_000,
87
89
  wasmFailureFallback: 'javascript',
88
90
  },
@@ -96,6 +98,7 @@ describe('waveform rendering selection', () => {
96
98
  autoHysteresis: -1,
97
99
  strategy: 'invalid',
98
100
  maxPointsPerPixel: Number.POSITIVE_INFINITY,
101
+ maxPointCount: 0,
99
102
  rawPointLimit: 0,
100
103
  wasmFailureFallback: 'fallback',
101
104
  },
@@ -106,7 +106,9 @@ function selectSamplingPlaceholderPoints(
106
106
  const count = end - start
107
107
  if (count <= 0) return []
108
108
  if (options.sampling.strategy === 'none') return points.slice(start, end)
109
- const target = Math.max(1, Math.floor(width * options.sampling.maxPointsPerPixel))
109
+ const target =
110
+ options.sampling.maxPointCount ??
111
+ Math.max(1, Math.floor(width * options.sampling.maxPointsPerPixel))
110
112
  if (count <= target) return points.slice(start, end)
111
113
  if (target === 1) return [points[start]!]
112
114
  return Array.from({ length: target }, (_, index) => {
@@ -11,6 +11,7 @@ export interface ResolvedWaveformSamplingOptions {
11
11
  autoHysteresis: number
12
12
  strategy: WaveformSamplingStrategy
13
13
  maxPointsPerPixel: number
14
+ maxPointCount: number | undefined
14
15
  rawPointLimit: number
15
16
  wasmFailureFallback: 'error' | 'javascript'
16
17
  }
@@ -36,6 +37,7 @@ export const DEFAULT_WAVEFORM_RENDERING_OPTIONS: ResolvedWaveformRenderingOption
36
37
  autoHysteresis: 0,
37
38
  strategy: 'peak',
38
39
  maxPointsPerPixel: 4,
40
+ maxPointCount: undefined,
39
41
  rawPointLimit: 100_000,
40
42
  wasmFailureFallback: 'error',
41
43
  },
@@ -72,6 +74,12 @@ function resolvePositiveNumber(value: unknown, fallback: number): number {
72
74
  return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback
73
75
  }
74
76
 
77
+ function resolveOptionalFiniteInteger(value: unknown, minimum: number): number | undefined {
78
+ return typeof value === 'number' && Number.isFinite(value) && value >= minimum
79
+ ? Math.floor(value)
80
+ : undefined
81
+ }
82
+
75
83
  function resolveNonNegativeNumber(value: number, fallback: number): number {
76
84
  return Number.isFinite(value) && value >= 0 ? value : fallback
77
85
  }
@@ -106,6 +114,7 @@ function resolveSamplingOptions(
106
114
  ? options.strategy
107
115
  : DEFAULT_WAVEFORM_RENDERING_OPTIONS.sampling.strategy,
108
116
  maxPointsPerPixel,
117
+ maxPointCount: resolveOptionalFiniteInteger(options?.maxPointCount, 1),
109
118
  rawPointLimit: resolveFiniteInteger(
110
119
  options?.rawPointLimit,
111
120
  1,
@@ -42,7 +42,10 @@ export const peakPreservingPointSelectionStrategy: RenderablePointSelectionStrat
42
42
 
43
43
  const domainStart = Math.min(domain[0], domain[1])
44
44
  const domainEnd = Math.max(domain[0], domain[1])
45
- const maximumPointCount = Math.max(4, Math.floor(width * options.maxPointsPerPixel))
45
+ const maximumPointCount = Math.max(
46
+ 4,
47
+ options.sampling.maxPointCount ?? Math.floor(width * options.maxPointsPerPixel),
48
+ )
46
49
  const bucketCount = Math.max(1, Math.floor(maximumPointCount / 4))
47
50
  const result: WaveformPoint[] = []
48
51
  const span = domainEnd - domainStart || 1
@@ -119,7 +122,8 @@ export function resolveRenderablePointSelectionStrategy(
119
122
  ): RenderablePointSelectionStrategy {
120
123
  const maximumPointCount = Math.max(
121
124
  4,
122
- Math.floor(request.width * request.options.maxPointsPerPixel),
125
+ request.options.sampling.maxPointCount ??
126
+ Math.floor(request.width * request.options.maxPointsPerPixel),
123
127
  )
124
128
  const shouldUseCompletePoints =
125
129
  !request.options.downsample ||
@@ -90,6 +90,7 @@ export interface WorkerSamplingSeriesRequest {
90
90
  previousSelectedMode?: 'raw' | 'sampled'
91
91
  strategy: WaveformSamplingStrategy
92
92
  maxPointsPerPixel?: number
93
+ maxPointCount?: number
93
94
  rawPointLimit?: number
94
95
  wasmFailureFallback?: 'error' | 'javascript'
95
96
  /** Rendering inputs that affect the cache key, even when only line geometry is overridden. */
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import type { WaveformPoint } from '@/types'
4
+ import type { WorkerSamplingSeriesRequest } from './protocol'
5
+ import { WorkerSamplingRepository } from './repository'
6
+
7
+ const points: WaveformPoint[] = Array.from({ length: 12 }, (_, index) => ({
8
+ x: index,
9
+ y: index === 5 ? 100 : index,
10
+ }))
11
+
12
+ function register(repository: WorkerSamplingRepository, datasetId: string) {
13
+ return repository.handle({
14
+ type: 'register-dataset',
15
+ requestId: 1,
16
+ datasetId,
17
+ revision: 0,
18
+ points,
19
+ })
20
+ }
21
+
22
+ function sampleSeries(datasetId: string): WorkerSamplingSeriesRequest {
23
+ return {
24
+ seriesId: `${datasetId}-series`,
25
+ datasetId,
26
+ revision: 1,
27
+ xDomain: [0, 11],
28
+ plotWidth: 1_000,
29
+ mode: 'auto',
30
+ autoThreshold: 2,
31
+ strategy: 'peak',
32
+ maxPointsPerPixel: 4,
33
+ maxPointCount: 4,
34
+ rawPointLimit: 5,
35
+ }
36
+ }
37
+
38
+ describe('WorkerSamplingRepository sampling targets', () => {
39
+ it('uses a fixed maximum point count instead of pixel density when configured', () => {
40
+ const repository = new WorkerSamplingRepository()
41
+ register(repository, 'fixed-target')
42
+ const response = repository.handle({
43
+ type: 'sample-viewport',
44
+ requestId: 51,
45
+ series: [sampleSeries('fixed-target')],
46
+ })
47
+
48
+ expect(response.type).toBe('sample-viewport-response')
49
+ if (response.type !== 'sample-viewport-response') return
50
+ expect(response.results[0]?.diagnostics.renderedPointCount).toBeLessThanOrEqual(4)
51
+ })
52
+ })
@@ -92,6 +92,9 @@ export function selectedStrategy(strategy: WorkerSamplingSeriesRequest['strategy
92
92
  }
93
93
 
94
94
  export function targetPointCount(request: WorkerSamplingSeriesRequest) {
95
+ if (Number.isFinite(request.maxPointCount) && (request.maxPointCount ?? 0) >= 1) {
96
+ return Math.floor(request.maxPointCount as number)
97
+ }
95
98
  const width = Number.isFinite(request.plotWidth) ? Math.max(0, request.plotWidth) : 0
96
99
  const density = Number.isFinite(request.maxPointsPerPixel)
97
100
  ? Math.max(0, request.maxPointsPerPixel ?? 0)
@@ -179,6 +182,7 @@ export function cacheKey(
179
182
  request.plotWidth,
180
183
  target,
181
184
  request.maxPointsPerPixel,
185
+ request.maxPointCount,
182
186
  request.mode,
183
187
  backend,
184
188
  strategy,
@@ -96,6 +96,8 @@ export interface WaveformSamplingOptions {
96
96
  strategy?: WaveformSamplingStrategy
97
97
  /** Upper bound for rendered points per horizontal CSS pixel. */
98
98
  maxPointsPerPixel?: number
99
+ /** Fixed per-series target point count. When set, this takes precedence over pixel density. */
100
+ maxPointCount?: number
99
101
  /** Visible-point count at which raw rendering should report a diagnostic. */
100
102
  rawPointLimit?: number
101
103
  /** Controls whether a forced WASM failure may temporarily show JavaScript results. */
@@ -5,6 +5,7 @@ import { InputNumber, RadioButton, RadioGroup, Select } from 'ant-design-vue'
5
5
  import { WaveformChart } from '../components'
6
6
  import type {
7
7
  WaveformData,
8
+ WaveformAnnotation,
8
9
  WaveformRenderingOptions,
9
10
  WaveformSamplingDiagnostics,
10
11
  WaveformSamplingMode,
@@ -67,7 +68,9 @@ const data = createDenseData()
67
68
  const mode = ref<WaveformSamplingMode>('auto')
68
69
  const strategy = ref<WaveformSamplingStrategy>('peak')
69
70
  const autoThreshold = ref(1_000)
70
- const maxPointsPerPixel = ref(3)
71
+ const maxPointCount = ref(1_000)
72
+ const minVisiblePoints = ref(2)
73
+ const annotations = ref<WaveformAnnotation[]>([])
71
74
  const diagnostics = reactive<Record<string, WaveformSamplingDiagnostics | undefined>>({})
72
75
 
73
76
  const rendering = computed<WaveformRenderingOptions>(() => ({
@@ -75,7 +78,7 @@ const rendering = computed<WaveformRenderingOptions>(() => ({
75
78
  mode: mode.value,
76
79
  strategy: strategy.value,
77
80
  autoThreshold: autoThreshold.value,
78
- maxPointsPerPixel: maxPointsPerPixel.value,
81
+ maxPointCount: maxPointCount.value,
79
82
  rawPointLimit: 100_000,
80
83
  wasmFailureFallback: 'javascript',
81
84
  },
@@ -126,24 +129,31 @@ function pointCount(value: number | undefined) {
126
129
  />
127
130
  </label>
128
131
  <label>
129
- <span>每像素点数</span>
132
+ <span>最多渲染点</span>
130
133
  <InputNumber
131
- v-model:value="maxPointsPerPixel"
132
- :min="0.25"
133
- :max="16"
134
- :step="0.25"
134
+ v-model:value="maxPointCount"
135
+ :min="1"
136
+ :max="100000"
137
+ :step="100"
135
138
  size="small"
136
139
  />
137
140
  </label>
141
+ <label>
142
+ <span>最少可见点</span>
143
+ <InputNumber v-model:value="minVisiblePoints" :min="2" :max="1000" :step="1" size="small" />
144
+ </label>
138
145
  </section>
139
146
 
140
147
  <section class="sampling-demo__workspace">
141
148
  <div class="sampling-demo__chart">
142
149
  <WaveformChart
143
150
  :data="data"
151
+ v-model:annotations="annotations"
144
152
  :rendering="rendering"
145
153
  :grid="{ rowCount: 1, columnCount: 1, showPagination: false }"
146
154
  :legend="{ position: 'top-right', orientation: 'vertical' }"
155
+ :max-zoom-scale="null"
156
+ :min-visible-points="minVisiblePoints"
147
157
  :title="{ visible: true, text: '多系列采样与缓存诊断', align: 'left' }"
148
158
  x-label="时间(s)"
149
159
  time-unit="s"