waveform-analysis 0.1.38 → 0.1.39
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 +14 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +315 -266
- package/dist/types/components/WaveformChart.vue.d.ts +1 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +1 -0
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +3 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -0
- package/dist/types/components/interaction/viewportDomain.d.ts +21 -0
- package/package.json +1 -1
- package/src/components/WaveformChart.vue +1 -0
- package/src/components/core/useWaveformChartController.ts +3 -0
- package/src/components/core/useWaveformChartLifecycle.ts +29 -19
- package/src/components/interaction/useWaveformViewport.ts +16 -0
- package/src/components/interaction/viewportDomain.ts +60 -0
- package/src/components/waveformChartCases/viewportReset.test.ts +102 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WaveformChartProps } from './core/waveformChartTypes';
|
|
2
2
|
declare const __VLS_export: import("vue").DefineComponent<WaveformChartProps, {
|
|
3
3
|
resetViewport: (trackIndex?: number) => void;
|
|
4
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void;
|
|
4
5
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
5
6
|
"point-hover": (point: import("..").WaveformPoint | null) => any;
|
|
6
7
|
"zoom-change": (domain: [number, number]) => any;
|
|
@@ -228,6 +228,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
|
|
|
228
228
|
finishViewportDrag: (event: PointerEvent) => void;
|
|
229
229
|
cancelViewportDrag: (event?: PointerEvent) => void;
|
|
230
230
|
resetViewport: (trackIndex?: number) => void;
|
|
231
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void;
|
|
231
232
|
requestViewportReset: (event: MouseEvent) => void;
|
|
232
233
|
toggleSeriesVisibility: (seriesId: string) => void;
|
|
233
234
|
changeDraftSeries: (seriesId: string) => void;
|
|
@@ -31,6 +31,9 @@ interface LifecycleContext {
|
|
|
31
31
|
trackLayouts: ComputedRef<TrackLayout[]>;
|
|
32
32
|
innerWidth: ComputedRef<number>;
|
|
33
33
|
innerHeight: ComputedRef<number>;
|
|
34
|
+
sharedZoomDomain: ComputedRef<[number, number]>;
|
|
35
|
+
initialXDomain: ComputedRef<[number, number]>;
|
|
36
|
+
sharedTransform: ShallowRef<ZoomTransform>;
|
|
34
37
|
activeInteractionMode: ComputedRef<string | undefined>;
|
|
35
38
|
hiddenSeriesIdSet: ComputedRef<Set<string>>;
|
|
36
39
|
internalHiddenSeriesIds: Ref<Set<string>>;
|
|
@@ -42,6 +42,7 @@ export declare function useWaveformViewport(context: ViewportContext): {
|
|
|
42
42
|
finishViewportDrag: (event: PointerEvent) => void;
|
|
43
43
|
cancelViewportDrag: (event?: PointerEvent) => void;
|
|
44
44
|
resetViewport: (trackIndex?: number) => void;
|
|
45
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void;
|
|
45
46
|
requestViewportReset: (event: MouseEvent) => void;
|
|
46
47
|
};
|
|
47
48
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ComputedRef, type Ref, type ShallowRef } from 'vue';
|
|
2
|
+
import type { ZoomTransform } from 'd3';
|
|
3
|
+
import type { TrackLayout } from '../core/types';
|
|
4
|
+
import type { ResolvedWaveformChartProps } from '../core/waveformChartTypes';
|
|
5
|
+
import type { AnnotationSeriesCandidate } from '../annotation';
|
|
6
|
+
interface ViewportDomainContext {
|
|
7
|
+
props: ResolvedWaveformChartProps;
|
|
8
|
+
trackLayouts: ComputedRef<TrackLayout[]>;
|
|
9
|
+
initialXDomain: ComputedRef<[number, number]>;
|
|
10
|
+
resolveInitialTrackDomain: (track: TrackLayout) => [number, number];
|
|
11
|
+
innerWidth: ComputedRef<number>;
|
|
12
|
+
sharedTransform: ShallowRef<ZoomTransform>;
|
|
13
|
+
independentTransforms: ShallowRef<ZoomTransform[]>;
|
|
14
|
+
cancelPendingZoom: () => void;
|
|
15
|
+
cancelViewportDrag: () => void;
|
|
16
|
+
clearHover: () => void;
|
|
17
|
+
editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>;
|
|
18
|
+
configureZoom: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function createViewportDomainSetter(context: ViewportDomainContext): (domain: [number, number], trackIndex?: number) => void;
|
|
21
|
+
export {};
|
package/package.json
CHANGED
|
@@ -39,6 +39,9 @@ interface LifecycleContext {
|
|
|
39
39
|
trackLayouts: ComputedRef<TrackLayout[]>
|
|
40
40
|
innerWidth: ComputedRef<number>
|
|
41
41
|
innerHeight: ComputedRef<number>
|
|
42
|
+
sharedZoomDomain: ComputedRef<[number, number]>
|
|
43
|
+
initialXDomain: ComputedRef<[number, number]>
|
|
44
|
+
sharedTransform: ShallowRef<ZoomTransform>
|
|
42
45
|
activeInteractionMode: ComputedRef<string | undefined>
|
|
43
46
|
hiddenSeriesIdSet: ComputedRef<Set<string>>
|
|
44
47
|
internalHiddenSeriesIds: Ref<Set<string>>
|
|
@@ -93,6 +96,9 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
93
96
|
trackLayouts,
|
|
94
97
|
innerWidth,
|
|
95
98
|
innerHeight,
|
|
99
|
+
sharedZoomDomain,
|
|
100
|
+
initialXDomain,
|
|
101
|
+
sharedTransform,
|
|
96
102
|
activeInteractionMode,
|
|
97
103
|
hiddenSeriesIdSet,
|
|
98
104
|
internalHiddenSeriesIds,
|
|
@@ -144,25 +150,24 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
144
150
|
emit('page-change', nextPage, pageCount.value)
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
let pendingSharedXDomain: [number, number] | undefined
|
|
147
154
|
let pendingIndependentXDomains: Array<[number, number] | undefined> | undefined
|
|
148
155
|
|
|
149
156
|
function handleBeforeDataReferenceChange() {
|
|
150
|
-
if (props.displayMode
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return track.xScale.domain() as [number, number]
|
|
165
|
-
})
|
|
157
|
+
if (props.displayMode === 'independent') {
|
|
158
|
+
pendingSharedXDomain = undefined
|
|
159
|
+
pendingIndependentXDomains = trackLayouts.value.map((track) => {
|
|
160
|
+
const current = track.xScale.domain() as [number, number]
|
|
161
|
+
const boundary = resolveInitialTrackDomain(track)
|
|
162
|
+
return current[1] - current[0] < boundary[1] - boundary[0] - 1e-12 ? current : undefined
|
|
163
|
+
})
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
pendingIndependentXDomains = undefined
|
|
167
|
+
const current = sharedZoomDomain.value
|
|
168
|
+
const boundary = initialXDomain.value
|
|
169
|
+
pendingSharedXDomain =
|
|
170
|
+
current[1] - current[0] < boundary[1] - boundary[0] - 1e-12 ? [...current] : undefined
|
|
166
171
|
}
|
|
167
172
|
|
|
168
173
|
function handleDataReferenceChange() {
|
|
@@ -186,10 +191,15 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
186
191
|
})
|
|
187
192
|
independentTransforms.value = nextTransforms
|
|
188
193
|
pendingIndependentXDomains = undefined
|
|
189
|
-
|
|
190
|
-
|
|
194
|
+
} else if (props.displayMode !== 'independent' && pendingSharedXDomain) {
|
|
195
|
+
const boundary = initialXDomain.value
|
|
196
|
+
const groups = trackLayouts.value
|
|
197
|
+
.filter((track) => track.hasVisibleSeries)
|
|
198
|
+
.map((track) => track.seriesList)
|
|
199
|
+
const domain = constrainZoomDomain(pendingSharedXDomain, boundary, groups, props)
|
|
200
|
+
sharedTransform.value = transformForDomain(domain, boundary, innerWidth.value)
|
|
201
|
+
pendingSharedXDomain = undefined
|
|
191
202
|
}
|
|
192
|
-
pendingIndependentXDomains = undefined
|
|
193
203
|
configureZoom()
|
|
194
204
|
})
|
|
195
205
|
}
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import type { AnnotationSeriesCandidate } from '../annotation'
|
|
12
12
|
import { tryReleasePointerCapture } from './pointerCapture'
|
|
13
13
|
import { transitionViewportInteraction } from './viewportInteractionState'
|
|
14
|
+
import { createViewportDomainSetter } from './viewportDomain'
|
|
14
15
|
import { constrainZoomDomain, transformForDomain } from './zoomConstraints'
|
|
15
16
|
interface ViewportContext {
|
|
16
17
|
props: ResolvedWaveformChartProps
|
|
@@ -344,6 +345,20 @@ export function useWaveformViewport(context: ViewportContext) {
|
|
|
344
345
|
editorSeriesOptions.value = []
|
|
345
346
|
void nextTick(configureZoom)
|
|
346
347
|
}
|
|
348
|
+
const setViewportDomain = createViewportDomainSetter({
|
|
349
|
+
props,
|
|
350
|
+
trackLayouts,
|
|
351
|
+
initialXDomain,
|
|
352
|
+
resolveInitialTrackDomain,
|
|
353
|
+
innerWidth,
|
|
354
|
+
sharedTransform,
|
|
355
|
+
independentTransforms,
|
|
356
|
+
cancelPendingZoom,
|
|
357
|
+
cancelViewportDrag,
|
|
358
|
+
clearHover,
|
|
359
|
+
editorSeriesOptions,
|
|
360
|
+
configureZoom,
|
|
361
|
+
})
|
|
347
362
|
const requestViewportReset = (event: MouseEvent) => {
|
|
348
363
|
if (isPresentationMode.value || !props.zoomable || !isZoomMode.value) return
|
|
349
364
|
if (props.displayMode === 'independent') {
|
|
@@ -372,6 +387,7 @@ export function useWaveformViewport(context: ViewportContext) {
|
|
|
372
387
|
finishViewportDrag,
|
|
373
388
|
cancelViewportDrag,
|
|
374
389
|
resetViewport,
|
|
390
|
+
setViewportDomain,
|
|
375
391
|
requestViewportReset,
|
|
376
392
|
}
|
|
377
393
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { nextTick, type ComputedRef, type Ref, type ShallowRef } from 'vue'
|
|
2
|
+
import type { ZoomTransform } from 'd3'
|
|
3
|
+
|
|
4
|
+
import type { TrackLayout } from '../core/types'
|
|
5
|
+
import type { ResolvedWaveformChartProps } from '../core/waveformChartTypes'
|
|
6
|
+
import type { AnnotationSeriesCandidate } from '../annotation'
|
|
7
|
+
import { constrainZoomDomain, transformForDomain } from './zoomConstraints'
|
|
8
|
+
|
|
9
|
+
interface ViewportDomainContext {
|
|
10
|
+
props: ResolvedWaveformChartProps
|
|
11
|
+
trackLayouts: ComputedRef<TrackLayout[]>
|
|
12
|
+
initialXDomain: ComputedRef<[number, number]>
|
|
13
|
+
resolveInitialTrackDomain: (track: TrackLayout) => [number, number]
|
|
14
|
+
innerWidth: ComputedRef<number>
|
|
15
|
+
sharedTransform: ShallowRef<ZoomTransform>
|
|
16
|
+
independentTransforms: ShallowRef<ZoomTransform[]>
|
|
17
|
+
cancelPendingZoom: () => void
|
|
18
|
+
cancelViewportDrag: () => void
|
|
19
|
+
clearHover: () => void
|
|
20
|
+
editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>
|
|
21
|
+
configureZoom: () => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createViewportDomainSetter(context: ViewportDomainContext) {
|
|
25
|
+
return (domain: [number, number], trackIndex?: number) => {
|
|
26
|
+
if (!Number.isFinite(domain[0]) || !Number.isFinite(domain[1]) || domain[0] === domain[1])
|
|
27
|
+
return
|
|
28
|
+
context.cancelPendingZoom()
|
|
29
|
+
context.cancelViewportDrag()
|
|
30
|
+
if (context.props.displayMode === 'independent') {
|
|
31
|
+
const indexes =
|
|
32
|
+
trackIndex === undefined
|
|
33
|
+
? context.trackLayouts.value.map((track) => track.index)
|
|
34
|
+
: [trackIndex]
|
|
35
|
+
const nextTransforms = [...context.independentTransforms.value]
|
|
36
|
+
indexes.forEach((index) => {
|
|
37
|
+
const track = context.trackLayouts.value.find((item) => item.index === index)
|
|
38
|
+
if (!track) return
|
|
39
|
+
const boundary = context.resolveInitialTrackDomain(track)
|
|
40
|
+
const constrained = constrainZoomDomain(domain, boundary, [track.seriesList], context.props)
|
|
41
|
+
nextTransforms[index] = transformForDomain(constrained, boundary, track.width)
|
|
42
|
+
})
|
|
43
|
+
context.independentTransforms.value = nextTransforms
|
|
44
|
+
} else {
|
|
45
|
+
const boundary = context.initialXDomain.value
|
|
46
|
+
const groups = context.trackLayouts.value
|
|
47
|
+
.filter((track) => track.hasVisibleSeries)
|
|
48
|
+
.map((track) => track.seriesList)
|
|
49
|
+
const constrained = constrainZoomDomain(domain, boundary, groups, context.props)
|
|
50
|
+
context.sharedTransform.value = transformForDomain(
|
|
51
|
+
constrained,
|
|
52
|
+
boundary,
|
|
53
|
+
context.innerWidth.value,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
context.clearHover()
|
|
57
|
+
context.editorSeriesOptions.value = []
|
|
58
|
+
void nextTick(context.configureZoom)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -119,6 +119,108 @@ describe('WaveformChart viewport reset', () => {
|
|
|
119
119
|
expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1000')
|
|
120
120
|
})
|
|
121
121
|
|
|
122
|
+
it('keeps a controlled shared viewport when the data reference changes', async () => {
|
|
123
|
+
const wrapper = await mountSizedChart(
|
|
124
|
+
{
|
|
125
|
+
kind: 'points',
|
|
126
|
+
points: [
|
|
127
|
+
{ x: 0, y: 0 },
|
|
128
|
+
{ x: 1, y: 1 },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
{ displayMode: 'separated' },
|
|
132
|
+
)
|
|
133
|
+
const chart = wrapper.vm as unknown as {
|
|
134
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void
|
|
135
|
+
}
|
|
136
|
+
chart.setViewportDomain([0.25, 0.75])
|
|
137
|
+
await flushPromises()
|
|
138
|
+
|
|
139
|
+
const startBefore = Number(wrapper.get('.waveform-chart__axis-endpoint--start').text())
|
|
140
|
+
const endBefore = Number(wrapper.get('.waveform-chart__axis-endpoint--end').text())
|
|
141
|
+
expect(startBefore).toBeGreaterThan(0)
|
|
142
|
+
expect(endBefore).toBeLessThan(1000)
|
|
143
|
+
|
|
144
|
+
await wrapper.setProps({
|
|
145
|
+
data: {
|
|
146
|
+
kind: 'points',
|
|
147
|
+
points: [
|
|
148
|
+
{ x: 0, y: 10 },
|
|
149
|
+
{ x: 1, y: 11 },
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
await flushPromises()
|
|
154
|
+
|
|
155
|
+
expect(Number(wrapper.get('.waveform-chart__axis-endpoint--start').text())).toBeCloseTo(
|
|
156
|
+
startBefore,
|
|
157
|
+
10,
|
|
158
|
+
)
|
|
159
|
+
expect(Number(wrapper.get('.waveform-chart__axis-endpoint--end').text())).toBeCloseTo(
|
|
160
|
+
endBefore,
|
|
161
|
+
10,
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('keeps independently controlled track viewports when the data reference changes', async () => {
|
|
166
|
+
const wrapper = await mountSizedChart(gridSeries(2), {
|
|
167
|
+
displayMode: 'independent',
|
|
168
|
+
grid: { rowCount: 1, columnCount: 2 },
|
|
169
|
+
})
|
|
170
|
+
const chart = wrapper.vm as unknown as {
|
|
171
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void
|
|
172
|
+
}
|
|
173
|
+
chart.setViewportDomain([0.2, 0.8], 0)
|
|
174
|
+
chart.setViewportDomain([0.1, 0.9], 1)
|
|
175
|
+
await flushPromises()
|
|
176
|
+
|
|
177
|
+
const endpoints = () =>
|
|
178
|
+
wrapper.findAll('.waveform-chart__track').map((track) => ({
|
|
179
|
+
start: track.get('.waveform-chart__axis-endpoint--start').text(),
|
|
180
|
+
end: track.get('.waveform-chart__axis-endpoint--end').text(),
|
|
181
|
+
}))
|
|
182
|
+
const endpointsBefore = endpoints()
|
|
183
|
+
expect(Number(endpointsBefore[0].start)).toBeGreaterThan(0)
|
|
184
|
+
expect(Number(endpointsBefore[0].end)).toBeLessThan(1000)
|
|
185
|
+
expect(Number(endpointsBefore[1].start)).toBeGreaterThan(0)
|
|
186
|
+
expect(Number(endpointsBefore[1].end)).toBeLessThan(1000)
|
|
187
|
+
|
|
188
|
+
await wrapper.setProps({ data: gridSeries(2) })
|
|
189
|
+
await flushPromises()
|
|
190
|
+
|
|
191
|
+
const endpointsAfter = endpoints()
|
|
192
|
+
endpointsAfter.forEach((endpoint, index) => {
|
|
193
|
+
expect(Number(endpoint.start)).toBeCloseTo(Number(endpointsBefore[index].start), 10)
|
|
194
|
+
expect(Number(endpoint.end)).toBeCloseTo(Number(endpointsBefore[index].end), 10)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('ignores invalid domains and applies viewport constraints', async () => {
|
|
199
|
+
const wrapper = await mountSizedChart(
|
|
200
|
+
{
|
|
201
|
+
kind: 'points',
|
|
202
|
+
points: [
|
|
203
|
+
{ x: 0, y: 0 },
|
|
204
|
+
{ x: 1, y: 1 },
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
{ displayMode: 'separated', minZoomSpan: 0.2 },
|
|
208
|
+
)
|
|
209
|
+
const chart = wrapper.vm as unknown as {
|
|
210
|
+
setViewportDomain: (domain: [number, number], trackIndex?: number) => void
|
|
211
|
+
}
|
|
212
|
+
chart.setViewportDomain([Number.NaN, 0.5])
|
|
213
|
+
await flushPromises()
|
|
214
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
215
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
|
|
216
|
+
|
|
217
|
+
chart.setViewportDomain([0.49, 0.51])
|
|
218
|
+
await flushPromises()
|
|
219
|
+
const start = Number(wrapper.get('.waveform-chart__axis-endpoint--start').text())
|
|
220
|
+
const end = Number(wrapper.get('.waveform-chart__axis-endpoint--end').text())
|
|
221
|
+
expect(end - start).toBeGreaterThanOrEqual(200)
|
|
222
|
+
})
|
|
223
|
+
|
|
122
224
|
it('resets shared and independent explicit domains to their included nice bounds', async () => {
|
|
123
225
|
const shared = await mountSizedChart(
|
|
124
226
|
{
|