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,23 +1,13 @@
1
- import {
2
- scaleLinear,
3
- select,
4
- zoom,
5
- zoomIdentity,
6
- zoomTransform,
7
- type D3ZoomEvent,
8
- type ZoomBehavior,
9
- type ZoomTransform,
10
- } from 'd3'
1
+ import { pointer, scaleLinear, select, zoomIdentity, zoomTransform, type ZoomTransform } from 'd3'
11
2
  import type { ComputedRef, ShallowRef } from 'vue'
12
3
 
13
- import { hasMinimumVisibleXValues } from '../../core/rendering'
14
- import { WHEEL_ZOOM_DEBOUNCE_MS, ZOOM_CONSTRAINTS } from '../core/constants'
4
+ import { WHEEL_ZOOM_DEBOUNCE_MS } from '../core/constants'
15
5
  import type { TrackLayout } from '../core/types'
16
6
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from '../core/waveformChartTypes'
17
7
  import { useAnimationFrameThrottle } from '../utils/useAnimationFrameThrottle'
18
- import { constrainViewportZoom, remotePanBoundary } from './remoteViewport'
8
+ import { resolveStepZoomDomain, sameZoomDomain } from './stepZoom'
19
9
  import { normalizeIntegerZoomDomain } from './integerZoom'
20
- import { resolveMinimumZoomSpan, transformForDomain, type ZoomSeriesGroup } from './zoomConstraints'
10
+ import { transformForDomain, type ZoomSeriesGroup } from './zoomConstraints'
21
11
  import {
22
12
  emitIndependentWheelZoomEnds,
23
13
  emitSharedWheelZoomEnd,
@@ -57,16 +47,13 @@ export function useWaveformZoom(context: ZoomContext) {
57
47
  initialXDomain,
58
48
  sharedZoomDomain,
59
49
  innerWidth,
60
- innerHeight,
61
50
  hasChartArea,
62
51
  isZoomMode,
63
52
  isPresentationMode,
64
53
  resolveInitialTrackDomain,
65
54
  cancelPendingHover,
66
55
  } = context
67
- const zoomBehaviors = new Map<number | 'shared', ZoomBehavior<SVGRectElement, unknown>>()
68
56
  const zoomThrottle = useAnimationFrameThrottle()
69
- let synchronizingZoomTransform = false
70
57
  let pendingSharedZoomTransform: ZoomTransform | null = null
71
58
  let pendingSharedZoomGesture: ZoomGestureKind | null = null
72
59
  let lastSharedZoomGesture: ZoomGestureKind | null = null
@@ -75,11 +62,10 @@ export function useWaveformZoom(context: ZoomContext) {
75
62
  const lastIndependentWheelSeriesIds = new Map<string, string[]>()
76
63
  let wheelZoomEndTimer: ReturnType<typeof setTimeout> | undefined
77
64
  let preservePendingWheelDuringDataRebind = false
78
- let zoomBindingEpoch = 0
79
65
 
80
66
  const scheduleZoomCommit = () => zoomThrottle.schedule(commitPendingZoom)
81
- const handleSharedZoom = (event: D3ZoomEvent<SVGRectElement, unknown>) => {
82
- if (synchronizingZoomTransform || isPresentationMode.value) return
67
+ const handleSharedZoom = (event: { transform: ZoomTransform }) => {
68
+ if (isPresentationMode.value) return
83
69
  cancelPendingHover()
84
70
  pendingSharedZoomTransform = event.transform
85
71
  pendingSharedZoomGesture = 'wheel'
@@ -91,11 +77,8 @@ export function useWaveformZoom(context: ZoomContext) {
91
77
  emit('zoom-intent', { start, end, gesture: 'wheel' })
92
78
  scheduleZoomCommit()
93
79
  }
94
- const handleIndependentZoom = (
95
- event: D3ZoomEvent<SVGRectElement, unknown>,
96
- trackIndex: number,
97
- ) => {
98
- if (synchronizingZoomTransform || isPresentationMode.value) return
80
+ const handleIndependentZoom = (event: { transform: ZoomTransform }, trackIndex: number) => {
81
+ if (isPresentationMode.value) return
99
82
  cancelPendingHover()
100
83
  pendingIndependentZoomTransforms.set(trackIndex, event.transform)
101
84
  pendingIndependentZoomGestures.set(trackIndex, 'wheel')
@@ -166,16 +149,6 @@ export function useWaveformZoom(context: ZoomContext) {
166
149
  emitSharedWheelZoomEnd(emit, sharedZoomDomain.value, trackLayouts.value)
167
150
  lastSharedZoomGesture = null
168
151
  }
169
- const flushPendingZoom = () => {
170
- zoomThrottle.flush()
171
- commitPendingZoom()
172
- if (lastSharedZoomGesture === 'wheel' || lastIndependentWheelSeriesIds.size) return
173
- emitZoomEnd()
174
- }
175
- const handleD3ZoomEnd = (bindingEpoch: number) => {
176
- if (bindingEpoch !== zoomBindingEpoch) return
177
- flushPendingZoom()
178
- }
179
152
  function scheduleWheelZoomEnd() {
180
153
  if (wheelZoomEndTimer !== undefined) clearTimeout(wheelZoomEndTimer)
181
154
  wheelZoomEndTimer = setTimeout(() => {
@@ -207,81 +180,50 @@ export function useWaveformZoom(context: ZoomContext) {
207
180
  }
208
181
  }
209
182
  const clearZoomBindings = (preservePendingWheel = false) => {
210
- zoomBindingEpoch += 1
211
183
  if (!preservePendingWheel) cancelPendingZoom()
212
184
  svgElement.value
213
185
  ?.querySelectorAll<SVGRectElement>('.waveform-chart__overlay')
214
186
  .forEach((overlay) => select(overlay).on('.zoom', null))
215
- zoomBehaviors.clear()
216
187
  }
217
- const resolveMaximumZoomScale = (
218
- domain: [number, number],
219
- groups: readonly ZoomSeriesGroup[],
220
- ): number => {
221
- const domainSpan = Math.abs(domain[1] - domain[0])
222
- if (!Number.isFinite(domainSpan) || domainSpan <= 0) return ZOOM_CONSTRAINTS.MIN_SCALE
223
- const minimumSpan = resolveMinimumZoomSpan(domain, groups, props)
224
- return Math.max(
225
- ZOOM_CONSTRAINTS.MIN_SCALE,
226
- minimumSpan > 0 ? domainSpan / minimumSpan : Number.POSITIVE_INFINITY,
227
- )
228
- }
229
- const constrainTransform = (
230
- transform: ZoomTransform,
231
- domain: [number, number],
188
+ const bindWheelZoom = (
189
+ overlay: SVGRectElement,
190
+ baseDomain: [number, number],
232
191
  width: number,
233
192
  groups: readonly ZoomSeriesGroup[],
234
- ): ZoomTransform => {
235
- const requested = transform.rescaleX(scaleLinear(domain, [0, width])).domain() as [
236
- number,
237
- number,
238
- ]
239
- return transformForDomain(
240
- constrainViewportZoom(requested, domain, groups, props),
241
- domain,
242
- width,
243
- )
244
- }
245
- const canZoomTrack = (track: TrackLayout): boolean => {
246
- const minimum = Number(props.minVisiblePoints)
247
- return hasMinimumVisibleXValues(
248
- track.seriesList,
249
- track.xScale.domain() as [number, number],
250
- Number.isFinite(minimum) && minimum > 0 ? Math.ceil(minimum) + 1 : minimum,
251
- )
252
- }
253
- const canZoomSharedTracks = (): boolean => {
254
- const tracks = trackLayouts.value.filter((track) => track.hasVisibleSeries)
255
- return tracks.length > 0 && tracks.every(canZoomTrack)
256
- }
257
- const resolveMinimumZoomScale = (domain: [number, number]): number => {
258
- const boundary = remotePanBoundary(props)
259
- return boundary
260
- ? Math.min(1, (domain[1] - domain[0]) / (boundary[1] - boundary[0]))
261
- : ZOOM_CONSTRAINTS.MIN_SCALE
262
- }
263
- const canHandleWheelZoom = (event: Event, canZoomIn: boolean, minimumScale: number): boolean => {
264
- if (event.type !== 'wheel') return false
265
- const deltaY = (event as WheelEvent).deltaY
266
- const currentScale = zoomTransform(event.currentTarget as Element).k
267
-
268
- // 放大操作:需要检查是否还有足够的数据点支持继续放大
269
- if (deltaY < 0) return canZoomIn
270
-
271
- // 缩小操作:只要当前缩放级别大于最小比例就允许
272
- // 即使已达到最小可见点数限制,用户也应该能够缩小视图
273
- if (deltaY > 0) {
274
- // 使用小的容差避免浮点精度问题导致无法缩小到边界
275
- const scaleTolerance = 1e-6
276
- return currentScale > minimumScale * (1 + scaleTolerance)
277
- }
278
-
279
- return false
193
+ transform: ZoomTransform,
194
+ trackIndex?: number,
195
+ ) => {
196
+ select(overlay)
197
+ .property('__zoom', transform)
198
+ .on(
199
+ 'wheel.zoom',
200
+ (event: WheelEvent) => {
201
+ if (!Number.isFinite(event.deltaY) || event.deltaY === 0) return
202
+ const current = zoomTransform(overlay)
203
+ .rescaleX(scaleLinear(baseDomain, [0, width]))
204
+ .domain() as [number, number]
205
+ const next = resolveStepZoomDomain(
206
+ current,
207
+ event.deltaY < 0 ? 'zoom-in' : 'zoom-out',
208
+ baseDomain,
209
+ groups,
210
+ props,
211
+ pointer(event, overlay)[0] / width,
212
+ )
213
+ if (sameZoomDomain(current, next)) return
214
+ event.preventDefault()
215
+ event.stopImmediatePropagation()
216
+ const transform = transformForDomain(next, baseDomain, width)
217
+ select(overlay).property('__zoom', transform)
218
+ if (trackIndex === undefined) handleSharedZoom({ transform })
219
+ else handleIndependentZoom({ transform }, trackIndex)
220
+ },
221
+ { passive: false },
222
+ )
280
223
  }
281
224
  const configureZoom = () => {
282
225
  const preservePendingWheel = preservePendingWheelDuringDataRebind
283
226
  clearZoomBindings(preservePendingWheel)
284
- const bindingEpoch = zoomBindingEpoch
285
227
  if (
286
228
  isPresentationMode.value ||
287
229
  !props.zoomable ||
@@ -299,35 +241,14 @@ export function useWaveformZoom(context: ZoomContext) {
299
241
  if (!overlay) return
300
242
  const dataDomain = resolveInitialTrackDomain(track)
301
243
  const groups = [track.seriesList]
302
- const minimumScale = resolveMinimumZoomScale(dataDomain)
303
- const behavior = zoom<SVGRectElement, unknown>()
304
- .filter((event) => {
305
- const currentTrack =
306
- trackLayouts.value.find((item) => item.index === track.index) ?? track
307
- return canHandleWheelZoom(event, canZoomTrack(currentTrack), minimumScale)
308
- })
309
- .scaleExtent([minimumScale, resolveMaximumZoomScale(dataDomain, groups)])
310
- .constrain((transform) => constrainTransform(transform, dataDomain, track.width, groups))
311
- .extent([
312
- [0, 0],
313
- [track.width, track.height],
314
- ])
315
- .translateExtent([
316
- [0, 0],
317
- [track.width, track.height],
318
- ])
319
- .on('zoom', (event) => handleIndependentZoom(event, track.index))
320
- .on('end', () => handleD3ZoomEnd(bindingEpoch))
321
- zoomBehaviors.set(track.index, behavior)
322
- synchronizingZoomTransform = true
323
- try {
324
- select(overlay)
325
- .call(behavior)
326
- .on('dblclick.zoom', null)
327
- .call(behavior.transform, independentTransforms.value[track.index] ?? zoomIdentity)
328
- } finally {
329
- synchronizingZoomTransform = false
330
- }
244
+ bindWheelZoom(
245
+ overlay,
246
+ dataDomain,
247
+ track.width,
248
+ groups,
249
+ independentTransforms.value[track.index] ?? zoomIdentity,
250
+ track.index,
251
+ )
331
252
  })
332
253
  return
333
254
  }
@@ -336,40 +257,12 @@ export function useWaveformZoom(context: ZoomContext) {
336
257
  const groups = trackLayouts.value
337
258
  .filter((track) => track.hasVisibleSeries)
338
259
  .map((track) => track.seriesList)
339
- const minimumScale = resolveMinimumZoomScale(initialXDomain.value)
340
- const behavior = zoom<SVGRectElement, unknown>()
341
- .filter((event) => canHandleWheelZoom(event, canZoomSharedTracks(), minimumScale))
342
- .scaleExtent([minimumScale, resolveMaximumZoomScale(initialXDomain.value, groups)])
343
- .constrain((transform) =>
344
- constrainTransform(transform, initialXDomain.value, innerWidth.value, groups),
345
- )
346
- .extent([
347
- [0, 0],
348
- [innerWidth.value, innerHeight.value],
349
- ])
350
- .translateExtent([
351
- [0, 0],
352
- [innerWidth.value, innerHeight.value],
353
- ])
354
- .on('zoom', handleSharedZoom)
355
- .on('end', () => handleD3ZoomEnd(bindingEpoch))
356
- zoomBehaviors.set('shared', behavior)
357
- synchronizingZoomTransform = true
358
- try {
359
- select(overlay)
360
- .call(behavior)
361
- .on('dblclick.zoom', null)
362
- .call(behavior.transform, sharedTransform.value)
363
- } finally {
364
- synchronizingZoomTransform = false
365
- }
260
+ bindWheelZoom(overlay, initialXDomain.value, innerWidth.value, groups, sharedTransform.value)
366
261
  }
367
262
 
368
263
  return {
369
264
  cancelPendingZoom,
370
265
  clearZoomBindings,
371
- canZoomTrack,
372
- canZoomSharedTracks,
373
266
  configureZoom,
374
267
  prepareForDataChange,
375
268
  }
@@ -73,7 +73,7 @@ export function resolveMinimumZoomSpan(
73
73
  groups: readonly ZoomSeriesGroup[],
74
74
  options: ZoomConstraintOptions,
75
75
  ): number {
76
- const normalized = alignIntegerZoomDomain(normalizedBoundary(boundary), options)
76
+ const normalized = normalizedBoundary(boundary)
77
77
  const boundarySpan = normalized[1] - normalized[0]
78
78
  if (!Number.isFinite(boundarySpan) || boundarySpan <= 0) return 0
79
79
  const configuredSpan =
@@ -89,8 +89,7 @@ export function resolveMinimumZoomSpan(
89
89
  : options.maxZoomScale !== null && configuredSpan === 0 && required === 0
90
90
  ? boundarySpan / ZOOM_CONSTRAINTS.DEFAULT_MAX_SCALE
91
91
  : 0
92
- const integerSpan = options.integerZoom ? (options.timeUnit === 's' ? 1 : 0.001) : 0
93
- return Math.max(configuredSpan, pointSpan, scaleSpan, integerSpan)
92
+ return Math.max(configuredSpan, pointSpan, scaleSpan)
94
93
  }
95
94
 
96
95
  function expandToMinimumPoints(
@@ -141,7 +140,7 @@ export function constrainZoomDomain(
141
140
  groups: readonly ZoomSeriesGroup[],
142
141
  options: ZoomConstraintOptions,
143
142
  ): [number, number] {
144
- const normalized = alignIntegerZoomDomain(normalizedBoundary(boundary), options)
143
+ const normalized = normalizedBoundary(boundary)
145
144
  const boundarySpan = normalized[1] - normalized[0]
146
145
  if (!Number.isFinite(boundarySpan) || boundarySpan <= 0) return normalized
147
146
  if (Math.abs(domain[1] - domain[0]) >= boundarySpan * (1 - 1e-12)) return normalized
@@ -159,7 +158,8 @@ export function constrainZoomDomain(
159
158
  const span = Math.max(minimumSpan, Math.min(boundarySpan, expanded[1] - expanded[0]))
160
159
  const center = (expanded[0] + expanded[1]) / 2
161
160
  const start = Math.max(normalized[0], Math.min(center - span / 2, normalized[1] - span))
162
- return alignIntegerZoomDomain([start, start + span], options)
161
+ const aligned = alignIntegerZoomDomain([start, start + span], options)
162
+ return [Math.max(normalized[0], aligned[0]), Math.min(normalized[1], aligned[1])]
163
163
  }
164
164
 
165
165
  export function transformForDomain(
@@ -7,6 +7,7 @@ import { waveformLineDasharray, waveformPointSeriesPath } from './seriesStyle'
7
7
 
8
8
  const props = defineProps<{
9
9
  track: TrackLayout
10
+ paginationOpacity?: number
10
11
  clipPathId: string
11
12
  }>()
12
13
 
@@ -51,11 +52,22 @@ const renderedSeriesPaths = computed<RenderedSeriesPath[]>(() =>
51
52
  >
52
53
  <g
53
54
  v-for="seriesPath in renderedSeriesPaths"
54
- :key="seriesPath.series.id"
55
+ :key="seriesPath.presentationKey ?? seriesPath.series.id"
56
+ :opacity="(seriesPath.opacity ?? 1) * (paginationOpacity ?? 1)"
57
+ :clip-path="seriesPath.animationClip ? `url(#${seriesPath.animationClip.id})` : undefined"
55
58
  class="waveform-track__series-item waveform-chart__series"
56
59
  :data-series-id="seriesPath.series.id"
57
60
  :data-series-name="seriesPath.series.name || undefined"
58
61
  >
62
+ <defs v-if="seriesPath.animationClip">
63
+ <clipPath :id="seriesPath.animationClip.id" clipPathUnits="userSpaceOnUse">
64
+ <rect
65
+ :x="seriesPath.animationClip.x"
66
+ :width="seriesPath.animationClip.width"
67
+ :height="track.height"
68
+ />
69
+ </clipPath>
70
+ </defs>
59
71
  <path
60
72
  v-if="seriesPath.path"
61
73
  class="waveform-track__line waveform-chart__line"
@@ -12,6 +12,7 @@ import WaveformTrackBackdrop from './WaveformTrackBackdrop.vue'
12
12
 
13
13
  interface Props {
14
14
  track: TrackLayout
15
+ paginationOpacity?: number
15
16
  clipPathId: string
16
17
  innerWidth: number
17
18
  zoomable: boolean
@@ -136,7 +137,11 @@ const frameInset = computed(() =>
136
137
  aria-hidden="true"
137
138
  />
138
139
 
139
- <WaveformSeriesLayer :track="track" :clip-path-id="clipPathId" />
140
+ <WaveformSeriesLayer
141
+ :track="track"
142
+ :clip-path-id="clipPathId"
143
+ :pagination-opacity="paginationOpacity"
144
+ />
140
145
  <WaveformZeroLines
141
146
  :track="track"
142
147
  :clip-path-id="clipPathId"
@@ -6,10 +6,11 @@ import type { WaveformAxesOptions } from '../../types'
6
6
  import { formatXAxisLabel } from '../../utils'
7
7
  import { formatYAxisTickLabel } from '../core/layout'
8
8
  import type { DisplaySeries, TrackLayout, WaveformYAxisLayout } from '../core/types'
9
- import { updateMaximumTickVisibility } from './yAxisTickVisibility'
9
+ import { scheduleMaximumTickVisibility } from './yAxisTickVisibility'
10
10
  import { sameTick } from '../core/yAxisPadding'
11
11
  import { resolveYAxisLabelBandWidth, resolveYAxisLabelFontSize } from '../core/yAxisLabelStyle'
12
12
  import { formatChannelLabel } from './channelLabel'
13
+ import { updateAxis, updateSvgAttribute } from './updateAxis'
13
14
 
14
15
  interface Props {
15
16
  track: TrackLayout
@@ -58,77 +59,89 @@ function renderAxes() {
58
59
  if (!element) return
59
60
  const unit = props.showUnits ? axis.seriesList[0]?.unit : undefined
60
61
  const highestTick = Math.max(...axis.tickValues)
61
- const yAxis = (axis.side === 'left' ? axisLeft(axis.scale) : axisRight(axis.scale))
62
- .tickFormat((value) =>
63
- formatYAxisTickLabel(
64
- Number(value),
65
- axis.scale.domain() as [number, number],
66
- axis.tickValues,
67
- unit,
68
- axis.side,
69
- ),
62
+ const lowestTick = Math.min(...axis.tickValues)
63
+ const format = (value: number) =>
64
+ formatYAxisTickLabel(
65
+ value,
66
+ axis.scale.domain() as [number, number],
67
+ axis.tickValues,
68
+ unit,
69
+ axis.side,
70
70
  )
71
+ const yAxis = (axis.side === 'left' ? axisLeft(axis.scale) : axisRight(axis.scale))
72
+ .tickFormat((value) => format(Number(value)))
71
73
  .tickSize(-4)
72
74
  .tickPadding(7)
73
75
  .tickSizeOuter(0)
74
76
  .tickValues(axis.tickValues)
75
77
 
76
78
  const selection = select(element)
77
- selection.call(yAxis)
78
- selection
79
- .selectAll<SVGTextElement, number>('.tick text')
80
- .attr('y', null)
81
- .attr('dy', '0')
82
- .attr('dominant-baseline', 'central')
83
- .attr('data-axis-maximum-label', (value) =>
84
- props.hideOverlappingMaximumLabel && sameTick(value, highestTick) ? 'true' : null,
85
- )
86
- .attr('data-axis-minimum-label', (value) =>
87
- props.hideOverlappingMaximumLabel && sameTick(value, Math.min(...axis.tickValues))
88
- ? 'true'
89
- : null,
90
- )
91
- selection
92
- .attr('data-label-domain-min', axis.scale.domain()[0])
93
- .attr('data-label-domain-max', axis.scale.domain()[1])
94
- .attr('data-label-unit', unit ?? null)
95
- selection
96
- .selectAll<SVGGElement, number>('.tick')
97
- .attr('data-maximum-tick', (value) =>
79
+ updateAxis(element, yAxis, axis.scale, axis.tickValues, axis.side, format)
80
+ updateSvgAttribute(element, 'data-label-domain-min', axis.scale.domain()[0])
81
+ updateSvgAttribute(element, 'data-label-domain-max', axis.scale.domain()[1])
82
+ updateSvgAttribute(element, 'data-label-unit', unit ?? null)
83
+ selection.selectAll<SVGGElement, number>('.tick').each(function (value) {
84
+ const text = this.querySelector('text')
85
+ if (text) {
86
+ updateSvgAttribute(text, 'y', null)
87
+ updateSvgAttribute(text, 'dy', '0')
88
+ updateSvgAttribute(text, 'dominant-baseline', 'central')
89
+ updateSvgAttribute(
90
+ text,
91
+ 'data-axis-maximum-label',
92
+ props.hideOverlappingMaximumLabel && sameTick(value, highestTick) ? 'true' : null,
93
+ )
94
+ updateSvgAttribute(
95
+ text,
96
+ 'data-axis-minimum-label',
97
+ props.hideOverlappingMaximumLabel && sameTick(value, lowestTick) ? 'true' : null,
98
+ )
99
+ }
100
+ updateSvgAttribute(
101
+ this,
102
+ 'data-maximum-tick',
98
103
  axis.maximumTick !== undefined && sameTick(value, axis.maximumTick) ? 'true' : null,
99
104
  )
100
- .attr('data-minimum-tick', (value) =>
105
+ updateSvgAttribute(
106
+ this,
107
+ 'data-minimum-tick',
101
108
  axis.minimumTick !== undefined && sameTick(value, axis.minimumTick) ? 'true' : null,
102
109
  )
103
- .attr('data-tick-value', (value) => value)
104
- .attr('data-tick-y', (value) => axis.scale(value))
105
- selection
106
- .selectAll('path.domain')
107
- .attr('display', props.axes?.y?.lineVisible === false ? 'none' : null)
110
+ updateSvgAttribute(this, 'data-tick-value', value)
111
+ updateSvgAttribute(this, 'data-tick-y', axis.scale(value))
112
+ })
113
+ selection.selectAll<SVGPathElement, unknown>('path.domain').each(function () {
114
+ updateSvgAttribute(this, 'display', props.axes?.y?.lineVisible === false ? 'none' : null)
115
+ })
108
116
  })
109
117
 
110
118
  scheduleVisibility()
111
119
  if (!xAxisElement.value) return
112
120
  const selection = select(xAxisElement.value)
113
- selection.call(
121
+ const format = (value: number) =>
122
+ formatXAxisLabel(
123
+ value,
124
+ props.track.xScale.domain() as [number, number],
125
+ props.timeUnit,
126
+ 'tick',
127
+ props.axes?.x?.labelFormatter ?? props.track.xAxisLabelFormatter,
128
+ )
129
+ updateAxis(
130
+ xAxisElement.value,
114
131
  axisBottom(props.track.xScale)
115
132
  .tickValues(props.track.xAxisTickValues)
116
- .tickFormat((value) =>
117
- formatXAxisLabel(
118
- Number(value),
119
- props.track.xScale.domain() as [number, number],
120
- props.timeUnit,
121
- 'tick',
122
- props.axes?.x?.labelFormatter ?? props.track.xAxisLabelFormatter,
123
- ),
124
- )
133
+ .tickFormat((value) => format(Number(value)))
125
134
  .tickSize(-4)
126
135
  .tickPadding(7)
127
136
  .tickSizeOuter(0),
137
+ props.track.xScale,
138
+ props.track.xAxisTickValues,
139
+ 'bottom',
140
+ format,
128
141
  )
129
- selection
130
- .selectAll('path.domain')
131
- .attr('display', props.axes?.x?.lineVisible === false ? 'none' : null)
142
+ selection.selectAll<SVGPathElement, unknown>('path.domain').each(function () {
143
+ updateSvgAttribute(this, 'display', props.axes?.x?.lineVisible === false ? 'none' : null)
144
+ })
132
145
  }
133
146
 
134
147
  let disposed = false
@@ -136,7 +149,7 @@ function scheduleVisibility() {
136
149
  if (disposed) return
137
150
  const apply = () => {
138
151
  const svg = yAxisElements.value[0]?.ownerSVGElement
139
- if (svg) updateMaximumTickVisibility(svg)
152
+ if (svg) scheduleMaximumTickVisibility(svg)
140
153
  }
141
154
  apply()
142
155
  }
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { ref, watchEffect } from 'vue'
3
+ import { updateGrid } from './updateGrid'
2
4
  import type { TrackLayout } from '../core/types'
3
5
 
4
6
  interface Props {
@@ -13,7 +15,35 @@ interface Props {
13
15
  }
14
16
  }
15
17
 
16
- defineProps<Props>()
18
+ const props = defineProps<Props>()
19
+ const minorVertical = ref<SVGGElement>()
20
+ const minorHorizontal = ref<SVGGElement>()
21
+ const majorVertical = ref<SVGGElement>()
22
+ const majorHorizontal = ref<SVGGElement>()
23
+ watchEffect(
24
+ () => {
25
+ const track = props.track
26
+ for (const [element, direction, ticks] of [
27
+ [minorVertical.value, 'vertical', track.xMinorTicks],
28
+ [minorHorizontal.value, 'horizontal', track.yMinorTicks],
29
+ [majorVertical.value, 'vertical', track.xMajorTicks],
30
+ [majorHorizontal.value, 'horizontal', track.yMajorTicks],
31
+ ] as const) {
32
+ if (element)
33
+ updateGrid(
34
+ element,
35
+ track.gridLines[direction] ? ticks : [],
36
+ direction === 'vertical' ? track.xScale : track.yScale,
37
+ direction,
38
+ direction === 'vertical' ? track.height : (track.width ?? props.innerWidth),
39
+ direction === 'vertical'
40
+ ? track.gridLines.verticalColor
41
+ : track.gridLines.horizontalColor,
42
+ )
43
+ }
44
+ },
45
+ { flush: 'post' },
46
+ )
17
47
  </script>
18
48
 
19
49
  <template>
@@ -34,58 +64,14 @@ defineProps<Props>()
34
64
  <g
35
65
  class="waveform-track__grid waveform-track__grid--minor waveform-chart__grid waveform-chart__grid--minor"
36
66
  >
37
- <template v-if="track.gridLines.vertical">
38
- <line
39
- v-for="tick in track.xMinorTicks"
40
- :key="`x-minor-${track.index}-${tick}`"
41
- data-grid-direction="vertical"
42
- :stroke="track.gridLines.verticalColor"
43
- :x1="track.xScale(tick)"
44
- :x2="track.xScale(tick)"
45
- y1="0"
46
- :y2="track.height"
47
- />
48
- </template>
49
- <template v-if="track.gridLines.horizontal">
50
- <line
51
- v-for="tick in track.yMinorTicks"
52
- :key="`y-minor-${track.index}-${tick}`"
53
- data-grid-direction="horizontal"
54
- :stroke="track.gridLines.horizontalColor"
55
- x1="0"
56
- :x2="track.width ?? innerWidth"
57
- :y1="track.yScale(tick)"
58
- :y2="track.yScale(tick)"
59
- />
60
- </template>
67
+ <g ref="minorVertical" />
68
+ <g ref="minorHorizontal" />
61
69
  </g>
62
70
  <g
63
71
  class="waveform-track__grid waveform-track__grid--major waveform-chart__grid waveform-chart__grid--major"
64
72
  >
65
- <template v-if="track.gridLines.vertical">
66
- <line
67
- v-for="tick in track.xMajorTicks"
68
- :key="`x-major-${track.index}-${tick}`"
69
- data-grid-direction="vertical"
70
- :stroke="track.gridLines.verticalColor"
71
- :x1="track.xScale(tick)"
72
- :x2="track.xScale(tick)"
73
- y1="0"
74
- :y2="track.height"
75
- />
76
- </template>
77
- <template v-if="track.gridLines.horizontal">
78
- <line
79
- v-for="tick in track.yMajorTicks"
80
- :key="`y-major-${track.index}-${tick}`"
81
- data-grid-direction="horizontal"
82
- :stroke="track.gridLines.horizontalColor"
83
- x1="0"
84
- :x2="track.width ?? innerWidth"
85
- :y1="track.yScale(tick)"
86
- :y2="track.yScale(tick)"
87
- />
88
- </template>
73
+ <g ref="majorVertical" />
74
+ <g ref="majorHorizontal" />
89
75
  </g>
90
76
  </g>
91
77