waveform-analysis 0.1.74 → 0.1.76

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 (150) hide show
  1. package/README.md +422 -60
  2. package/dist/assets/workerRuntime-BJiRmIZD.js +2446 -0
  3. package/dist/index.cjs +1 -2
  4. package/dist/index.js +1 -7119
  5. package/dist/style.css +1 -1
  6. package/dist/types/animation/WaveformAnimationController.d.ts +30 -0
  7. package/dist/types/animation/core/Animation.d.ts +13 -0
  8. package/dist/types/animation/core/Animator.d.ts +23 -0
  9. package/dist/types/animation/core/easing.d.ts +3 -0
  10. package/dist/types/animation/core/errors.d.ts +4 -0
  11. package/dist/types/animation/core/handle.d.ts +7 -0
  12. package/dist/types/animation/core/owner.d.ts +3 -0
  13. package/dist/types/animation/core/propertyGroups.d.ts +17 -0
  14. package/dist/types/animation/core/task.d.ts +29 -0
  15. package/dist/types/animation/core/types.d.ts +74 -0
  16. package/dist/types/animation/core/validation.d.ts +30 -0
  17. package/dist/types/animation/core/values.d.ts +8 -0
  18. package/dist/types/animation/presets/annotation.d.ts +4 -0
  19. package/dist/types/animation/presets/lineDrawing.d.ts +7 -0
  20. package/dist/types/animation/presets/shared.d.ts +14 -0
  21. package/dist/types/animation/presets/transitions.d.ts +4 -0
  22. package/dist/types/animation/types.d.ts +84 -0
  23. package/dist/types/components/WaveformChart.vue.d.ts +3 -2
  24. package/dist/types/components/animation/annotationPresentation.d.ts +17 -0
  25. package/dist/types/components/animation/chartExportVersion.d.ts +2 -0
  26. package/dist/types/components/animation/config.d.ts +31 -0
  27. package/dist/types/components/animation/context.d.ts +20 -0
  28. package/dist/types/components/animation/geometry.d.ts +10 -0
  29. package/dist/types/components/animation/paginationPresentation.d.ts +13 -0
  30. package/dist/types/components/animation/pathBuffer.d.ts +4 -0
  31. package/dist/types/components/animation/trackPresentation.d.ts +35 -0
  32. package/dist/types/components/animation/useWaveformAnimations.d.ts +27 -0
  33. package/dist/types/components/annotation/WaveformAnnotationContextMenu.vue.d.ts +2 -2
  34. package/dist/types/components/annotation/types.d.ts +7 -0
  35. package/dist/types/components/controls/useChartImageExport.d.ts +2 -0
  36. package/dist/types/components/core/amplitudeLimits.d.ts +6 -0
  37. package/dist/types/components/core/layout.d.ts +6 -9
  38. package/dist/types/components/core/trackLayoutBuilder.d.ts +3 -1
  39. package/dist/types/components/core/types.d.ts +8 -0
  40. package/dist/types/components/core/useWaveformChartController.d.ts +26 -1
  41. package/dist/types/components/core/useWaveformData.d.ts +2 -2
  42. package/dist/types/components/core/useWaveformMeasurements.d.ts +4 -1
  43. package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
  44. package/dist/types/components/core/waveformChartTypes.d.ts +6 -3
  45. package/dist/types/components/core/xAxisTicks.d.ts +2 -0
  46. package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
  47. package/dist/types/components/core/yAxisTicks.d.ts +6 -0
  48. package/dist/types/components/data/types.d.ts +1 -1
  49. package/dist/types/components/interaction/integerZoom.d.ts +3 -3
  50. package/dist/types/components/interaction/stepZoom.d.ts +5 -0
  51. package/dist/types/components/interaction/useWaveformViewport.d.ts +0 -3
  52. package/dist/types/components/interaction/useWaveformZoom.d.ts +0 -2
  53. package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
  54. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
  55. package/dist/types/components/rendering/updateAxis.d.ts +5 -0
  56. package/dist/types/components/rendering/updateGrid.d.ts +3 -0
  57. package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
  58. package/dist/types/components/utils/assignElement.d.ts +2 -0
  59. package/dist/types/core/data.d.ts +1 -1
  60. package/dist/types/demo/DemoChartHost.vue.d.ts +1 -1
  61. package/dist/types/demo/remoteWaveformLoader.d.ts +23 -0
  62. package/dist/types/demo/types.d.ts +11 -2
  63. package/dist/types/demo/useRemoteWaveformWindow.d.ts +22 -0
  64. package/dist/types/index.d.ts +10 -1
  65. package/dist/types/types/chart.d.ts +8 -1
  66. package/dist/types/types/index.d.ts +1 -1
  67. package/dist/types/utils/formatters.d.ts +4 -4
  68. package/dist/types/utils/xAxisNumber.d.ts +2 -0
  69. package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
  70. package/package.json +5 -2
  71. package/src/App.vue +34 -100
  72. package/src/DemoRouterApp.vue +1 -0
  73. package/src/animation/WaveformAnimationController.ts +328 -0
  74. package/src/animation/core/Animation.ts +37 -0
  75. package/src/animation/core/Animator.ts +200 -0
  76. package/src/animation/core/easing.ts +74 -0
  77. package/src/animation/core/errors.ts +24 -0
  78. package/src/animation/core/handle.ts +34 -0
  79. package/src/animation/core/owner.ts +9 -0
  80. package/src/animation/core/propertyGroups.ts +143 -0
  81. package/src/animation/core/task.ts +153 -0
  82. package/src/animation/core/types.ts +81 -0
  83. package/src/animation/core/validation.ts +173 -0
  84. package/src/animation/core/values.ts +38 -0
  85. package/src/animation/presets/annotation.ts +66 -0
  86. package/src/animation/presets/lineDrawing.ts +51 -0
  87. package/src/animation/presets/shared.ts +58 -0
  88. package/src/animation/presets/transitions.ts +77 -0
  89. package/src/animation/types.ts +81 -0
  90. package/src/components/WaveformChart.vue +1 -0
  91. package/src/components/WaveformChartView.vue +13 -6
  92. package/src/components/animation/annotationPresentation.ts +145 -0
  93. package/src/components/animation/chartExportVersion.ts +5 -0
  94. package/src/components/animation/config.ts +87 -0
  95. package/src/components/animation/context.ts +56 -0
  96. package/src/components/animation/geometry.ts +148 -0
  97. package/src/components/animation/paginationPresentation.ts +114 -0
  98. package/src/components/animation/pathBuffer.ts +61 -0
  99. package/src/components/animation/trackPresentation.ts +172 -0
  100. package/src/components/animation/useWaveformAnimations.ts +420 -0
  101. package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
  102. package/src/components/annotation/types.ts +3 -0
  103. package/src/components/controls/useChartCommands.ts +12 -13
  104. package/src/components/controls/useChartImageExport.ts +4 -0
  105. package/src/components/core/amplitudeLimits.ts +44 -0
  106. package/src/components/core/layout.ts +22 -34
  107. package/src/components/core/trackLayoutBuilder.ts +21 -23
  108. package/src/components/core/types.ts +4 -0
  109. package/src/components/core/useWaveformChartController.ts +27 -23
  110. package/src/components/core/useWaveformChartLifecycle.ts +11 -2
  111. package/src/components/core/useWaveformData.ts +11 -5
  112. package/src/components/core/useWaveformLayout.ts +9 -4
  113. package/src/components/core/useWaveformMeasurements.ts +3 -0
  114. package/src/components/core/useWaveformRenderSampling.ts +23 -1
  115. package/src/components/core/useWaveformYAxisLayout.ts +2 -0
  116. package/src/components/core/waveformChartTypes.ts +6 -2
  117. package/src/components/core/xAxisTicks.ts +69 -0
  118. package/src/components/core/yAxisLayoutMetrics.ts +4 -0
  119. package/src/components/core/yAxisTicks.ts +39 -0
  120. package/src/components/data/types.ts +2 -0
  121. package/src/components/interaction/integerZoom.ts +31 -12
  122. package/src/components/interaction/remoteViewport.ts +26 -4
  123. package/src/components/interaction/stepZoom.ts +33 -0
  124. package/src/components/interaction/useWaveformViewport.ts +3 -26
  125. package/src/components/interaction/useWaveformZoom.ts +50 -157
  126. package/src/components/interaction/zoomConstraints.ts +5 -5
  127. package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
  128. package/src/components/rendering/WaveformTrack.vue +6 -1
  129. package/src/components/rendering/WaveformTrackAxes.vue +64 -51
  130. package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
  131. package/src/components/rendering/updateAxis.ts +57 -0
  132. package/src/components/rendering/updateGrid.ts +42 -0
  133. package/src/components/rendering/yAxisTickVisibility.ts +45 -18
  134. package/src/components/utils/assignElement.ts +7 -0
  135. package/src/core/data.ts +5 -2
  136. package/src/data/simulatedWaveforms.ts +0 -10
  137. package/src/demo/DemoChartHost.vue +49 -4
  138. package/src/demo/remoteWaveformLoader.ts +114 -0
  139. package/src/demo/types.ts +11 -1
  140. package/src/demo/useRemoteWaveformWindow.ts +229 -0
  141. package/src/index.ts +54 -0
  142. package/src/router.ts +1 -0
  143. package/src/types/chart.ts +10 -1
  144. package/src/types/index.ts +2 -0
  145. package/src/utils/formatters.ts +10 -5
  146. package/src/utils/xAxisNumber.ts +36 -0
  147. package/src/views/AnimationDemo.vue +129 -0
  148. package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
  149. package/dist/types/data/sx2Channel.d.ts +0 -10
  150. package/src/data/sx2Channel.ts +0 -315
@@ -1,4 +1,8 @@
1
- import type { WaveformOverlayMode } from '../../types'
1
+ import type {
2
+ WaveformAmplitudeLimit,
3
+ WaveformAmplitudeLimits,
4
+ WaveformOverlayMode,
5
+ } from '../../types'
2
6
  import { formatScientificAxisLabel, formatScientificAxisExponent, paddedDomain } from '../../utils'
3
7
  import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
4
8
  import { MAX_MULTI_Y_AXIS_COUNT } from './constants'
@@ -8,7 +12,9 @@ import {
8
12
  resolveTrackFixedYDomain,
9
13
  type WaveformYDomain,
10
14
  } from './yDomain'
11
- import { resolveYAxisTicksECharts } from './niceScale'
15
+ import { resolveYAxisTicks } from './yAxisTicks'
16
+ export { resolveYAxisTicks, type ResolvedYAxisTicks } from './yAxisTicks'
17
+ import { applyAmplitudeLimits, mergeAmplitudeLimits } from './amplitudeLimits'
12
18
  import { applyYAxisPadding, withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
13
19
  import { resolveYAxisLabelBandWidth } from './yAxisLabelStyle'
14
20
 
@@ -28,36 +34,6 @@ export function resolveYAxisTickCount(_plotHeight: number, splitNumber?: number)
28
34
  return 5
29
35
  }
30
36
 
31
- export interface ResolvedYAxisTicks {
32
- domain: [number, number]
33
- values: number[]
34
- }
35
-
36
- export function resolveYAxisTicks(
37
- domain: [number, number],
38
- tickCount = 5,
39
- nice = true,
40
- ): ResolvedYAxisTicks {
41
- if (!nice) {
42
- // 如果不需要 nice,使用简单的线性分割
43
- const effectiveTickCount =
44
- typeof tickCount === 'number' && Number.isFinite(tickCount)
45
- ? Math.max(2, Math.floor(tickCount))
46
- : 5
47
- const [axisStart, axisEnd] = domain
48
- return {
49
- domain,
50
- values: Array.from(
51
- { length: effectiveTickCount },
52
- (_, index) => axisStart + ((axisEnd - axisStart) * index) / (effectiveTickCount - 1),
53
- ),
54
- }
55
- }
56
-
57
- // 使用 ECharts 兼容的 1/2/5 系列算法
58
- return resolveYAxisTicksECharts(domain, tickCount)
59
- }
60
-
61
37
  export function formatYAxisTickLabel(
62
38
  value: number,
63
39
  domain: [number, number],
@@ -101,6 +77,7 @@ export interface YAxisSeriesGroup {
101
77
  seriesList: DisplaySeries[]
102
78
  domain: [number, number]
103
79
  fixed: boolean
80
+ amplitudeLimit?: WaveformAmplitudeLimit
104
81
  maximumTick?: number
105
82
  minimumTick?: number
106
83
  }
@@ -191,6 +168,7 @@ export function resolveRenderedYAxisSeriesGroups(
191
168
  yDomains?: Record<string, WaveformYDomain>,
192
169
  viewportYDomains?: Record<string, [number, number]>,
193
170
  padding?: YAxisPaddingOptions | number,
171
+ amplitudeLimits?: WaveformAmplitudeLimits,
194
172
  ): YAxisSeriesGroup[] {
195
173
  const viewportYDomain = viewportYDomains?.[track.id]
196
174
  const axisTrack =
@@ -198,7 +176,13 @@ export function resolveRenderedYAxisSeriesGroups(
198
176
  ? track
199
177
  : { ...track, visibleSeries: track.series }
200
178
  return resolveYAxisSeriesGroups(axisTrack, overlayMode, yDomain, yDomains).map((group) => {
201
- const padded = applyYAxisPadding(group, viewportYDomain, padding)
179
+ const limit = mergeAmplitudeLimits(group.seriesList, amplitudeLimits)
180
+ const automatic = limit ? buildYAxisSeriesGroups(axisTrack, overlayMode)[group.index]! : group
181
+ const padded = applyYAxisPadding(automatic, limit ? undefined : viewportYDomain, padding)
182
+ if (limit) {
183
+ padded.domain = applyAmplitudeLimits(padded.domain, limit, automatic.domain)
184
+ padded.amplitudeLimit = limit
185
+ }
202
186
  return track.visibleSeries.length
203
187
  ? padded
204
188
  : { ...padded, maximumTick: undefined, minimumTick: undefined }
@@ -215,10 +199,11 @@ export function axisTextMetrics(
215
199
  measureTextWidth?: (text: string) => number,
216
200
  maximumTick?: number,
217
201
  minimumTick?: number,
202
+ amplitudeLimit?: WaveformAmplitudeLimit,
218
203
  ): { tickTextWidth: number; titleTextWidth: number } {
219
204
  const resolvedTicks = tickValues
220
205
  ? { domain, values: tickValues }
221
- : resolveYAxisTicks(domain, tickCount, nice)
206
+ : resolveYAxisTicks(domain, tickCount, nice, amplitudeLimit)
222
207
  const inRange = (value?: number) =>
223
208
  value !== undefined && value >= resolvedTicks.domain[0] && value <= resolvedTicks.domain[1]
224
209
  ? value
@@ -320,6 +305,7 @@ export function buildYAxisSlots(
320
305
  padding?: YAxisPaddingOptions | number,
321
306
  showUnits = true,
322
307
  measureTextWidth?: (text: string) => number,
308
+ amplitudeLimits?: WaveformAmplitudeLimits,
323
309
  ): { slots: YAxisSlot[]; clearance: { left: number; right: number } } {
324
310
  const labelBandWidth = resolveYAxisLabelBandWidth(
325
311
  typeof padding === 'object' ? padding.labelTextStyle : undefined,
@@ -335,6 +321,7 @@ export function buildYAxisSlots(
335
321
  yDomains,
336
322
  viewportYDomains,
337
323
  padding,
324
+ amplitudeLimits,
338
325
  ).forEach((group) => {
339
326
  const sideIndex = sideIndexes[group.side]++
340
327
  const key = `${group.side}:${sideIndex}`
@@ -348,6 +335,7 @@ export function buildYAxisSlots(
348
335
  measureTextWidth,
349
336
  group.maximumTick,
350
337
  group.minimumTick,
338
+ group.amplitudeLimit,
351
339
  )
352
340
  widths.set(key, Math.max(widths.get(key) ?? Y_AXIS_CHARACTER_WIDTH, tickTextWidth))
353
341
  titleWidths.set(key, Math.max(titleWidths.get(key) ?? Y_AXIS_CHARACTER_WIDTH, titleTextWidth))
@@ -1,18 +1,12 @@
1
+ import { generateWaveformPath } from '../animation/geometry'
1
2
  import { withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
2
3
  import { selectSeriesRenderSourcePoints } from '../../core/rendering'
3
4
  import { pointSourceFromPoints } from '../../core/waveformPointSource'
4
- import {
5
- curveStep,
6
- curveStepAfter,
7
- curveStepBefore,
8
- line,
9
- scaleLinear,
10
- zoomIdentity,
11
- type ZoomTransform,
12
- } from 'd3'
5
+ import { scaleLinear, zoomIdentity, type ZoomTransform } from 'd3'
13
6
 
14
7
  import { type ResolvedWaveformRenderingOptions } from '../../core/rendering'
15
8
  import type {
9
+ WaveformAmplitudeLimits,
16
10
  WaveformDisplayMode,
17
11
  WaveformOverlayMode,
18
12
  WaveformPoint,
@@ -41,7 +35,6 @@ import {
41
35
  alignIntegerZoomDomain,
42
36
  normalizeIntegerZoomDomain,
43
37
  integerZoomTicks,
44
- formatIntegerZoomLabel,
45
38
  } from '../interaction/integerZoom'
46
39
  import {
47
40
  Y_AXIS_LABEL_GAP,
@@ -66,6 +59,7 @@ export interface BuildTrackLayoutsOptions {
66
59
  initialXDomains?: Record<string, [number, number]>
67
60
  xDomainStrategy?: WaveformXDomainStrategy
68
61
  integerZoom?: boolean
62
+ amplitudeLimits?: WaveformAmplitudeLimits
69
63
  fixedYDomain?: [number, number]
70
64
  fixedYDomains?: Record<string, [number, number]>
71
65
  yDomains?: Record<string, [number, number]>
@@ -86,6 +80,7 @@ export interface BuildTrackLayoutsOptions {
86
80
  useNonEmptyBottomTracks?: boolean
87
81
  compactYAxisLayout?: boolean
88
82
  showAxisUnits?: boolean
83
+ domainTimeUnit?: 's' | 'ms'
89
84
  }
90
85
 
91
86
  function resolveIndependentXDomain(
@@ -103,7 +98,7 @@ function resolveIndependentXDomain(
103
98
  explicitDomain
104
99
  ? applyXDomainStrategy(explicitDomain, strategy, true)
105
100
  : applyXDomainStrategy(track.xDomain, strategy),
106
- options,
101
+ { integerZoom: options.integerZoom, timeUnit: options.domainTimeUnit ?? options.timeUnit },
107
102
  )
108
103
  }
109
104
 
@@ -148,13 +143,19 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
148
143
  options.fixedYDomains,
149
144
  options.yDomains,
150
145
  options.yAxisPadding ?? options.yAxisUpperPaddingRatio,
146
+ options.amplitudeLimits,
151
147
  )
152
148
  const sideIndexes = { left: 0, right: 0 }
153
149
  const sideOffsets = { left: 0, right: 0 }
154
150
  const yAxes: WaveformYAxisLayout[] = yAxisGroups.map((group) => {
155
151
  const sideIndex = sideIndexes[group.side]++
156
152
  const tickCount = resolveYAxisTickCount(cell.plotHeight, options.yAxisSplitNumber)
157
- const resolvedTicks = resolveYAxisTicks(group.domain, tickCount, options.yAxisNice !== false)
153
+ const resolvedTicks = resolveYAxisTicks(
154
+ group.domain,
155
+ tickCount,
156
+ options.yAxisNice !== false,
157
+ group.amplitudeLimit,
158
+ )
158
159
  const scale = scaleLinear(resolvedTicks.domain, [cell.plotHeight, 0])
159
160
  const majorTicks = resolvedTicks.values
160
161
  const maximumTick =
@@ -227,8 +228,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
227
228
  Math.max(2, Math.floor(cell.width / 100)),
228
229
  options,
229
230
  )
230
- const xAxisLabelFormatter =
231
- options.xAxisLabelFormatter ?? (options.integerZoom ? formatIntegerZoomLabel : undefined)
231
+ const xAxisLabelFormatter = options.xAxisLabelFormatter
232
232
  const yMajorTicks =
233
233
  yAxes[0]?.majorTicks ??
234
234
  (isEmpty
@@ -264,17 +264,15 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
264
264
  hasErrorPoints: trackSeries.hasErrorPoints,
265
265
  },
266
266
  )
267
- const pathGenerator = line<WaveformPoint>()
268
- .x((point) => xScale(point.x))
269
- .y((point) => seriesYScale(point.y))
270
- if (trackSeries.lineType === 'step-start') pathGenerator.curve(curveStepBefore)
271
- if (trackSeries.lineType === 'step-middle') pathGenerator.curve(curveStep)
272
- if (trackSeries.lineType === 'step-end' || trackSeries.lineType === 'step-after') {
273
- pathGenerator.curve(curveStepAfter)
274
- }
275
267
  return {
276
268
  series: trackSeries,
277
- path: renderPoints.linePoints.length ? pathGenerator(renderPoints.linePoints) : null,
269
+ path: generateWaveformPath(
270
+ renderPoints.linePoints,
271
+ trackSeries.lineType,
272
+ xScale,
273
+ seriesYScale,
274
+ ),
275
+ lineRenderPoints: renderPoints.linePoints,
278
276
  pointRenderPoints: renderPoints.pointRenderPoints,
279
277
  errorBarRenderPoints: renderPoints.errorBarRenderPoints,
280
278
  yScale: seriesYScale,
@@ -47,6 +47,10 @@ export interface DisplayTrack {
47
47
  export interface TrackSeriesPath {
48
48
  series: DisplaySeries
49
49
  path: string | null
50
+ lineRenderPoints?: WaveformPoint[]
51
+ presentationKey?: string
52
+ opacity?: number
53
+ animationClip?: { id: string; x: number; width: number }
50
54
  pointRenderPoints: WaveformPoint[]
51
55
  errorBarRenderPoints: WaveformPoint[]
52
56
  yScale: ScaleLinear<number, number>
@@ -1,4 +1,6 @@
1
- import { chartExportOptions } from '../controls/chartExportOptions'
1
+ import { assignElement } from '../utils/assignElement'
2
+ import { chartExportVersion } from '../animation/chartExportVersion'
3
+ import { useWaveformAnimations } from '../animation/useWaveformAnimations'
2
4
  import { useChartImageExport } from '../controls/useChartImageExport'
3
5
  import { useControlMode } from '../controls/useControlMode'
4
6
  import { useChartCommands } from '../controls/useChartCommands'
@@ -16,9 +18,7 @@ import {
16
18
  watch,
17
19
  watchEffect,
18
20
  type ComponentPublicInstance,
19
- type Ref,
20
21
  } from 'vue'
21
-
22
22
  import { useWaveformInstanceId } from '../../utils/waveformId'
23
23
  import { useWaveformAnnotationInteraction, type AnnotationSeriesCandidate } from '../annotation'
24
24
  import { useWaveformChartAnnotations } from '../annotation/useWaveformChartAnnotations'
@@ -32,6 +32,7 @@ import { resolveCompactPaginationBand } from './pagination'
32
32
  import type { WaveformHoverState } from './types'
33
33
  import { useWaveformChartLifecycle } from './useWaveformChartLifecycle'
34
34
  import { usePreparedWaveformSeries } from './useWaveformData'
35
+ import { remotePanBoundary } from '../interaction/remoteViewport'
35
36
  import { useWaveformLayout } from './useWaveformLayout'
36
37
  import { useWaveformPresentation } from './useWaveformPresentation'
37
38
  import { usePlotLabelLayout } from './usePlotLabelLayout'
@@ -41,14 +42,6 @@ import type {
41
42
  ViewportSelectionState,
42
43
  WaveformChartEmit,
43
44
  } from './waveformChartTypes'
44
-
45
- function assignElement<T extends Element>(
46
- target: Ref<T | undefined>,
47
- element: Element | ComponentPublicInstance | null,
48
- ) {
49
- target.value = element instanceof Element ? (element as T) : undefined
50
- }
51
-
52
45
  export function useWaveformChartController(
53
46
  inputProps: ResolvedWaveformChartProps,
54
47
  emit: WaveformChartEmit,
@@ -90,7 +83,7 @@ export function useWaveformChartController(
90
83
  const hoverThrottle = useAnimationFrameThrottle()
91
84
 
92
85
  watch(
93
- [() => props.yDomain, () => props.yDomains],
86
+ [() => props.yDomain, () => props.yDomains, () => props.amplitudeLimits],
94
87
  () => {
95
88
  sharedYDomains.value = {}
96
89
  independentYDomains.value = {}
@@ -110,6 +103,7 @@ export function useWaveformChartController(
110
103
  handleBeforeDataReferenceChange()
111
104
  },
112
105
  () => handleDataReferenceChange(),
106
+ () => Boolean(remotePanBoundary(props)),
113
107
  )
114
108
 
115
109
  const presentation = useWaveformPresentation({
@@ -186,7 +180,17 @@ export function useWaveformChartController(
186
180
  renderingOptions,
187
181
  linePointOverrides,
188
182
  })
189
-
183
+ const animations = useWaveformAnimations({
184
+ props,
185
+ currentPage,
186
+ containerSize: computed(() => [observedWidth.value, observedHeight.value]),
187
+ layoutReady: computed(() => observedWidth.value > 0 && observedHeight.value > 0),
188
+ tracks: trackLayouts,
189
+ preparedSeries,
190
+ innerWidth,
191
+ innerHeight: presentation.innerHeight,
192
+ sampling,
193
+ })
190
194
  const paginationVisible = computed(
191
195
  () => gridOptions.value.showPagination && pageCount.value > 1 && !isCleanView.value,
192
196
  )
@@ -261,8 +265,6 @@ export function useWaveformChartController(
261
265
  isPresentationMode,
262
266
  editorSeriesOptions,
263
267
  resolveInitialTrackDomain,
264
- canZoomTrack: zoom.canZoomTrack,
265
- canZoomSharedTracks: zoom.canZoomSharedTracks,
266
268
  configureZoom: zoom.configureZoom,
267
269
  cancelPendingZoom: zoom.cancelPendingZoom,
268
270
  clearHover: () => hover.clearHover(),
@@ -389,10 +391,8 @@ export function useWaveformChartController(
389
391
  height: chartHeight.value,
390
392
  titleHeight: titleAreaHeight.value,
391
393
  }),
392
- version: () => [
393
- props.data,
394
- JSON.stringify({
395
- ...chartExportOptions(props),
394
+ version: () =>
395
+ chartExportVersion(props, {
396
396
  hidden: [...hiddenSeriesIdSet.value],
397
397
  width: chartWidth.value,
398
398
  height: chartHeight.value,
@@ -404,7 +404,8 @@ export function useWaveformChartController(
404
404
  independentY: independentYDomains.value,
405
405
  boundaries: boundaries.value,
406
406
  }),
407
- ],
407
+ prepare: animations.prepareExport,
408
+ release: animations.releaseExport,
408
409
  ready: sampling.ready,
409
410
  })
410
411
 
@@ -418,7 +419,11 @@ export function useWaveformChartController(
418
419
  ...annotations,
419
420
  ...viewport,
420
421
  ...hover,
421
- ...commands,
422
+ ...animations.wrap(commands),
423
+ presentationTracks: animations.presentationTracks,
424
+ presentationAnnotations: animations.presentationAnnotations,
425
+ paginationOpacity: animations.paginationOpacity,
426
+ stabilizeAnimations: animations.stabilizeAnimations,
422
427
  ...imageExport,
423
428
  margin,
424
429
  getPageSize,
@@ -431,7 +436,7 @@ export function useWaveformChartController(
431
436
  contextMenu: annotationInteraction.contextMenu,
432
437
  editorSeriesOptions,
433
438
  clipPathId,
434
- goToPage: lifecycle.goToPage,
439
+ goToPage: animations.wrapPagination(lifecycle.goToPage, pageCount),
435
440
  setContainer: (element: Element | ComponentPublicInstance | null) =>
436
441
  assignElement(container, element),
437
442
  setSvgElement: (element: Element | ComponentPublicInstance | null) =>
@@ -442,5 +447,4 @@ export function useWaveformChartController(
442
447
  assignElement(sharedOverlayElement, element),
443
448
  })
444
449
  }
445
-
446
450
  export type WaveformChartController = ReturnType<typeof useWaveformChartController>
@@ -16,6 +16,7 @@ import type { NormalizedWaveformGridOptions } from './grid'
16
16
  import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
17
17
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
18
18
  import { isEditableTarget } from '../interaction/interactionTarget'
19
+ import { remotePanBoundary } from '../interaction/remoteViewport'
19
20
 
20
21
  export interface LifecycleContext {
21
22
  preserveFullViewport?: () => boolean
@@ -64,7 +65,6 @@ export interface LifecycleContext {
64
65
  }
65
66
 
66
67
  export function useWaveformChartLifecycle(context: LifecycleContext) {
67
- useWaveformMeasurements(context)
68
68
  const {
69
69
  props,
70
70
  emit,
@@ -135,6 +135,15 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
135
135
  const { handleBeforeDataReferenceChange, handleDataReferenceChange } =
136
136
  useWaveformViewportRestore(context)
137
137
 
138
+ useWaveformMeasurements(context, {
139
+ beforeResize: () => {
140
+ if (remotePanBoundary(props)) handleBeforeDataReferenceChange()
141
+ },
142
+ afterResize: () => {
143
+ if (remotePanBoundary(props)) handleDataReferenceChange()
144
+ },
145
+ })
146
+
138
147
  watch(
139
148
  [
140
149
  innerWidth,
@@ -163,7 +172,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
163
172
  )
164
173
 
165
174
  watch(
166
- () => [props.displayMode, props.integerZoom, props.integerZoom ? props.timeUnit : undefined],
175
+ () => [props.displayMode, props.integerZoom],
167
176
  () => {
168
177
  const previousPage = currentPage.value
169
178
  currentPage.value = 1
@@ -31,8 +31,11 @@ export interface PreparedWaveformSeries {
31
31
  hasErrorPoints: boolean
32
32
  }
33
33
 
34
- export function prepareWaveformSeries(data: WaveformData): PreparedWaveformSeries[] {
35
- return normalizeWaveformSeriesSources(data).map(({ source, ...series }) => {
34
+ export function prepareWaveformSeries(
35
+ data: WaveformData,
36
+ retainEmpty = false,
37
+ ): PreparedWaveformSeries[] {
38
+ return normalizeWaveformSeriesSources(data, retainEmpty).map(({ source, ...series }) => {
36
39
  const sourceMetrics = source.metrics(series.errorBar.visible)
37
40
  return {
38
41
  ...series,
@@ -61,11 +64,14 @@ export function usePreparedWaveformSeries(
61
64
  data: () => WaveformData,
62
65
  onBeforeDataChange: () => void,
63
66
  onDataChange: () => void,
67
+ retainEmpty: () => boolean = () => false,
64
68
  ) {
65
- const preparedSeries = shallowRef<PreparedWaveformSeries[]>(prepareWaveformSeries(data()))
66
- watch(data, (nextData) => {
69
+ const preparedSeries = shallowRef<PreparedWaveformSeries[]>(
70
+ prepareWaveformSeries(data(), retainEmpty()),
71
+ )
72
+ watch([data, retainEmpty], ([nextData, preserveEmpty]) => {
67
73
  onBeforeDataChange()
68
- preparedSeries.value = prepareWaveformSeries(nextData)
74
+ preparedSeries.value = prepareWaveformSeries(nextData, preserveEmpty)
69
75
  onDataChange()
70
76
  })
71
77
  return preparedSeries
@@ -52,6 +52,9 @@ export function useWaveformLayout(context: LayoutContext) {
52
52
  annotationInteraction,
53
53
  linePointOverrides,
54
54
  } = context
55
+ // Display-unit switches must not change the base used by existing D3 transforms.
56
+ const domainTimeUnit = props.timeUnit
57
+ const domainOptions = () => ({ integerZoom: props.integerZoom, timeUnit: domainTimeUnit })
55
58
  const chartSeries = computed<DisplaySeries[]>(() =>
56
59
  preparedSeries.value.map((series, index): DisplaySeries => ({
57
60
  ...series,
@@ -174,12 +177,12 @@ export function useWaveformLayout(context: LayoutContext) {
174
177
  props.xDomainStrategy,
175
178
  true,
176
179
  ),
177
- props,
180
+ domainOptions(),
178
181
  )
179
182
  }
180
183
  return alignIntegerZoomDomain(
181
184
  applyXDomainStrategy(sharedXDomain.value, props.xDomainStrategy),
182
- props,
185
+ domainOptions(),
183
186
  )
184
187
  })
185
188
  const resolveOriginalTrackDomain = (track: TrackLayout): [number, number] => {
@@ -201,7 +204,7 @@ export function useWaveformLayout(context: LayoutContext) {
201
204
  props.xDomainStrategy,
202
205
  true,
203
206
  ),
204
- props,
207
+ domainOptions(),
205
208
  )
206
209
  }
207
210
  return alignIntegerZoomDomain(
@@ -209,7 +212,7 @@ export function useWaveformLayout(context: LayoutContext) {
209
212
  paddedDomain(track.seriesList.flatMap((series) => series.xDomain)),
210
213
  props.xDomainStrategy,
211
214
  ),
212
- props,
215
+ domainOptions(),
213
216
  )
214
217
  }
215
218
  const initialXDomain = computed(
@@ -261,10 +264,12 @@ export function useWaveformLayout(context: LayoutContext) {
261
264
  effectiveXDomains: context.boundaries?.value,
262
265
  xDomainStrategy: props.xDomainStrategy,
263
266
  integerZoom: props.integerZoom,
267
+ amplitudeLimits: props.amplitudeLimits,
264
268
  fixedYDomain: props.yDomain,
265
269
  fixedYDomains: props.yDomains,
266
270
  yDomains: viewportYDomains.value,
267
271
  timeUnit: props.timeUnit,
272
+ domainTimeUnit,
268
273
  xAxisLabelFormatter: props.axes?.x?.labelFormatter,
269
274
  yAxisSplitNumber: props.axes?.y?.splitNumber,
270
275
  yAxisNice: props.axes?.y?.nice,
@@ -15,6 +15,7 @@ export function useWaveformMeasurements(
15
15
  | 'titleAreaReserved'
16
16
  | 'titleMeasureStyle'
17
17
  >,
18
+ resizeHooks: { beforeResize?: () => void; afterResize?: () => void } = {},
18
19
  ) {
19
20
  const {
20
21
  container,
@@ -53,8 +54,10 @@ export function useWaveformMeasurements(
53
54
  onMounted(() => {
54
55
  if (!container.value) return
55
56
  resizeObserver.value = new ResizeObserver(([entry]) => {
57
+ resizeHooks.beforeResize?.()
56
58
  observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
57
59
  observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
60
+ resizeHooks.afterResize?.()
58
61
  void nextTick(measureTitle)
59
62
  })
60
63
  resizeObserver.value.observe(container.value)
@@ -103,6 +103,7 @@ function errorPayload(
103
103
  }
104
104
 
105
105
  export function useWaveformRenderSampling(context: SamplingContext) {
106
+ const resultState = shallowRef<'success' | 'fallback' | 'error'>('success')
106
107
  const settledSignature = shallowRef<string>()
107
108
  const dataEpoch = shallowRef(0)
108
109
  const session = createWaveformSamplingSession()
@@ -292,10 +293,14 @@ export function useWaveformRenderSampling(context: SamplingContext) {
292
293
  requestId: ++session.requestId,
293
294
  series: batch,
294
295
  })
295
- if (result.type !== 'sample-viewport-response') return
296
+ if (result.type !== 'sample-viewport-response') {
297
+ if (samplingScheduler.isCurrent(token)) resultState.value = 'error'
298
+ return
299
+ }
296
300
  response = result
297
301
  } catch (error) {
298
302
  if (!samplingScheduler.isCurrent(token)) return
303
+ resultState.value = 'error'
299
304
  emitErrorOnce(
300
305
  errorPayload(
301
306
  error instanceof Error ? error.message : String(error),
@@ -310,8 +315,18 @@ export function useWaveformRenderSampling(context: SamplingContext) {
310
315
  return
311
316
  }
312
317
  if (!samplingScheduler.isCurrent(token)) return
318
+ if (response.workerError || client.workerFailureReason) resultState.value = 'fallback'
313
319
  const sourceById = new Map(sampledTargets.map((target) => [target.series.id, target.source]))
320
+ if (
321
+ sampledTargets.some(
322
+ (target) =>
323
+ !response.results.some((result) => result.seriesId === target.series.id && result.output),
324
+ )
325
+ )
326
+ resultState.value = 'error'
314
327
  response.results.forEach((result) => {
328
+ if (result.status !== 'ok' && resultState.value !== 'error')
329
+ resultState.value = result.output ? 'fallback' : 'error'
315
330
  const source = sourceById.get(result.seriesId)
316
331
  const selected = source ? sourcePoints(result, source) : undefined
317
332
  if (selected && (result.status === 'ok' || result.status === 'wasm-unavailable')) {
@@ -369,6 +384,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
369
384
  const scheduleSampling = (hysteresis: boolean) => {
370
385
  settledSignature.value = undefined
371
386
  const signature = targetSignature.value
387
+ resultState.value = 'success'
372
388
  samplingScheduler.schedule(async (token) => {
373
389
  try {
374
390
  await runSampling(token, hysteresis)
@@ -410,6 +426,12 @@ export function useWaveformRenderSampling(context: SamplingContext) {
410
426
  onScopeDispose(() => session.dispose())
411
427
 
412
428
  return {
429
+ requestSignature: targetSignature,
430
+ state: computed(() =>
431
+ settledSignature.value === targetSignature.value && !session.autoModeSettleTimer
432
+ ? resultState.value
433
+ : 'pending',
434
+ ),
413
435
  linePointOverrides: context.linePointOverrides,
414
436
  ready: () => settledSignature.value === targetSignature.value && !session.autoModeSettleTimer,
415
437
  }
@@ -49,6 +49,7 @@ export function useWaveformYAxisLayout(context: YAxisLayoutContext) {
49
49
  props.axes?.y,
50
50
  showUnits.value,
51
51
  isEdgeCompact.value ? measureYAxisTextWidth : undefined,
52
+ props.amplitudeLimits,
52
53
  ),
53
54
  )
54
55
  const hasYAxisLabels = computed(() =>
@@ -84,6 +85,7 @@ export function useWaveformYAxisLayout(context: YAxisLayoutContext) {
84
85
  props.axes?.y,
85
86
  showUnits.value,
86
87
  isEdgeCompact.value ? measureYAxisTextWidth : undefined,
88
+ props.amplitudeLimits,
87
89
  ),
88
90
  )
89
91
  const resolvedChartLeftMargin = computed(() =>
@@ -1,5 +1,7 @@
1
+ import type { WaveformAnimationProps } from '../../animation/types'
1
2
  import type { WaveformToolbarOptions, WaveformControlState } from '../../types/controls'
2
3
  import type {
4
+ WaveformAmplitudeLimits,
3
5
  WaveformAnnotation,
4
6
  WaveformAxesOptions,
5
7
  WaveformData,
@@ -26,7 +28,7 @@ import type {
26
28
  } from '../data/types'
27
29
  import type { WaveformGridOptions } from './grid'
28
30
 
29
- export interface WaveformChartProps {
31
+ export interface WaveformChartProps extends WaveformAnimationProps {
30
32
  toolbar?: boolean | WaveformToolbarOptions
31
33
  data: WaveformData
32
34
  displayMode?: WaveformDisplayMode
@@ -42,7 +44,7 @@ export interface WaveformChartProps {
42
44
  lineColor?: string
43
45
  showTooltip?: boolean
44
46
  zoomable?: boolean
45
- /** Snap viewport endpoints outwards to integer X-axis display units. */
47
+ /** Prefer integer display-unit endpoints; preserve any window narrower than one unit. */
46
48
  integerZoom?: boolean
47
49
  pannable?: boolean
48
50
  /** Global record bounds in seconds; enables horizontal panning beyond loaded data. */
@@ -53,6 +55,8 @@ export interface WaveformChartProps {
53
55
  initialXDomain?: [number, number]
54
56
  initialXDomains?: Record<string, [number, number]>
55
57
  xDomainStrategy?: WaveformXDomainStrategy
58
+ /** Strict bounds per normalized series ID; active bounds override Y viewports. */
59
+ amplitudeLimits?: WaveformAmplitudeLimits
56
60
  yDomain?: [number, number]
57
61
  yDomains?: Record<string, [number, number]>
58
62
  timeUnit?: 's' | 'ms'