waveform-analysis 0.1.74 → 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 (97) hide show
  1. package/README.md +246 -39
  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/types.d.ts +8 -0
  37. package/dist/types/components/core/useWaveformChartController.d.ts +25 -0
  38. package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
  39. package/dist/types/components/core/waveformChartTypes.d.ts +2 -1
  40. package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
  41. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
  42. package/dist/types/components/rendering/updateAxis.d.ts +5 -0
  43. package/dist/types/components/rendering/updateGrid.d.ts +3 -0
  44. package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
  45. package/dist/types/components/utils/assignElement.d.ts +2 -0
  46. package/dist/types/index.d.ts +9 -0
  47. package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
  48. package/package.json +5 -2
  49. package/src/DemoRouterApp.vue +1 -0
  50. package/src/animation/WaveformAnimationController.ts +328 -0
  51. package/src/animation/core/Animation.ts +37 -0
  52. package/src/animation/core/Animator.ts +200 -0
  53. package/src/animation/core/easing.ts +74 -0
  54. package/src/animation/core/errors.ts +24 -0
  55. package/src/animation/core/handle.ts +34 -0
  56. package/src/animation/core/owner.ts +9 -0
  57. package/src/animation/core/propertyGroups.ts +143 -0
  58. package/src/animation/core/task.ts +153 -0
  59. package/src/animation/core/types.ts +81 -0
  60. package/src/animation/core/validation.ts +173 -0
  61. package/src/animation/core/values.ts +38 -0
  62. package/src/animation/presets/annotation.ts +66 -0
  63. package/src/animation/presets/lineDrawing.ts +51 -0
  64. package/src/animation/presets/shared.ts +58 -0
  65. package/src/animation/presets/transitions.ts +77 -0
  66. package/src/animation/types.ts +81 -0
  67. package/src/components/WaveformChart.vue +1 -0
  68. package/src/components/WaveformChartView.vue +13 -4
  69. package/src/components/animation/annotationPresentation.ts +145 -0
  70. package/src/components/animation/chartExportVersion.ts +5 -0
  71. package/src/components/animation/config.ts +87 -0
  72. package/src/components/animation/context.ts +56 -0
  73. package/src/components/animation/geometry.ts +148 -0
  74. package/src/components/animation/paginationPresentation.ts +114 -0
  75. package/src/components/animation/pathBuffer.ts +61 -0
  76. package/src/components/animation/trackPresentation.ts +172 -0
  77. package/src/components/animation/useWaveformAnimations.ts +420 -0
  78. package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
  79. package/src/components/annotation/types.ts +3 -0
  80. package/src/components/controls/useChartImageExport.ts +4 -0
  81. package/src/components/core/trackLayoutBuilder.ts +9 -18
  82. package/src/components/core/types.ts +4 -0
  83. package/src/components/core/useWaveformChartController.ts +24 -20
  84. package/src/components/core/useWaveformRenderSampling.ts +23 -1
  85. package/src/components/core/waveformChartTypes.ts +2 -1
  86. package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
  87. package/src/components/rendering/WaveformTrack.vue +6 -1
  88. package/src/components/rendering/WaveformTrackAxes.vue +64 -51
  89. package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
  90. package/src/components/rendering/updateAxis.ts +57 -0
  91. package/src/components/rendering/updateGrid.ts +42 -0
  92. package/src/components/rendering/yAxisTickVisibility.ts +45 -18
  93. package/src/components/utils/assignElement.ts +7 -0
  94. package/src/index.ts +52 -0
  95. package/src/router.ts +1 -0
  96. package/src/views/AnimationDemo.vue +129 -0
  97. package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
@@ -1,5 +1,6 @@
1
1
  import { sameTick } from '../core/yAxisPadding'
2
2
  import { formatYAxisTickLabel } from '../core/layout'
3
+ import { updateSvgAttribute } from './updateAxis'
3
4
 
4
5
  interface Box {
5
6
  left: number
@@ -42,14 +43,25 @@ export function linesOverlap(a: number, b: number, widthA = 1, widthB = 1): bool
42
43
  }
43
44
 
44
45
  function hide(element: Element) {
45
- element.setAttribute('display', 'none')
46
- element.setAttribute('data-maximum-hidden', 'true')
46
+ updateSvgAttribute(element, 'display', 'none')
47
+ updateSvgAttribute(element, 'data-maximum-hidden', 'true')
47
48
  }
48
49
 
49
50
  function strokeWidth(element: Element): number {
50
51
  return Number.parseFloat(getComputedStyle(element).strokeWidth) || 1
51
52
  }
52
53
 
54
+ const pending = new WeakSet<SVGSVGElement>()
55
+ /** Coalesce sibling axes after Vue's current DOM patch, without adding a late animation frame. */
56
+ export function scheduleMaximumTickVisibility(svg: SVGSVGElement) {
57
+ if (pending.has(svg)) return
58
+ pending.add(svg)
59
+ queueMicrotask(() => {
60
+ pending.delete(svg)
61
+ updateMaximumTickVisibility(svg)
62
+ })
63
+ }
64
+
53
65
  function formatCompactLabels(svg: SVGSVGElement) {
54
66
  svg.querySelectorAll<SVGTextElement>('text[data-axis-maximum-label]').forEach((maximum) => {
55
67
  const axis = maximum.parentElement!.parentElement!
@@ -64,13 +76,14 @@ function formatCompactLabels(svg: SVGSVGElement) {
64
76
  Number(axis.getAttribute('data-label-domain-max')),
65
77
  ]
66
78
  labels.forEach((text) => {
67
- text.textContent = formatYAxisTickLabel(
79
+ const label = formatYAxisTickLabel(
68
80
  value(text),
69
81
  domain,
70
82
  visibleValues,
71
83
  axis.getAttribute('data-label-unit') ?? undefined,
72
84
  axis.getAttribute('data-y-axis-side') === 'right' ? 'right' : 'left',
73
85
  )
86
+ if (text.textContent !== label) text.textContent = label
74
87
  })
75
88
  })
76
89
  }
@@ -78,24 +91,38 @@ function formatCompactLabels(svg: SVGSVGElement) {
78
91
  /** Apply after axes and grid are in the DOM; never feed the filtered set back into layout. */
79
92
  export function updateMaximumTickVisibility(svg: SVGSVGElement) {
80
93
  svg.querySelectorAll('[data-maximum-hidden]').forEach((el) => {
81
- el.removeAttribute('display')
82
- el.removeAttribute('data-maximum-hidden')
94
+ updateSvgAttribute(el, 'display', null)
95
+ updateSvgAttribute(el, 'data-maximum-hidden', null)
83
96
  })
84
97
  svg.querySelectorAll('.waveform-track__axis--y .tick text').forEach((text) => {
85
- text.removeAttribute('display')
98
+ updateSvgAttribute(text, 'display', null)
86
99
  })
87
100
  formatCompactLabels(svg)
101
+ const boxes = new Map<SVGTextElement, Box>()
102
+ const widths = new Map<Element, number>()
103
+ const box = (text: SVGTextElement) => {
104
+ if (!boxes.has(text)) boxes.set(text, textBox(text))
105
+ return boxes.get(text)!
106
+ }
107
+ const width = (element: Element) => {
108
+ if (!widths.has(element)) widths.set(element, strokeWidth(element))
109
+ return widths.get(element)!
110
+ }
88
111
  const minimumLabels = [...svg.querySelectorAll<SVGTextElement>('text[data-axis-minimum-label]')]
89
112
  svg.querySelectorAll<SVGTextElement>('text[data-axis-maximum-label]').forEach((maximum) => {
90
- const box = textBox(maximum)
91
- if (minimumLabels.some((minimum) => labelsOverlap(box, textBox(minimum)))) hide(maximum)
113
+ const maximumBox = box(maximum)
114
+ if (minimumLabels.some((minimum) => labelsOverlap(maximumBox, box(minimum)))) hide(maximum)
92
115
  })
93
116
  svg.querySelectorAll<SVGGElement>('.waveform-track__axis--y .tick').forEach((tick) => {
94
117
  const position = Number(tick.getAttribute('data-tick-y'))
95
118
  const height = Number(tick.closest('.waveform-track')?.getAttribute('data-track-height'))
96
119
  const line = tick.querySelector('line')
97
- if (sameTick(position, 0) || sameTick(position, height)) line?.setAttribute('display', 'none')
98
- else line?.removeAttribute('display')
120
+ if (line)
121
+ updateSvgAttribute(
122
+ line,
123
+ 'display',
124
+ sameTick(position, 0) || sameTick(position, height) ? 'none' : null,
125
+ )
99
126
  })
100
127
  const minimumTicks = [...svg.querySelectorAll<SVGGElement>('.tick[data-minimum-tick]')]
101
128
  const maximumTicks = [...svg.querySelectorAll<SVGGElement>('.tick[data-maximum-tick]')]
@@ -105,7 +132,7 @@ export function updateMaximumTickVisibility(svg: SVGSVGElement) {
105
132
  if (!text) return
106
133
  maximumTicks.forEach((maximum) => {
107
134
  const other = maximum.querySelector('text')
108
- if (other && other !== text && labelsOverlap(textBox(text), textBox(other))) hide(other)
135
+ if (other && other !== text && labelsOverlap(box(text), box(other))) hide(other)
109
136
  })
110
137
  })
111
138
  const extremeTicks = [...new Set([...minimumTicks, ...maximumTicks])]
@@ -113,14 +140,14 @@ export function updateMaximumTickVisibility(svg: SVGSVGElement) {
113
140
  const text = tick.querySelector('text')
114
141
  const line = tick.querySelector('line')
115
142
  if (!text || !line) return
116
- const box = textBox(text)
143
+ const textBounds = box(text)
117
144
  labels.forEach((other) => {
118
145
  if (text.getAttribute('display') === 'none') return
119
146
  if (other.getAttribute('display') === 'none') return
120
147
  if (other.parentElement?.matches('[data-minimum-tick], [data-maximum-tick]')) return
121
148
  if (!tick.hasAttribute('data-minimum-tick') && other.hasAttribute('data-axis-minimum-label'))
122
149
  return
123
- if (labelsOverlap(box, textBox(other))) hide(other)
150
+ if (labelsOverlap(textBounds, box(other))) hide(other)
124
151
  })
125
152
  const axis = tick.parentElement!
126
153
  const value = Number(tick.getAttribute('data-tick-value'))
@@ -134,8 +161,8 @@ export function updateMaximumTickVisibility(svg: SVGSVGElement) {
134
161
  linesOverlap(
135
162
  position,
136
163
  Number(other.getAttribute('data-tick-y')),
137
- strokeWidth(line),
138
- strokeWidth(otherLine),
164
+ width(line),
165
+ width(otherLine),
139
166
  )
140
167
  )
141
168
  hide(otherLine)
@@ -145,12 +172,12 @@ export function updateMaximumTickVisibility(svg: SVGSVGElement) {
145
172
  const track = axis.closest('.waveform-track')!
146
173
  track.querySelectorAll('[data-grid-direction="horizontal"]').forEach((gridLine) => {
147
174
  const gridY = Number(gridLine.getAttribute('y1'))
148
- if (linesOverlap(position, gridY, strokeWidth(line), strokeWidth(gridLine))) hide(gridLine)
175
+ if (linesOverlap(position, gridY, width(line), width(gridLine))) hide(gridLine)
149
176
  })
150
177
  if (tick.hasAttribute('data-maximum-tick'))
151
- tick.setAttribute('data-maximum-value', String(value))
178
+ updateSvgAttribute(tick, 'data-maximum-value', value)
152
179
  if (tick.hasAttribute('data-minimum-tick'))
153
- tick.setAttribute('data-minimum-value', String(value))
180
+ updateSvgAttribute(tick, 'data-minimum-value', value)
154
181
  })
155
182
  formatCompactLabels(svg)
156
183
  }
@@ -0,0 +1,7 @@
1
+ import type { ComponentPublicInstance, Ref } from 'vue'
2
+ export function assignElement<T extends Element>(
3
+ target: Ref<T | undefined>,
4
+ element: Element | ComponentPublicInstance | null,
5
+ ) {
6
+ target.value = element instanceof Element ? (element as T) : undefined
7
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,58 @@
7
7
  export { default as WaveformChart } from './components/WaveformChart.vue'
8
8
  export type { WaveformChartProps } from './components/core/waveformChartTypes'
9
9
 
10
+ // Numeric engine, pure preset factories and owner-scoped chart animation.
11
+ export { getEasingFunction } from './animation/core/easing'
12
+ export { WaveformAnimationController } from './animation/WaveformAnimationController'
13
+ export {
14
+ createLineDrawingAnimation,
15
+ createStaggeredLineDrawingAnimations,
16
+ } from './animation/presets/lineDrawing'
17
+ export {
18
+ createPathTransitionAnimation,
19
+ createYAxisScaleAnimation,
20
+ } from './animation/presets/transitions'
21
+ export {
22
+ createAnnotationAppearAnimation,
23
+ createAnnotationDisappearAnimation,
24
+ } from './animation/presets/annotation'
25
+ export { Animation } from './animation/core/Animation'
26
+ export { Animator, globalAnimator } from './animation/core/Animator'
27
+ export type {
28
+ AnimationValue,
29
+ AnimationDirection,
30
+ AnimationState,
31
+ AnimationFrame,
32
+ AnimationTrack,
33
+ AnimationConfig,
34
+ AnimationHandle,
35
+ AnimationScheduler,
36
+ EasingFunction,
37
+ EasingFamily,
38
+ EasingType,
39
+ } from './animation/core/types'
40
+ export type {
41
+ LineDrawingDirection,
42
+ AnnotationAnimationType,
43
+ LineDrawingOptions,
44
+ StaggeredLineDrawingOptions,
45
+ DataUpdateOptions,
46
+ AnnotationAnimationOptions,
47
+ WaveformEnterAnimationOptions,
48
+ WaveformUpdateAnimationOptions,
49
+ WaveformAnnotationAnimationOptions,
50
+ WaveformAnimationProps,
51
+ ClipAnimationState,
52
+ AnimationBinding,
53
+ NumericGeometry,
54
+ GeometryTransitionInput,
55
+ GeometryTransitionFrame,
56
+ AnnotationAnimationState,
57
+ AnimationScope,
58
+ AnimationObjectKey,
59
+ WaveformAnimationControllerOptions,
60
+ } from './animation/types'
61
+
10
62
  // 类型定义
11
63
  export type {
12
64
  // 图表类型
package/src/router.ts CHANGED
@@ -3,6 +3,7 @@ import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-rou
3
3
  import App from './App.vue'
4
4
 
5
5
  export const routes: RouteRecordRaw[] = [
6
+ { path: '/animation', name: 'animation', component: () => import('./views/AnimationDemo.vue') },
6
7
  {
7
8
  path: '/',
8
9
  name: 'workspace',
@@ -0,0 +1,129 @@
1
+ <script setup lang="ts">
2
+ import { ref } from 'vue'
3
+ import { WaveformChart } from '@/index'
4
+ import type {
5
+ AnnotationAnimationType,
6
+ LineDrawingDirection,
7
+ WaveformData,
8
+ WaveformAnnotation,
9
+ } from '@/index'
10
+
11
+ const enabled = ref(true)
12
+ const enter = ref(true)
13
+ const update = ref(true)
14
+ const annotationEnabled = ref(true)
15
+ const effect = ref<AnnotationAnimationType>('scale')
16
+ const direction = ref<LineDrawingDirection>('left-to-right')
17
+ let generation = 0
18
+ function generate(count = 500): WaveformData {
19
+ return {
20
+ kind: 'series',
21
+ series: Array.from({ length: 3 }, (_, channel) => ({
22
+ id: channel === 0 ? 'signal' : `signal-${channel}`,
23
+ name: `信号 ${channel + 1}`,
24
+ data: {
25
+ kind: 'points' as const,
26
+ points: Array.from({ length: count }, (_, i) => ({
27
+ x: i / (count - 1),
28
+ y: Math.sin((i / (count - 1)) * Math.PI * 6 + channel) * (10 + generation * 2),
29
+ })),
30
+ },
31
+ })),
32
+ }
33
+ }
34
+ const data = ref(generate())
35
+ const annotations = ref<WaveformAnnotation[]>([])
36
+ const key = ref(0)
37
+ function replace(compatible: boolean) {
38
+ generation++
39
+ data.value = generate(compatible ? 500 : 750)
40
+ }
41
+ function toggleAnnotation() {
42
+ annotations.value = annotations.value.length
43
+ ? []
44
+ : Array.from({ length: 3 }, (_, channel) => ({
45
+ id: channel === 0 ? 'note' : `note-${channel}`,
46
+ seriesId: channel === 0 ? 'signal' : `signal-${channel}`,
47
+ x: 0.4,
48
+ y: 10,
49
+ text: '受控标注',
50
+ }))
51
+ }
52
+ </script>
53
+
54
+ <template>
55
+ <main class="animation-demo">
56
+ <h1>波形动画</h1>
57
+ <p>
58
+ 分别控制入场、更新 /
59
+ 切页和标注效果。切换分页时波形与标注同步淡入,坐标轴立即更新。拖动、缩放与导出会提交最终画面。
60
+ </p>
61
+ <div class="animation-demo__controls">
62
+ <label><input v-model="enabled" type="checkbox" /> 动画</label>
63
+ <label><input v-model="enter" type="checkbox" /> 入场</label>
64
+ <label><input v-model="update" type="checkbox" /> 更新 / 切页</label>
65
+ <label><input v-model="annotationEnabled" type="checkbox" /> 标注</label>
66
+ <label
67
+ >入场方向
68
+ <select v-model="direction">
69
+ <option value="left-to-right">从左向右</option>
70
+ <option value="right-to-left">从右向左</option>
71
+ <option value="center-out">从中心展开</option>
72
+ </select></label
73
+ >
74
+ <label
75
+ >标注效果
76
+ <select v-model="effect">
77
+ <option value="fade-in">淡入</option>
78
+ <option value="scale">缩放</option>
79
+ <option value="slide-in">滑入</option>
80
+ <option value="bounce">弹跳</option>
81
+ </select></label
82
+ >
83
+ <button @click="key++">重新入场</button>
84
+ <button @click="replace(true)">更新振幅</button>
85
+ <button @click="replace(false)">改变点数</button>
86
+ <button @click="toggleAnnotation">增删标注</button>
87
+ </div>
88
+ <WaveformChart
89
+ :key="key"
90
+ :data="data"
91
+ :height="360"
92
+ :grid="{ rowCount: 1, columnCount: 1 }"
93
+ :animation-enabled="enabled"
94
+ :animation="{ enabled: enter, direction }"
95
+ :animation-update="{ enabled: update }"
96
+ :animation-annotation="{ enabled: annotationEnabled, type: effect }"
97
+ :annotations="annotations"
98
+ @update:annotations="annotations = $event"
99
+ />
100
+ </main>
101
+ </template>
102
+
103
+ <style scoped>
104
+ .animation-demo {
105
+ padding: 24px;
106
+ max-width: 1280px;
107
+ margin: auto;
108
+ }
109
+ .animation-demo__controls {
110
+ display: flex;
111
+ flex-wrap: wrap;
112
+ gap: 12px;
113
+ margin-bottom: 20px;
114
+ }
115
+ .animation-demo__controls label {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 4px;
119
+ }
120
+ button,
121
+ select {
122
+ padding: 6px;
123
+ }
124
+ @media (max-width: 520px) {
125
+ .animation-demo {
126
+ padding: 8px;
127
+ }
128
+ }
129
+ </style>