waveform-analysis 0.1.31 → 0.1.35

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 (30) hide show
  1. package/README.md +26 -3
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +430 -401
  4. package/dist/types/components/WaveformChart.vue.d.ts +3 -2
  5. package/dist/types/components/core/trackLayoutBuilder.d.ts +2 -1
  6. package/dist/types/components/core/useWaveformChartController.d.ts +1 -0
  7. package/dist/types/components/core/waveformChartTypes.d.ts +4 -3
  8. package/dist/types/components/core/xDomain.d.ts +3 -0
  9. package/dist/types/components/data/types.d.ts +1 -1
  10. package/dist/types/components/waveform.d.ts +1 -1
  11. package/dist/types/index.d.ts +1 -1
  12. package/dist/types/types/chart.d.ts +15 -0
  13. package/dist/types/types/index.d.ts +1 -1
  14. package/package.json +1 -1
  15. package/src/components/WaveformChart.vue +1 -0
  16. package/src/components/core/trackLayoutBuilder.ts +19 -7
  17. package/src/components/core/useWaveformChartLifecycle.ts +1 -0
  18. package/src/components/core/useWaveformLayout.ts +19 -6
  19. package/src/components/core/waveformChartTypes.ts +5 -1
  20. package/src/components/core/xDomain.test.ts +28 -0
  21. package/src/components/core/xDomain.ts +25 -0
  22. package/src/components/data/types.ts +2 -0
  23. package/src/components/interaction/useWaveformViewport.ts +15 -1
  24. package/src/components/waveform.ts +1 -0
  25. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -65
  26. package/src/components/waveformChartCases/viewportReset.test.ts +149 -0
  27. package/src/components/waveformChartCases/xDomainStrategy.test.ts +96 -0
  28. package/src/index.ts +2 -0
  29. package/src/types/chart.ts +17 -0
  30. package/src/types/index.ts +2 -0
@@ -0,0 +1,149 @@
1
+ import { flushPromises } from '@vue/test-utils'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ import { flushAnimationFrames } from '../../test/setup'
5
+ import { gridSeries, mountSizedChart } from '../../test/waveformChart'
6
+
7
+ describe('WaveformChart viewport reset', () => {
8
+ const niceExplicitStrategy = {
9
+ type: 'nice' as const,
10
+ bounds: 'end' as const,
11
+ tickCount: 10,
12
+ includeExplicit: true,
13
+ }
14
+
15
+ it('resets a shared viewport on double-click and emits a global payload', async () => {
16
+ const wrapper = await mountSizedChart(
17
+ {
18
+ kind: 'points',
19
+ points: [
20
+ { x: 0, y: 0 },
21
+ { x: 2, y: 1 },
22
+ ],
23
+ },
24
+ { displayMode: 'separated' },
25
+ )
26
+ const overlay = wrapper.get('.waveform-chart__overlay')
27
+ const width = Number(overlay.attributes('width'))
28
+ Object.defineProperty(overlay.element, 'getBoundingClientRect', {
29
+ value: () => ({ left: 0, top: 0, width, height: 290 }),
30
+ })
31
+ overlay.element.dispatchEvent(
32
+ new WheelEvent('wheel', {
33
+ deltaY: -4000,
34
+ clientX: width / 2,
35
+ clientY: 145,
36
+ bubbles: true,
37
+ cancelable: true,
38
+ }),
39
+ )
40
+ flushAnimationFrames()
41
+ await flushPromises()
42
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe('0')
43
+
44
+ overlay.element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }))
45
+ await flushPromises()
46
+
47
+ expect(wrapper.emitted('zoom-reset')).toEqual([[{}]])
48
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
49
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
50
+ })
51
+
52
+ it('resets only the double-clicked independent track and identifies it', async () => {
53
+ const wrapper = await mountSizedChart(gridSeries(2), {
54
+ displayMode: 'independent',
55
+ grid: { rowCount: 1, columnCount: 2 },
56
+ })
57
+ const overlays = wrapper.findAll('.waveform-chart__overlay--independent')
58
+ for (const overlay of overlays) {
59
+ const width = Number(overlay.attributes('width'))
60
+ Object.defineProperty(overlay.element, 'getBoundingClientRect', {
61
+ value: () => ({ left: 0, top: 0, width, height: 260 }),
62
+ })
63
+ overlay.element.dispatchEvent(
64
+ new WheelEvent('wheel', {
65
+ deltaY: -4000,
66
+ clientX: width / 2,
67
+ clientY: 130,
68
+ bubbles: true,
69
+ cancelable: true,
70
+ }),
71
+ )
72
+ flushAnimationFrames()
73
+ await flushPromises()
74
+ }
75
+ const startsBeforeReset = wrapper.findAll('.waveform-chart__axis-endpoint--start')
76
+ expect(startsBeforeReset[0].text()).not.toBe('0')
77
+ expect(startsBeforeReset[1].text()).not.toBe('0')
78
+ const secondTrackStart = startsBeforeReset[1].text()
79
+
80
+ overlays[0].element.dispatchEvent(
81
+ new MouseEvent('dblclick', { bubbles: true, cancelable: true }),
82
+ )
83
+ await flushPromises()
84
+
85
+ const startsAfterReset = wrapper.findAll('.waveform-chart__axis-endpoint--start')
86
+ expect(startsAfterReset[0].text()).toBe('0')
87
+ expect(startsAfterReset[1].text()).toBe(secondTrackStart)
88
+ expect(wrapper.emitted('zoom-reset')).toEqual([[{ trackIndex: 0, seriesIds: ['channel-0'] }]])
89
+ })
90
+
91
+ it('keeps the exposed no-argument resetViewport global', async () => {
92
+ const wrapper = await mountSizedChart(gridSeries(2), {
93
+ displayMode: 'independent',
94
+ grid: { rowCount: 1, columnCount: 2 },
95
+ })
96
+ const overlay = wrapper.findAll('.waveform-chart__overlay--independent')[0]
97
+ const width = Number(overlay.attributes('width'))
98
+ Object.defineProperty(overlay.element, 'getBoundingClientRect', {
99
+ value: () => ({ left: 0, top: 0, width, height: 260 }),
100
+ })
101
+ overlay.element.dispatchEvent(
102
+ new WheelEvent('wheel', {
103
+ deltaY: -4000,
104
+ clientX: width / 2,
105
+ clientY: 130,
106
+ bubbles: true,
107
+ cancelable: true,
108
+ }),
109
+ )
110
+ flushAnimationFrames()
111
+ await flushPromises()
112
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).not.toBe('0')
113
+
114
+ const chart = wrapper.vm as unknown as { resetViewport: () => void }
115
+ chart.resetViewport()
116
+ await flushPromises()
117
+
118
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0')
119
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1000')
120
+ })
121
+
122
+ it('resets shared and independent explicit domains to their included nice bounds', async () => {
123
+ const shared = await mountSizedChart(
124
+ {
125
+ kind: 'points',
126
+ points: [
127
+ { x: 0, y: 0 },
128
+ { x: 4.999999, y: 1 },
129
+ ],
130
+ },
131
+ { initialXDomain: [0, 4.999999], xDomainStrategy: niceExplicitStrategy },
132
+ )
133
+ const independent = await mountSizedChart(gridSeries(2), {
134
+ displayMode: 'independent',
135
+ initialXDomains: { 'channel-0': [0, 4.999999] },
136
+ xDomainStrategy: niceExplicitStrategy,
137
+ })
138
+
139
+ ;(shared.vm as unknown as { resetViewport: () => void }).resetViewport()
140
+ ;(independent.vm as unknown as { resetViewport: () => void }).resetViewport()
141
+ await flushPromises()
142
+
143
+ expect(shared.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
144
+ expect(shared.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
145
+ const firstTrack = independent.findAll('.waveform-chart__track')[0]
146
+ expect(firstTrack.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
147
+ expect(firstTrack.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
148
+ })
149
+ })
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { gridSeries, mountSizedChart } from '../../test/waveformChart'
4
+
5
+ describe('WaveformChart x domain strategy', () => {
6
+ it('expands automatic x domains without changing source seconds', async () => {
7
+ const data = {
8
+ kind: 'points' as const,
9
+ points: [
10
+ { x: 0, y: 0 },
11
+ { x: 4.999999, y: 1 },
12
+ ],
13
+ }
14
+ const wrapper = await mountSizedChart(data, {
15
+ timeUnit: 'ms',
16
+ xDomainStrategy: { type: 'nice', bounds: 'end' },
17
+ })
18
+
19
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
20
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
21
+ expect(data.points[1]?.x).toBe(4.999999)
22
+ })
23
+
24
+ it('keeps explicit initial x domains exact by default', async () => {
25
+ const wrapper = await mountSizedChart(
26
+ {
27
+ kind: 'points',
28
+ points: [
29
+ { x: 123, y: 0 },
30
+ { x: 456, y: 1 },
31
+ ],
32
+ },
33
+ {
34
+ timeUnit: 's',
35
+ initialXDomain: [120, 460],
36
+ xDomainStrategy: { type: 'nice' },
37
+ },
38
+ )
39
+
40
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('120')
41
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('460')
42
+ })
43
+
44
+ it('keeps explicit per-track initial x domains exact by default', async () => {
45
+ const wrapper = await mountSizedChart(gridSeries(1), {
46
+ displayMode: 'independent',
47
+ initialXDomains: { 'channel-0': [0, 4.999999] },
48
+ xDomainStrategy: { type: 'nice', bounds: 'end' },
49
+ })
50
+
51
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
52
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4999.999')
53
+ })
54
+
55
+ it('includes an explicit shared initial x domain when requested', async () => {
56
+ const wrapper = await mountSizedChart(
57
+ {
58
+ kind: 'points',
59
+ points: [
60
+ { x: 0, y: 0 },
61
+ { x: 4.999999, y: 1 },
62
+ ],
63
+ },
64
+ {
65
+ initialXDomain: [0, 4.999999],
66
+ xDomainStrategy: {
67
+ type: 'nice',
68
+ bounds: 'end',
69
+ tickCount: 10,
70
+ includeExplicit: true,
71
+ },
72
+ },
73
+ )
74
+
75
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
76
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
77
+ })
78
+
79
+ it('includes explicit per-track initial x domains in independent mode', async () => {
80
+ const wrapper = await mountSizedChart(gridSeries(2), {
81
+ displayMode: 'independent',
82
+ grid: { rowCount: 1, columnCount: 2 },
83
+ initialXDomains: {
84
+ 'channel-0': [0, 4.999999],
85
+ 'channel-1': [10, 14.999999],
86
+ },
87
+ xDomainStrategy: { type: 'nice', bounds: 'end', tickCount: 10, includeExplicit: true },
88
+ })
89
+
90
+ const tracks = wrapper.findAll('.waveform-chart__track')
91
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
92
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
93
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('10000')
94
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('15000')
95
+ })
96
+ })
package/src/index.ts CHANGED
@@ -13,7 +13,9 @@ export type {
13
13
  WaveformDisplayMode,
14
14
  WaveformOverlayMode,
15
15
  WaveformInteractionMode,
16
+ WaveformXDomainStrategy,
16
17
  WaveformZoomEndPayload,
18
+ WaveformZoomResetPayload,
17
19
  WaveformAnnotationStyle,
18
20
  WaveformAnnotation,
19
21
  WaveformRenderingOptions,
@@ -26,6 +26,17 @@ export type WaveformOverlayMode = 'single-axis' | 'multi-axis'
26
26
  /** 标注工具模式 */
27
27
  export type WaveformInteractionMode = 'zoom' | 'annotation'
28
28
 
29
+ /** Controls how the initial X viewport is derived when no explicit domain is configured. */
30
+ export interface WaveformXDomainStrategy {
31
+ type: 'data' | 'nice'
32
+ /** Selects which bounds are expanded when type is `nice`. Defaults to `both`. */
33
+ bounds?: 'both' | 'end'
34
+ /** Stable tick count used to calculate nice bounds. Defaults to 10. */
35
+ tickCount?: number
36
+ /** Applies the strategy to explicit initial domains. Defaults to false. */
37
+ includeExplicit?: boolean
38
+ }
39
+
29
40
  /** Describes the X-axis viewport after a zoom gesture completes. */
30
41
  export interface WaveformZoomEndPayload {
31
42
  start: number
@@ -38,6 +49,12 @@ export interface WaveformZoomEndPayload {
38
49
  gesture?: 'wheel' | 'box'
39
50
  }
40
51
 
52
+ /** Identifies the viewport reset by a double-click gesture. */
53
+ export interface WaveformZoomResetPayload {
54
+ trackIndex?: number
55
+ seriesIds?: string[]
56
+ }
57
+
41
58
  /** 标注颜色样式 */
42
59
  export interface WaveformAnnotationStyle {
43
60
  borderColor?: string
@@ -8,7 +8,9 @@ export type {
8
8
  WaveformDisplayMode,
9
9
  WaveformOverlayMode,
10
10
  WaveformInteractionMode,
11
+ WaveformXDomainStrategy,
11
12
  WaveformZoomEndPayload,
13
+ WaveformZoomResetPayload,
12
14
  WaveformAnnotationStyle,
13
15
  WaveformAnnotation,
14
16
  WaveformRenderingOptions,