waveform-analysis 0.1.57 → 0.1.59
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.
- package/README.md +12 -7
- package/dist/index.cjs +1 -1
- package/dist/index.js +752 -680
- package/dist/types/components/WaveformChart.vue.d.ts +2 -0
- package/dist/types/components/core/grid.d.ts +3 -0
- package/dist/types/components/core/trackPagination.d.ts +4 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +3 -1
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +1 -2
- package/dist/types/components/core/useWaveformLayout.d.ts +2 -1
- package/dist/types/components/core/waveformChartTypes.d.ts +4 -1
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/index.d.ts +1 -1
- package/dist/types/components/interaction/useWaveformZoom.d.ts +2 -1
- package/dist/types/components/interaction/zoomEventPayload.d.ts +7 -0
- package/dist/types/core/rendering.d.ts +1 -1
- package/dist/types/core/renderingOptions.d.ts +1 -1
- package/dist/types/core/wasmSampling.d.ts +1 -1
- package/dist/types/core/workerSampling/multiresolution.d.ts +1 -1
- package/dist/types/core/workerSampling/protocol.d.ts +1 -1
- package/dist/types/core/workerSampling/repositoryDataset.d.ts +1 -1
- package/dist/types/core/workerSampling/repositorySupport.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/chart.d.ts +8 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/geometry.d.ts +1 -1
- package/dist/types/wasm/waveform_sampling_wasm.d.ts +74 -0
- package/dist/types/wasm/waveform_sampling_wasm_bg.wasm.d.ts +19 -0
- package/package.json +5 -4
- package/src/components/WaveformChartView.vue +3 -3
- package/src/components/core/grid.test.ts +3 -0
- package/src/components/core/grid.ts +4 -0
- package/src/components/core/trackPagination.ts +40 -0
- package/src/components/core/useWaveformChartController.ts +6 -1
- package/src/components/core/useWaveformChartLifecycle.ts +15 -9
- package/src/components/core/useWaveformLayout.ts +12 -9
- package/src/components/core/waveformChartTypes.ts +4 -0
- package/src/components/data/types.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/components/interaction/useWaveformViewport.ts +2 -0
- package/src/components/interaction/useWaveformZoom.ts +58 -61
- package/src/components/interaction/zoomEventPayload.ts +88 -0
- package/src/components/waveformChartCases/trackOrder.test.ts +62 -0
- package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +213 -0
- package/src/core/rendering.ts +1 -1
- package/src/core/renderingOptions.ts +1 -1
- package/src/core/workerSampling/multiresolution.ts +1 -1
- package/src/core/workerSampling/protocol.ts +1 -1
- package/src/core/workerSampling/repository.ts +1 -1
- package/src/core/workerSampling/repositoryDataset.ts +1 -1
- package/src/core/workerSampling/repositorySupport.ts +1 -1
- package/src/index.ts +1 -0
- package/src/types/chart.ts +9 -0
- package/src/types/index.ts +1 -0
- package/src/utils/geometry.ts +1 -1
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
resolveYAxisSeriesGroups,
|
|
32
32
|
} from './layout'
|
|
33
33
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
34
|
+
import { createFrameNumberResolver, resolvePageableTracks } from './trackPagination'
|
|
34
35
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
35
36
|
import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
36
37
|
import { applyXDomainStrategy } from './xDomain'
|
|
@@ -107,9 +108,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
107
108
|
})
|
|
108
109
|
})
|
|
109
110
|
const renderingOptions = computed(() => resolveWaveformRenderingOptions(props.rendering))
|
|
110
|
-
const
|
|
111
|
+
const pageableTracks = computed(() =>
|
|
112
|
+
resolvePageableTracks(chartTracks.value, gridOptions.value.hideEmptyTracks),
|
|
113
|
+
)
|
|
114
|
+
const pageCount = computed(() => getPageCount(pageableTracks.value.length, gridOptions.value))
|
|
111
115
|
const pagedTracks = computed(() =>
|
|
112
|
-
paginateSeries(
|
|
116
|
+
paginateSeries(pageableTracks.value, currentPage.value, gridOptions.value),
|
|
113
117
|
)
|
|
114
118
|
const layoutTracks = computed(() => {
|
|
115
119
|
const tracksWithSeries = pagedTracks.value.filter((track) => track.series.length > 0)
|
|
@@ -357,16 +361,15 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
357
361
|
}
|
|
358
362
|
: undefined
|
|
359
363
|
})
|
|
360
|
-
const resolveFrameNumber = (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
: `${props.frameNumber}-${trackIndex + 1}`
|
|
366
|
-
}
|
|
364
|
+
const resolveFrameNumber = createFrameNumberResolver(
|
|
365
|
+
() => props.frameNumber,
|
|
366
|
+
() => props.frameNumbers,
|
|
367
|
+
() => chartTracks.value,
|
|
368
|
+
)
|
|
367
369
|
return {
|
|
368
370
|
chartSeries,
|
|
369
371
|
chartTracks,
|
|
372
|
+
pageableTracks,
|
|
370
373
|
renderingOptions,
|
|
371
374
|
gridOptions,
|
|
372
375
|
pageCount,
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
WaveformTitleOptions,
|
|
17
17
|
WaveformXDomainStrategy,
|
|
18
18
|
WaveformZeroLineOptions,
|
|
19
|
+
WaveformZoomIntentPayload,
|
|
19
20
|
WaveformZoomEndPayload,
|
|
20
21
|
WaveformZoomResetPayload,
|
|
21
22
|
} from '../data/types'
|
|
@@ -43,6 +44,8 @@ export interface WaveformChartProps {
|
|
|
43
44
|
yDomains?: Record<string, [number, number]>
|
|
44
45
|
timeUnit?: 's' | 'ms'
|
|
45
46
|
frameNumber?: string | number
|
|
47
|
+
/** Per-track frame watermark overrides, keyed by stable trackId. */
|
|
48
|
+
frameNumbers?: Record<string, string | number>
|
|
46
49
|
frameStyle?: WaveformFrameStyle
|
|
47
50
|
axes?: WaveformAxesOptions
|
|
48
51
|
annotations?: WaveformAnnotation[]
|
|
@@ -88,6 +91,7 @@ export type ResolvedWaveformChartProps = Readonly<
|
|
|
88
91
|
|
|
89
92
|
export interface WaveformChartEmit {
|
|
90
93
|
(event: 'point-hover', point: WaveformPoint | null): void
|
|
94
|
+
(event: 'zoom-intent', payload: WaveformZoomIntentPayload): void
|
|
91
95
|
(event: 'zoom-change', domain: [number, number]): void
|
|
92
96
|
(event: 'zoom-end', payload: WaveformZoomEndPayload): void
|
|
93
97
|
(event: 'zoom-reset', payload: WaveformZoomResetPayload): void
|
package/src/components/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { tryReleasePointerCapture } from './pointerCapture'
|
|
|
10
10
|
import { transitionViewportInteraction } from './viewportInteractionState'
|
|
11
11
|
import { createViewportDomainSetter } from './viewportDomain'
|
|
12
12
|
import { constrainZoomDomain, transformForDomain } from './zoomConstraints'
|
|
13
|
+
import { emitBoxZoomIntent } from './zoomEventPayload'
|
|
13
14
|
interface ViewportContext {
|
|
14
15
|
props: ResolvedWaveformChartProps
|
|
15
16
|
emit: WaveformChartEmit
|
|
@@ -254,6 +255,7 @@ export function useWaveformViewport(context: ViewportContext) {
|
|
|
254
255
|
} else {
|
|
255
256
|
sharedTransform.value = transformForDomain(xDomain, baseXDomain, innerWidth.value)
|
|
256
257
|
}
|
|
258
|
+
emitBoxZoomIntent(emit, xDomain, track, active.independent)
|
|
257
259
|
const targets = active.independent
|
|
258
260
|
? [track]
|
|
259
261
|
: trackLayouts.value.filter((target) => target.hasVisibleSeries)
|
|
@@ -21,6 +21,11 @@ import {
|
|
|
21
21
|
transformForDomain,
|
|
22
22
|
type ZoomSeriesGroup,
|
|
23
23
|
} from './zoomConstraints'
|
|
24
|
+
import {
|
|
25
|
+
emitIndependentWheelZoomEnds,
|
|
26
|
+
emitSharedWheelZoomEnd,
|
|
27
|
+
seriesIdentity,
|
|
28
|
+
} from './zoomEventPayload'
|
|
24
29
|
|
|
25
30
|
interface ZoomContext {
|
|
26
31
|
props: ResolvedWaveformChartProps
|
|
@@ -70,9 +75,10 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
70
75
|
let lastSharedZoomGesture: ZoomGestureKind | null = null
|
|
71
76
|
const pendingIndependentZoomTransforms = new Map<number, ZoomTransform>()
|
|
72
77
|
const pendingIndependentZoomGestures = new Map<number, ZoomGestureKind>()
|
|
73
|
-
const
|
|
74
|
-
const lastZoomedTrackIndexes = new Set<number>()
|
|
78
|
+
const lastIndependentWheelSeriesIds = new Map<string, string[]>()
|
|
75
79
|
let wheelZoomEndTimer: ReturnType<typeof setTimeout> | undefined
|
|
80
|
+
let preservePendingWheelDuringDataRebind = false
|
|
81
|
+
let zoomBindingEpoch = 0
|
|
76
82
|
|
|
77
83
|
const scheduleZoomCommit = () => zoomThrottle.schedule(commitPendingZoom)
|
|
78
84
|
const handleSharedZoom = (event: D3ZoomEvent<SVGRectElement, unknown>) => {
|
|
@@ -80,8 +86,12 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
80
86
|
cancelPendingHover()
|
|
81
87
|
pendingSharedZoomTransform = event.transform
|
|
82
88
|
pendingSharedZoomGesture = 'wheel'
|
|
83
|
-
scheduleZoomCommit()
|
|
84
89
|
scheduleWheelZoomEnd()
|
|
90
|
+
const domain = event.transform
|
|
91
|
+
.rescaleX(scaleLinear(initialXDomain.value, [0, innerWidth.value]))
|
|
92
|
+
.domain()
|
|
93
|
+
emit('zoom-intent', { start: domain[0], end: domain[1], gesture: 'wheel' })
|
|
94
|
+
scheduleZoomCommit()
|
|
85
95
|
}
|
|
86
96
|
const handleIndependentZoom = (
|
|
87
97
|
event: D3ZoomEvent<SVGRectElement, unknown>,
|
|
@@ -91,8 +101,21 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
91
101
|
cancelPendingHover()
|
|
92
102
|
pendingIndependentZoomTransforms.set(trackIndex, event.transform)
|
|
93
103
|
pendingIndependentZoomGestures.set(trackIndex, 'wheel')
|
|
94
|
-
scheduleZoomCommit()
|
|
95
104
|
scheduleWheelZoomEnd()
|
|
105
|
+
const track = trackLayouts.value.find((item) => item.index === trackIndex)
|
|
106
|
+
if (track) {
|
|
107
|
+
const domain = event.transform
|
|
108
|
+
.rescaleX(scaleLinear(resolveInitialTrackDomain(track), [0, track.width]))
|
|
109
|
+
.domain()
|
|
110
|
+
emit('zoom-intent', {
|
|
111
|
+
start: domain[0],
|
|
112
|
+
end: domain[1],
|
|
113
|
+
trackIndex,
|
|
114
|
+
seriesIds: track.seriesList.map((series) => series.id),
|
|
115
|
+
gesture: 'wheel',
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
scheduleZoomCommit()
|
|
96
119
|
}
|
|
97
120
|
function commitPendingZoom() {
|
|
98
121
|
if (isPresentationMode.value) {
|
|
@@ -113,12 +136,7 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
113
136
|
if (!pendingIndependentZoomTransforms.size) return
|
|
114
137
|
const nextTransforms = [...independentTransforms.value]
|
|
115
138
|
const changedTrackIndexes = Array.from(pendingIndependentZoomTransforms.keys())
|
|
116
|
-
|
|
117
|
-
changedTrackIndexes.forEach((trackIndex) => lastZoomedTrackIndexes.add(trackIndex))
|
|
118
|
-
changedTrackIndexes.forEach((trackIndex) => {
|
|
119
|
-
const gesture = pendingIndependentZoomGestures.get(trackIndex)
|
|
120
|
-
if (gesture) lastIndependentZoomGestures.set(trackIndex, gesture)
|
|
121
|
-
})
|
|
139
|
+
const changedGestures = new Map(pendingIndependentZoomGestures)
|
|
122
140
|
pendingIndependentZoomTransforms.forEach((transform, trackIndex) => {
|
|
123
141
|
nextTransforms[trackIndex] = transform
|
|
124
142
|
})
|
|
@@ -129,69 +147,37 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
129
147
|
changedTrackIndexes.forEach((trackIndex) => {
|
|
130
148
|
const track = tracksByIndex.get(trackIndex)
|
|
131
149
|
if (!track) return
|
|
150
|
+
if (changedGestures.get(trackIndex) === 'wheel') {
|
|
151
|
+
const seriesIds = track.seriesList.map((series) => series.id)
|
|
152
|
+
lastIndependentWheelSeriesIds.set(seriesIdentity(seriesIds), seriesIds)
|
|
153
|
+
}
|
|
132
154
|
const domain = track.xScale.domain()
|
|
133
155
|
emit('zoom-change', [domain[0], domain[1]])
|
|
134
156
|
})
|
|
135
157
|
}
|
|
136
158
|
const emitZoomEnd = () => {
|
|
137
159
|
if (props.displayMode === 'independent') {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (lastIndependentZoomGestures.get(trackIndex) !== 'wheel') return
|
|
141
|
-
const track = tracksByIndex.get(trackIndex)
|
|
142
|
-
if (!track) return
|
|
143
|
-
const domain = track.xScale.domain() as [number, number]
|
|
144
|
-
const yDomain = track.yScale.domain()
|
|
145
|
-
emit('zoom-end', {
|
|
146
|
-
start: domain[0],
|
|
147
|
-
end: domain[1],
|
|
148
|
-
yStart: yDomain[0],
|
|
149
|
-
yEnd: yDomain[1],
|
|
150
|
-
trackIndex,
|
|
151
|
-
seriesIds: track.seriesList.map((series) => series.id),
|
|
152
|
-
gesture: 'wheel',
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
lastZoomedTrackIndexes.clear()
|
|
156
|
-
lastIndependentZoomGestures.clear()
|
|
160
|
+
emitIndependentWheelZoomEnds(emit, trackLayouts.value, lastIndependentWheelSeriesIds.values())
|
|
161
|
+
lastIndependentWheelSeriesIds.clear()
|
|
157
162
|
return
|
|
158
163
|
}
|
|
159
164
|
if (lastSharedZoomGesture !== 'wheel') {
|
|
160
165
|
lastSharedZoomGesture = null
|
|
161
166
|
return
|
|
162
167
|
}
|
|
163
|
-
|
|
164
|
-
const visibleTracks = trackLayouts.value.filter((track) => track.hasVisibleSeries)
|
|
165
|
-
if (visibleTracks.length === 1) {
|
|
166
|
-
const yDomain = visibleTracks[0]?.yScale.domain()
|
|
167
|
-
emit('zoom-end', {
|
|
168
|
-
start: domain[0],
|
|
169
|
-
end: domain[1],
|
|
170
|
-
yStart: yDomain?.[0],
|
|
171
|
-
yEnd: yDomain?.[1],
|
|
172
|
-
gesture: 'wheel',
|
|
173
|
-
})
|
|
174
|
-
} else {
|
|
175
|
-
emit('zoom-end', {
|
|
176
|
-
start: domain[0],
|
|
177
|
-
end: domain[1],
|
|
178
|
-
yRanges: Object.fromEntries(
|
|
179
|
-
visibleTracks.map((track) => [
|
|
180
|
-
track.series?.trackId ?? track.series?.id ?? track.id,
|
|
181
|
-
track.yScale.domain() as [number, number],
|
|
182
|
-
]),
|
|
183
|
-
),
|
|
184
|
-
gesture: 'wheel',
|
|
185
|
-
})
|
|
186
|
-
}
|
|
168
|
+
emitSharedWheelZoomEnd(emit, sharedZoomDomain.value, trackLayouts.value)
|
|
187
169
|
lastSharedZoomGesture = null
|
|
188
170
|
}
|
|
189
171
|
const flushPendingZoom = () => {
|
|
190
172
|
zoomThrottle.flush()
|
|
191
173
|
commitPendingZoom()
|
|
192
|
-
if (lastSharedZoomGesture === 'wheel' ||
|
|
174
|
+
if (lastSharedZoomGesture === 'wheel' || lastIndependentWheelSeriesIds.size) return
|
|
193
175
|
emitZoomEnd()
|
|
194
176
|
}
|
|
177
|
+
const handleD3ZoomEnd = (bindingEpoch: number) => {
|
|
178
|
+
if (bindingEpoch !== zoomBindingEpoch) return
|
|
179
|
+
flushPendingZoom()
|
|
180
|
+
}
|
|
195
181
|
function scheduleWheelZoomEnd() {
|
|
196
182
|
if (wheelZoomEndTimer !== undefined) clearTimeout(wheelZoomEndTimer)
|
|
197
183
|
wheelZoomEndTimer = setTimeout(() => {
|
|
@@ -199,24 +185,32 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
199
185
|
zoomThrottle.flush()
|
|
200
186
|
commitPendingZoom()
|
|
201
187
|
emitZoomEnd()
|
|
188
|
+
preservePendingWheelDuringDataRebind = false
|
|
202
189
|
}, WHEEL_ZOOM_DEBOUNCE_MS)
|
|
203
190
|
}
|
|
191
|
+
const prepareForDataChange = () => {
|
|
192
|
+
if (wheelZoomEndTimer === undefined) return
|
|
193
|
+
zoomThrottle.flush()
|
|
194
|
+
commitPendingZoom()
|
|
195
|
+
preservePendingWheelDuringDataRebind = true
|
|
196
|
+
}
|
|
204
197
|
const cancelPendingZoom = () => {
|
|
205
198
|
pendingSharedZoomTransform = null
|
|
206
199
|
pendingSharedZoomGesture = null
|
|
207
200
|
lastSharedZoomGesture = null
|
|
208
201
|
pendingIndependentZoomTransforms.clear()
|
|
209
202
|
pendingIndependentZoomGestures.clear()
|
|
210
|
-
|
|
211
|
-
lastIndependentZoomGestures.clear()
|
|
203
|
+
lastIndependentWheelSeriesIds.clear()
|
|
212
204
|
zoomThrottle.cancel()
|
|
205
|
+
preservePendingWheelDuringDataRebind = false
|
|
213
206
|
if (wheelZoomEndTimer !== undefined) {
|
|
214
207
|
clearTimeout(wheelZoomEndTimer)
|
|
215
208
|
wheelZoomEndTimer = undefined
|
|
216
209
|
}
|
|
217
210
|
}
|
|
218
|
-
const clearZoomBindings = () => {
|
|
219
|
-
|
|
211
|
+
const clearZoomBindings = (preservePendingWheel = false) => {
|
|
212
|
+
zoomBindingEpoch += 1
|
|
213
|
+
if (!preservePendingWheel) cancelPendingZoom()
|
|
220
214
|
svgElement.value
|
|
221
215
|
?.querySelectorAll<SVGRectElement>('.waveform-chart__overlay')
|
|
222
216
|
.forEach((overlay) => select(overlay).on('.zoom', null))
|
|
@@ -268,7 +262,9 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
268
262
|
return false
|
|
269
263
|
}
|
|
270
264
|
const configureZoom = () => {
|
|
271
|
-
|
|
265
|
+
const preservePendingWheel = preservePendingWheelDuringDataRebind
|
|
266
|
+
clearZoomBindings(preservePendingWheel)
|
|
267
|
+
const bindingEpoch = zoomBindingEpoch
|
|
272
268
|
if (
|
|
273
269
|
isPresentationMode.value ||
|
|
274
270
|
!props.zoomable ||
|
|
@@ -303,7 +299,7 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
303
299
|
[track.width, track.height],
|
|
304
300
|
])
|
|
305
301
|
.on('zoom', (event) => handleIndependentZoom(event, track.index))
|
|
306
|
-
.on('end',
|
|
302
|
+
.on('end', () => handleD3ZoomEnd(bindingEpoch))
|
|
307
303
|
zoomBehaviors.set(track.index, behavior)
|
|
308
304
|
synchronizingZoomTransform = true
|
|
309
305
|
try {
|
|
@@ -337,7 +333,7 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
337
333
|
[innerWidth.value, innerHeight.value],
|
|
338
334
|
])
|
|
339
335
|
.on('zoom', handleSharedZoom)
|
|
340
|
-
.on('end',
|
|
336
|
+
.on('end', () => handleD3ZoomEnd(bindingEpoch))
|
|
341
337
|
zoomBehaviors.set('shared', behavior)
|
|
342
338
|
synchronizingZoomTransform = true
|
|
343
339
|
try {
|
|
@@ -356,5 +352,6 @@ export function useWaveformZoom(context: ZoomContext) {
|
|
|
356
352
|
canZoomTrack,
|
|
357
353
|
canZoomSharedTracks,
|
|
358
354
|
configureZoom,
|
|
355
|
+
prepareForDataChange,
|
|
359
356
|
}
|
|
360
357
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { TrackLayout } from '../core/types'
|
|
2
|
+
import type { WaveformChartEmit } from '../core/waveformChartTypes'
|
|
3
|
+
|
|
4
|
+
export function seriesIdentity(seriesIds: readonly string[]) {
|
|
5
|
+
return [...seriesIds].sort().join('\u0000')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function findTrackBySeriesIds(
|
|
9
|
+
trackLayouts: readonly TrackLayout[],
|
|
10
|
+
seriesIds: readonly string[],
|
|
11
|
+
) {
|
|
12
|
+
const identity = seriesIdentity(seriesIds)
|
|
13
|
+
return trackLayouts.find(
|
|
14
|
+
(track) => seriesIdentity(track.seriesList.map((series) => series.id)) === identity,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function emitBoxZoomIntent(
|
|
19
|
+
emit: WaveformChartEmit,
|
|
20
|
+
domain: [number, number],
|
|
21
|
+
track: TrackLayout,
|
|
22
|
+
independent: boolean,
|
|
23
|
+
) {
|
|
24
|
+
emit(
|
|
25
|
+
'zoom-intent',
|
|
26
|
+
independent
|
|
27
|
+
? {
|
|
28
|
+
start: domain[0],
|
|
29
|
+
end: domain[1],
|
|
30
|
+
trackIndex: track.index,
|
|
31
|
+
seriesIds: track.seriesList.map((series) => series.id),
|
|
32
|
+
gesture: 'box',
|
|
33
|
+
}
|
|
34
|
+
: { start: domain[0], end: domain[1], gesture: 'box' },
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function emitIndependentWheelZoomEnds(
|
|
39
|
+
emit: WaveformChartEmit,
|
|
40
|
+
trackLayouts: readonly TrackLayout[],
|
|
41
|
+
pendingSeriesIds: Iterable<readonly string[]>,
|
|
42
|
+
) {
|
|
43
|
+
for (const seriesIds of pendingSeriesIds) {
|
|
44
|
+
const track = findTrackBySeriesIds(trackLayouts, seriesIds)
|
|
45
|
+
if (!track) continue
|
|
46
|
+
const domain = track.xScale.domain() as [number, number]
|
|
47
|
+
const yDomain = track.yScale.domain()
|
|
48
|
+
emit('zoom-end', {
|
|
49
|
+
start: domain[0],
|
|
50
|
+
end: domain[1],
|
|
51
|
+
yStart: yDomain[0],
|
|
52
|
+
yEnd: yDomain[1],
|
|
53
|
+
trackIndex: track.index,
|
|
54
|
+
seriesIds: [...seriesIds],
|
|
55
|
+
gesture: 'wheel',
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function emitSharedWheelZoomEnd(
|
|
61
|
+
emit: WaveformChartEmit,
|
|
62
|
+
domain: [number, number],
|
|
63
|
+
trackLayouts: readonly TrackLayout[],
|
|
64
|
+
) {
|
|
65
|
+
const visibleTracks = trackLayouts.filter((track) => track.hasVisibleSeries)
|
|
66
|
+
if (visibleTracks.length === 1) {
|
|
67
|
+
const yDomain = visibleTracks[0]?.yScale.domain()
|
|
68
|
+
emit('zoom-end', {
|
|
69
|
+
start: domain[0],
|
|
70
|
+
end: domain[1],
|
|
71
|
+
yStart: yDomain?.[0],
|
|
72
|
+
yEnd: yDomain?.[1],
|
|
73
|
+
gesture: 'wheel',
|
|
74
|
+
})
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
emit('zoom-end', {
|
|
78
|
+
start: domain[0],
|
|
79
|
+
end: domain[1],
|
|
80
|
+
yRanges: Object.fromEntries(
|
|
81
|
+
visibleTracks.map((track) => [
|
|
82
|
+
track.series?.trackId ?? track.series?.id ?? track.id,
|
|
83
|
+
track.yScale.domain() as [number, number],
|
|
84
|
+
]),
|
|
85
|
+
),
|
|
86
|
+
gesture: 'wheel',
|
|
87
|
+
})
|
|
88
|
+
}
|
|
@@ -104,6 +104,68 @@ describe('WaveformChart track order', () => {
|
|
|
104
104
|
expect(wrapper.findAll('.waveform-chart__crosshair line')).toHaveLength(2)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
it('keeps incremental watermarks stable when empty ordered tracks are hidden', async () => {
|
|
108
|
+
const wrapper = await mountSizedChart(mergedFourTrackSeries(), {
|
|
109
|
+
frameNumber: 1,
|
|
110
|
+
grid: {
|
|
111
|
+
rowCount: 2,
|
|
112
|
+
columnCount: 1,
|
|
113
|
+
hideEmptyTracks: true,
|
|
114
|
+
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
expect(
|
|
119
|
+
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
120
|
+
).toEqual(['frame-1', 'frame-3'])
|
|
121
|
+
expect(
|
|
122
|
+
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
123
|
+
).toEqual(['1', '3'])
|
|
124
|
+
|
|
125
|
+
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
wrapper.findAll('.waveform-chart__track').map((track) => track.attributes('data-track-id')),
|
|
129
|
+
).toEqual(['frame-4'])
|
|
130
|
+
expect(
|
|
131
|
+
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
132
|
+
).toEqual(['4'])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('uses stable track IDs for explicit frame watermark overrides', async () => {
|
|
136
|
+
const wrapper = await mountSizedChart(mergedFourTrackSeries(), {
|
|
137
|
+
frameNumber: 1,
|
|
138
|
+
frameNumbers: { 'frame-1': 'A', 'frame-3': 'C', 'frame-4': 'D' },
|
|
139
|
+
grid: {
|
|
140
|
+
rowCount: 2,
|
|
141
|
+
columnCount: 1,
|
|
142
|
+
hideEmptyTracks: true,
|
|
143
|
+
trackOrder: ['frame-1', 'frame-2', 'frame-3', 'frame-4'],
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
expect(
|
|
148
|
+
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
149
|
+
).toEqual(['A', 'C'])
|
|
150
|
+
|
|
151
|
+
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
152
|
+
|
|
153
|
+
expect(
|
|
154
|
+
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
155
|
+
).toEqual(['D'])
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('keeps the existing incremental watermark fallback when frame mappings are absent', async () => {
|
|
159
|
+
const wrapper = await mountSizedChart(orderedSeries(['frame-1', 'frame-2']), {
|
|
160
|
+
frameNumber: 7,
|
|
161
|
+
grid: { rowCount: 2, columnCount: 1 },
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
expect(
|
|
165
|
+
wrapper.findAll('.waveform-chart__watermark').map((watermark) => watermark.text()),
|
|
166
|
+
).toEqual(['7', '8'])
|
|
167
|
+
})
|
|
168
|
+
|
|
107
169
|
it('appends data tracks absent from trackOrder in input order', async () => {
|
|
108
170
|
const wrapper = await mountSizedChart(
|
|
109
171
|
{
|