waveform-analysis 0.1.73 → 0.1.75

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 (112) hide show
  1. package/README.md +256 -46
  2. package/dist/assets/workerRuntime-BJiRmIZD.js +2446 -0
  3. package/dist/index.cjs +1 -2
  4. package/dist/index.js +1 -7115
  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/layout.d.ts +2 -1
  37. package/dist/types/components/core/types.d.ts +8 -0
  38. package/dist/types/components/core/useWaveformChartController.d.ts +25 -0
  39. package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
  40. package/dist/types/components/core/waveformChartTypes.d.ts +2 -1
  41. package/dist/types/components/core/yAxisTitleLayout.d.ts +1 -1
  42. package/dist/types/components/interaction/useExtremaTickViewport.d.ts +1 -1
  43. package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
  44. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
  45. package/dist/types/components/rendering/updateAxis.d.ts +5 -0
  46. package/dist/types/components/rendering/updateGrid.d.ts +3 -0
  47. package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
  48. package/dist/types/components/utils/assignElement.d.ts +2 -0
  49. package/dist/types/demo/types.d.ts +2 -1
  50. package/dist/types/index.d.ts +9 -0
  51. package/dist/types/types/chart.d.ts +4 -2
  52. package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
  53. package/package.json +5 -2
  54. package/src/App.vue +6 -3
  55. package/src/DemoRouterApp.vue +1 -0
  56. package/src/animation/WaveformAnimationController.ts +328 -0
  57. package/src/animation/core/Animation.ts +37 -0
  58. package/src/animation/core/Animator.ts +200 -0
  59. package/src/animation/core/easing.ts +74 -0
  60. package/src/animation/core/errors.ts +24 -0
  61. package/src/animation/core/handle.ts +34 -0
  62. package/src/animation/core/owner.ts +9 -0
  63. package/src/animation/core/propertyGroups.ts +143 -0
  64. package/src/animation/core/task.ts +153 -0
  65. package/src/animation/core/types.ts +81 -0
  66. package/src/animation/core/validation.ts +173 -0
  67. package/src/animation/core/values.ts +38 -0
  68. package/src/animation/presets/annotation.ts +66 -0
  69. package/src/animation/presets/lineDrawing.ts +51 -0
  70. package/src/animation/presets/shared.ts +58 -0
  71. package/src/animation/presets/transitions.ts +77 -0
  72. package/src/animation/types.ts +81 -0
  73. package/src/components/WaveformChart.vue +1 -0
  74. package/src/components/WaveformChartView.vue +13 -4
  75. package/src/components/animation/annotationPresentation.ts +145 -0
  76. package/src/components/animation/chartExportVersion.ts +5 -0
  77. package/src/components/animation/config.ts +87 -0
  78. package/src/components/animation/context.ts +56 -0
  79. package/src/components/animation/geometry.ts +148 -0
  80. package/src/components/animation/paginationPresentation.ts +114 -0
  81. package/src/components/animation/pathBuffer.ts +61 -0
  82. package/src/components/animation/trackPresentation.ts +172 -0
  83. package/src/components/animation/useWaveformAnimations.ts +420 -0
  84. package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
  85. package/src/components/annotation/types.ts +3 -0
  86. package/src/components/controls/useChartImageExport.ts +4 -0
  87. package/src/components/core/layout.ts +36 -22
  88. package/src/components/core/trackLayoutBuilder.ts +17 -23
  89. package/src/components/core/types.ts +4 -0
  90. package/src/components/core/useWaveformChartController.ts +24 -20
  91. package/src/components/core/useWaveformLayout.ts +3 -1
  92. package/src/components/core/useWaveformRenderSampling.ts +23 -1
  93. package/src/components/core/waveformChartTypes.ts +2 -1
  94. package/src/components/core/yAxisLayoutMetrics.ts +24 -16
  95. package/src/components/core/yAxisPadding.ts +10 -5
  96. package/src/components/core/yAxisTitleLayout.ts +9 -48
  97. package/src/components/interaction/useExtremaTickViewport.ts +1 -1
  98. package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
  99. package/src/components/rendering/WaveformTrack.vue +6 -1
  100. package/src/components/rendering/WaveformTrackAxes.vue +64 -51
  101. package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
  102. package/src/components/rendering/updateAxis.ts +57 -0
  103. package/src/components/rendering/updateGrid.ts +42 -0
  104. package/src/components/rendering/yAxisTickVisibility.ts +45 -18
  105. package/src/components/utils/assignElement.ts +7 -0
  106. package/src/demo/DemoYAxisPaddingControls.vue +11 -3
  107. package/src/demo/types.ts +2 -1
  108. package/src/index.ts +53 -1
  109. package/src/router.ts +1 -0
  110. package/src/types/chart.ts +4 -2
  111. package/src/views/AnimationDemo.vue +129 -0
  112. package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
@@ -1,15 +1,8 @@
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 {
@@ -170,7 +163,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
170
163
  ? group.minimumTick
171
164
  : undefined
172
165
  const tickValues = withMaximumTick(majorTicks, maximumTick, minimumTick)
173
- const { tickTextWidth } = axisTextMetrics(
166
+ const { tickTextWidth, titleTextWidth } = axisTextMetrics(
174
167
  scale.domain() as [number, number],
175
168
  false,
176
169
  tickValues,
@@ -178,10 +171,13 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
178
171
  tickCount,
179
172
  )
180
173
  const clearance =
181
- tickTextWidth +
174
+ Math.max(
175
+ tickTextWidth,
176
+ titleTextWidth +
177
+ Y_AXIS_LABEL_GAP +
178
+ resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle),
179
+ ) +
182
180
  Y_AXIS_TICK_PADDING +
183
- Y_AXIS_LABEL_GAP +
184
- resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) +
185
181
  Y_AXIS_OUTER_PADDING
186
182
  const slot = options.yAxisSlots?.find(
187
183
  (candidate) => candidate.side === group.side && candidate.sideIndex === sideIndex,
@@ -192,7 +188,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
192
188
  ? -sideOffsets.left
193
189
  : cell.width + sideOffsets.right
194
190
  const labelDistance =
195
- tickTextWidth +
191
+ titleTextWidth +
196
192
  Y_AXIS_TICK_PADDING +
197
193
  Y_AXIS_LABEL_GAP +
198
194
  resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) / 2
@@ -261,17 +257,15 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
261
257
  hasErrorPoints: trackSeries.hasErrorPoints,
262
258
  },
263
259
  )
264
- const pathGenerator = line<WaveformPoint>()
265
- .x((point) => xScale(point.x))
266
- .y((point) => seriesYScale(point.y))
267
- if (trackSeries.lineType === 'step-start') pathGenerator.curve(curveStepBefore)
268
- if (trackSeries.lineType === 'step-middle') pathGenerator.curve(curveStep)
269
- if (trackSeries.lineType === 'step-end' || trackSeries.lineType === 'step-after') {
270
- pathGenerator.curve(curveStepAfter)
271
- }
272
260
  return {
273
261
  series: trackSeries,
274
- path: renderPoints.linePoints.length ? pathGenerator(renderPoints.linePoints) : null,
262
+ path: generateWaveformPath(
263
+ renderPoints.linePoints,
264
+ trackSeries.lineType,
265
+ xScale,
266
+ seriesYScale,
267
+ ),
268
+ lineRenderPoints: renderPoints.linePoints,
275
269
  pointRenderPoints: renderPoints.pointRenderPoints,
276
270
  errorBarRenderPoints: renderPoints.errorBarRenderPoints,
277
271
  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'
@@ -41,14 +41,6 @@ import type {
41
41
  ViewportSelectionState,
42
42
  WaveformChartEmit,
43
43
  } 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
44
  export function useWaveformChartController(
53
45
  inputProps: ResolvedWaveformChartProps,
54
46
  emit: WaveformChartEmit,
@@ -186,7 +178,17 @@ export function useWaveformChartController(
186
178
  renderingOptions,
187
179
  linePointOverrides,
188
180
  })
189
-
181
+ const animations = useWaveformAnimations({
182
+ props,
183
+ currentPage,
184
+ containerSize: computed(() => [observedWidth.value, observedHeight.value]),
185
+ layoutReady: computed(() => observedWidth.value > 0 && observedHeight.value > 0),
186
+ tracks: trackLayouts,
187
+ preparedSeries,
188
+ innerWidth,
189
+ innerHeight: presentation.innerHeight,
190
+ sampling,
191
+ })
190
192
  const paginationVisible = computed(
191
193
  () => gridOptions.value.showPagination && pageCount.value > 1 && !isCleanView.value,
192
194
  )
@@ -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>
@@ -281,7 +281,9 @@ export function useWaveformLayout(context: LayoutContext) {
281
281
  }),
282
282
  )
283
283
  useExtremaTickViewport(
284
- () => props.axes?.y?.extremaTicksVisible !== false,
284
+ () =>
285
+ Number(props.axes?.y?.maximumTickVisible !== false) * 2 +
286
+ Number(props.axes?.y?.minimumTickVisible !== false),
285
287
  innerWidth,
286
288
  trackLayouts,
287
289
  sharedTransform,
@@ -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
  }
@@ -1,3 +1,4 @@
1
+ import type { WaveformAnimationProps } from '../../animation/types'
1
2
  import type { WaveformToolbarOptions, WaveformControlState } from '../../types/controls'
2
3
  import type {
3
4
  WaveformAnnotation,
@@ -26,7 +27,7 @@ import type {
26
27
  } from '../data/types'
27
28
  import type { WaveformGridOptions } from './grid'
28
29
 
29
- export interface WaveformChartProps {
30
+ export interface WaveformChartProps extends WaveformAnimationProps {
30
31
  toolbar?: boolean | WaveformToolbarOptions
31
32
  data: WaveformData
32
33
  displayMode?: WaveformDisplayMode
@@ -60,29 +60,37 @@ export function resolveYAxisLayoutMetrics(
60
60
  padding,
61
61
  ),
62
62
  )
63
- .map(
64
- (group) =>
65
- axisTextMetrics(
66
- group.domain,
67
- nice,
68
- undefined,
69
- showUnits ? group.seriesList[0]?.unit : undefined,
70
- tickCount,
71
- compact,
72
- measureTextWidth,
73
- group.maximumTick,
74
- group.minimumTick,
75
- ).tickTextWidth,
63
+ .map((group) =>
64
+ axisTextMetrics(
65
+ group.domain,
66
+ nice,
67
+ undefined,
68
+ showUnits ? group.seriesList[0]?.unit : undefined,
69
+ tickCount,
70
+ compact,
71
+ measureTextWidth,
72
+ group.maximumTick,
73
+ group.minimumTick,
74
+ ),
76
75
  )
77
- const tickTextWidth = Math.max(Y_AXIS_CHARACTER_WIDTH, ...axisText)
76
+ const tickTextWidth = Math.max(
77
+ Y_AXIS_CHARACTER_WIDTH,
78
+ ...axisText.map((text) => text.tickTextWidth),
79
+ )
80
+ const titleTextWidth = Math.max(
81
+ Y_AXIS_CHARACTER_WIDTH,
82
+ ...axisText.map((text) => text.titleTextWidth),
83
+ )
78
84
  const tickClearance = tickTextWidth + Y_AXIS_TICK_PADDING + Y_AXIS_OUTER_PADDING
79
85
  const labelCenterX = -(
80
86
  Y_AXIS_TICK_PADDING +
81
- tickTextWidth +
87
+ titleTextWidth +
82
88
  Y_AXIS_LABEL_GAP +
83
89
  labelBandWidth / 2
84
90
  )
85
91
  const fullClearance =
86
- tickTextWidth + Y_AXIS_TICK_PADDING + Y_AXIS_LABEL_GAP + labelBandWidth + Y_AXIS_OUTER_PADDING
92
+ Math.max(tickTextWidth, titleTextWidth + Y_AXIS_LABEL_GAP + labelBandWidth) +
93
+ Y_AXIS_TICK_PADDING +
94
+ Y_AXIS_OUTER_PADDING
87
95
  return { tickClearance, fullClearance, labelCenterX }
88
96
  }
@@ -15,7 +15,7 @@ export function applyYAxisPadding(
15
15
  options: YAxisPaddingOptions | number,
16
16
  ): YAxisSeriesGroup {
17
17
  const config = typeof options === 'number' ? { upperPaddingRatio: options } : options
18
- if (group.fixed) return withYAxisExtremaTicks(group, config?.extremaTicksVisible)
18
+ if (group.fixed) return withYAxisExtremaTicks(group, config)
19
19
  const upper = paddingRatio(config?.upperPaddingEnabled, config?.upperPaddingRatio)
20
20
  const lower = paddingRatio(config?.lowerPaddingEnabled, config?.lowerPaddingRatio)
21
21
  const [a, b] = group.domain
@@ -27,11 +27,14 @@ export function applyYAxisPadding(
27
27
  ...group,
28
28
  domain: viewport ?? [Number.isFinite(min) ? min : a, Number.isFinite(max) ? max : b],
29
29
  },
30
- config?.extremaTicksVisible,
30
+ config,
31
31
  )
32
32
  }
33
33
 
34
- function withYAxisExtremaTicks(group: YAxisSeriesGroup, visible = true): YAxisSeriesGroup {
34
+ function withYAxisExtremaTicks(
35
+ group: YAxisSeriesGroup,
36
+ options: YAxisPaddingOptions,
37
+ ): YAxisSeriesGroup {
35
38
  const maxima = group.seriesList.flatMap((series) =>
36
39
  Number.isFinite(series.rawYMaximum) ? [series.rawYMaximum!] : [],
37
40
  )
@@ -40,8 +43,10 @@ function withYAxisExtremaTicks(group: YAxisSeriesGroup, visible = true): YAxisSe
40
43
  )
41
44
  return {
42
45
  ...group,
43
- maximumTick: visible && maxima.length ? Math.max(...maxima) : undefined,
44
- minimumTick: visible && minima.length ? Math.min(...minima) : undefined,
46
+ maximumTick:
47
+ options?.maximumTickVisible !== false && maxima.length ? Math.max(...maxima) : undefined,
48
+ minimumTick:
49
+ options?.minimumTickVisible !== false && minima.length ? Math.min(...minima) : undefined,
45
50
  }
46
51
  }
47
52
 
@@ -1,13 +1,9 @@
1
1
  import { formatYAxisTickLayoutLabel } from './layout'
2
2
  import type { TrackLayout, WaveformYAxisLayout } from './types'
3
3
  import type { WaveformYAxisLabelTextStyle } from '../../types'
4
- import { resolveYAxisLabelBandWidth, resolveYAxisLabelFontSize } from './yAxisLabelStyle'
4
+ import { resolveYAxisLabelBandWidth } from './yAxisLabelStyle'
5
5
  import { Y_AXIS_CHARACTER_WIDTH, Y_AXIS_TICK_PADDING } from './yAxisConstants'
6
6
 
7
- // Match the 11px ticks in WaveformTrack.css.
8
- const TICK_FONT_SIZE = 11
9
- const TEXT_GAP = 2
10
-
11
7
  interface TitleLayoutOptions {
12
8
  labelTextStyle?: WaveformYAxisLabelTextStyle
13
9
  showUnits?: boolean
@@ -15,52 +11,20 @@ interface TitleLayoutOptions {
15
11
  measureTextWidth?: (text: string) => number
16
12
  }
17
13
 
18
- function estimateTitleWidth(title: string, fontSize: number): number {
19
- return Array.from(title).reduce((width, character) => {
20
- // Latin labels average well below one em; full-width characters occupy one em.
21
- const em = /[\u2e80-\u9fff\uf900-\ufaff\uff01-\uff60]/u.test(character)
22
- ? 1
23
- : /[MW@%]/u.test(character)
24
- ? 0.95
25
- : /[ilI1., :;|'!]/u.test(character)
26
- ? 0.4
27
- : 0.65
28
- return width + em * fontSize
29
- }, 0)
30
- }
31
-
32
14
  type TitleTrack = Pick<TrackLayout, 'height' | 'column' | 'yAxes'>
33
15
 
34
- function titleDistance(
35
- track: TitleTrack,
36
- axis: WaveformYAxisLayout,
37
- yLabel: string | undefined,
38
- options: TitleLayoutOptions,
39
- ): number {
16
+ function titleDistance(axis: WaveformYAxisLayout, options: TitleLayoutOptions): number {
40
17
  const series = axis.seriesList[0]
41
- const title = series?.name.trim() || yLabel || ''
42
- const halfTitleHeight =
43
- estimateTitleWidth(title, resolveYAxisLabelFontSize(options.labelTextStyle)) / 2
44
- const titleTop = track.height / 2 - halfTitleHeight - TEXT_GAP
45
- const titleBottom = track.height / 2 + halfTitleHeight + TEXT_GAP
46
- const lowestTick = Math.min(...axis.tickValues)
47
- const highestTick = Math.max(...axis.tickValues)
48
- const tickWidths = axis.tickValues.flatMap((value) => {
49
- // D3 places ticks at scale(value) + 0.5. The lowest label is bottom-aligned;
50
- // all other labels are vertically centered using dy="0.32em".
51
- const y = axis.scale(value) + 0.5
52
- const isTopAligned = options.compact && value === highestTick
53
- const top = y - (value === lowestTick ? TICK_FONT_SIZE : isTopAligned ? 0 : TICK_FONT_SIZE / 2)
54
- const bottom =
55
- value === lowestTick ? y : y + (isTopAligned ? TICK_FONT_SIZE : TICK_FONT_SIZE / 2)
56
- if (bottom < titleTop || top > titleBottom) return []
18
+ // Reserve the widest tick, including extrema, regardless of the frame height.
19
+ // The layout formatter excludes scientific multipliers but retains the numeric label.
20
+ const tickWidths = axis.tickValues.map((value) => {
57
21
  const text = formatYAxisTickLayoutLabel(
58
22
  value,
59
23
  axis.scale.domain() as [number, number],
60
24
  axis.tickValues,
61
25
  options.showUnits === false ? undefined : series?.unit,
62
26
  )
63
- return [options.measureTextWidth?.(text) ?? text.length * Y_AXIS_CHARACTER_WIDTH]
27
+ return options.measureTextWidth?.(text) ?? text.length * Y_AXIS_CHARACTER_WIDTH
64
28
  })
65
29
  // The title band leaves 4px beside each glyph.
66
30
  return (
@@ -72,7 +36,7 @@ function titleDistance(
72
36
 
73
37
  export function alignLeftYAxisTitles(
74
38
  layouts: TitleTrack[],
75
- yLabel?: string,
39
+ _yLabel?: string,
76
40
  options: TitleLayoutOptions = {},
77
41
  ): void {
78
42
  const distances = new Map<string, number>()
@@ -81,11 +45,8 @@ export function alignLeftYAxisTitles(
81
45
  for (const axis of track.yAxes) {
82
46
  if (axis.side !== 'left') continue
83
47
  const key = `${track.column}:${leftIndex++}`
84
- const distance = titleDistance(track, axis, yLabel, options)
85
- // Compact slots already reserve every tick. Only move names inward, so
86
- // alignment can never push them beyond the space reserved at the chart edge.
87
- const boundedDistance = options.compact ? Math.min(axis.x - axis.labelX, distance) : distance
88
- distances.set(key, Math.max(distances.get(key) ?? 0, boundedDistance))
48
+ const distance = titleDistance(axis, options)
49
+ distances.set(key, Math.max(distances.get(key) ?? 0, distance))
89
50
  }
90
51
  }
91
52
  for (const track of layouts) {
@@ -5,7 +5,7 @@ import type { TrackLayout } from '../core/types'
5
5
 
6
6
  /** Keep X coordinates stable when toggling extrema changes the reserved Y-label width. */
7
7
  export function useExtremaTickViewport(
8
- visible: () => boolean,
8
+ visible: () => number,
9
9
  innerWidth: ComputedRef<number>,
10
10
  tracks: ComputedRef<TrackLayout[]>,
11
11
  sharedTransform: ShallowRef<ZoomTransform>,
@@ -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"