waveform-analysis 0.1.37 → 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 +45 -30
- 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/App.test.ts +2 -2
- 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/legendVisibility.test.ts +34 -17
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +29 -3
- package/src/components/waveformChartCases/presentationMode.test.ts +16 -3
- package/src/components/waveformChartCases/viewportReset.test.ts +102 -0
- package/src/data/simulatedWaveforms.test.ts +7 -3
- package/src/data/simulatedWaveforms.ts +14 -1
- package/src/test/waveformChart.ts +12 -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
package/src/App.test.ts
CHANGED
|
@@ -235,7 +235,7 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
235
235
|
tracks.map((track) =>
|
|
236
236
|
track.findAll('.waveform-chart__series').map((item) => item.attributes('data-series-name')),
|
|
237
237
|
),
|
|
238
|
-
).toEqual([['正弦基波'], ['谐波扰动'], ['阻尼振荡'], ['阶跃响应']])
|
|
238
|
+
).toEqual([['正弦基波'], ['谐波扰动', '谐波对比'], ['阻尼振荡'], ['阶跃响应']])
|
|
239
239
|
expect(
|
|
240
240
|
tracks
|
|
241
241
|
.slice(1)
|
|
@@ -253,6 +253,7 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
253
253
|
expect(simulatedSeries.map((item) => item.name)).toEqual([
|
|
254
254
|
'正弦基波',
|
|
255
255
|
'谐波扰动',
|
|
256
|
+
'谐波对比',
|
|
256
257
|
'阻尼振荡',
|
|
257
258
|
'阶跃响应',
|
|
258
259
|
'脉冲响应',
|
|
@@ -321,7 +322,6 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
321
322
|
const styleSelect = frameControls.findAllComponents(Select)[0]
|
|
322
323
|
|
|
323
324
|
expect(colorPickers).toHaveLength(2)
|
|
324
|
-
|
|
325
325
|
const initialFrames = wrapper.findAll('.waveform-chart__plot-frame')
|
|
326
326
|
expect(initialFrames.length > 1 && initialFrames.every((frame) => frame.attributes('stroke-width') === '2')).toBe(true)
|
|
327
327
|
|
|
@@ -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
|
+
}
|
|
@@ -16,13 +16,30 @@ function annotationEditorExists() {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
describe('WaveformChart', () => {
|
|
19
|
+
it('shows legends when a track contains at least two series', async () => {
|
|
20
|
+
const oneSeries = visibilitySeries()
|
|
21
|
+
if (oneSeries.kind === 'series') oneSeries.series = oneSeries.series.slice(0, 1)
|
|
22
|
+
const withoutLegend = await mountSizedChart(oneSeries, {
|
|
23
|
+
grid: { rowCount: 1, columnCount: 1 },
|
|
24
|
+
})
|
|
25
|
+
expect(withoutLegend.findAll('.waveform-chart__legend')).toHaveLength(0)
|
|
26
|
+
|
|
27
|
+
const twoSeries = visibilitySeries()
|
|
28
|
+
if (twoSeries.kind === 'series') twoSeries.series = twoSeries.series.slice(0, 2)
|
|
29
|
+
const withLegend = await mountSizedChart(twoSeries, {
|
|
30
|
+
grid: { rowCount: 1, columnCount: 1 },
|
|
31
|
+
})
|
|
32
|
+
expect(withLegend.findAll('.waveform-chart__legend')).toHaveLength(1)
|
|
33
|
+
expect(withLegend.find('.waveform-chart__legend').attributes('data-position')).toBe('top-right')
|
|
34
|
+
})
|
|
35
|
+
|
|
19
36
|
it('resolves legend positions by stable track id across pages', async () => {
|
|
20
37
|
const wrapper = await mountSizedChart(
|
|
21
38
|
{
|
|
22
39
|
kind: 'series',
|
|
23
|
-
series: Array.from({ length:
|
|
40
|
+
series: Array.from({ length: 12 }, (_, index) => ({
|
|
24
41
|
id: `series-${index}`,
|
|
25
|
-
trackId: `frame-${Math.floor(index /
|
|
42
|
+
trackId: `frame-${Math.floor(index / 3)}`,
|
|
26
43
|
name: `series ${index}`,
|
|
27
44
|
data: {
|
|
28
45
|
kind: 'points' as const,
|
|
@@ -75,9 +92,9 @@ describe('WaveformChart', () => {
|
|
|
75
92
|
const wrapper = await mountSizedChart(
|
|
76
93
|
{
|
|
77
94
|
kind: 'series',
|
|
78
|
-
series: Array.from({ length:
|
|
95
|
+
series: Array.from({ length: 6 }, (_, index) => ({
|
|
79
96
|
id: `series-${index}`,
|
|
80
|
-
trackId: `frame-${Math.floor(index /
|
|
97
|
+
trackId: `frame-${Math.floor(index / 3)}`,
|
|
81
98
|
name: `series ${index}`,
|
|
82
99
|
data: {
|
|
83
100
|
kind: 'points',
|
|
@@ -112,7 +129,7 @@ describe('WaveformChart', () => {
|
|
|
112
129
|
})
|
|
113
130
|
|
|
114
131
|
const items = wrapper.findAll('.waveform-chart__legend-item')
|
|
115
|
-
expect(items).toHaveLength(
|
|
132
|
+
expect(items).toHaveLength(3)
|
|
116
133
|
expect(items.every((item) => item.attributes('disabled') !== undefined)).toBe(true)
|
|
117
134
|
expect(wrapper.get('.waveform-legend__panel').classes()).not.toContain(
|
|
118
135
|
'waveform-legend__panel--interactive',
|
|
@@ -128,7 +145,7 @@ describe('WaveformChart', () => {
|
|
|
128
145
|
overlayMode: 'multi-axis',
|
|
129
146
|
})
|
|
130
147
|
|
|
131
|
-
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(
|
|
148
|
+
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(3)
|
|
132
149
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
|
133
150
|
const annotationLayer = wrapper.get('.waveform-annotation-layer').element
|
|
134
151
|
const legendLayer = wrapper.get('.waveform-chart__legend-layer').element
|
|
@@ -143,8 +160,8 @@ describe('WaveformChart', () => {
|
|
|
143
160
|
|
|
144
161
|
expect(
|
|
145
162
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
146
|
-
).toEqual(['low'])
|
|
147
|
-
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(
|
|
163
|
+
).toEqual(['low', 'mid'])
|
|
164
|
+
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(2)
|
|
148
165
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(false)
|
|
149
166
|
expect(wrapper.findAll('.waveform-chart__legend-item')[1].classes()).toContain('is-hidden')
|
|
150
167
|
expect(wrapper.findAll('.waveform-chart__legend-item')[1].attributes('aria-pressed')).toBe(
|
|
@@ -170,12 +187,12 @@ describe('WaveformChart', () => {
|
|
|
170
187
|
)
|
|
171
188
|
flushAnimationFrames()
|
|
172
189
|
await flushPromises()
|
|
173
|
-
expect(wrapper.findAll('.waveform-chart__tooltip-series')).toHaveLength(
|
|
190
|
+
expect(wrapper.findAll('.waveform-chart__tooltip-series')).toHaveLength(2)
|
|
174
191
|
expect(wrapper.get('.waveform-chart__tooltip-series').text()).toContain('低量程')
|
|
175
192
|
|
|
176
193
|
await wrapper.findAll('.waveform-chart__legend-item')[1].trigger('click')
|
|
177
194
|
await flushPromises()
|
|
178
|
-
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(
|
|
195
|
+
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(3)
|
|
179
196
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
|
180
197
|
expect(wrapper.emitted('series-visibility-change')?.at(-1)).toEqual([
|
|
181
198
|
{ seriesId: 'high', visible: true, hiddenSeriesIds: [] },
|
|
@@ -191,20 +208,20 @@ describe('WaveformChart', () => {
|
|
|
191
208
|
|
|
192
209
|
expect(
|
|
193
210
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
194
|
-
).toEqual(['low'])
|
|
211
|
+
).toEqual(['low', 'mid'])
|
|
195
212
|
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
|
196
213
|
expect(wrapper.emitted('update:hidden-series-ids')?.at(-1)).toEqual([
|
|
197
214
|
['high', 'temporarily-absent', 'low'],
|
|
198
215
|
])
|
|
199
216
|
expect(
|
|
200
217
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
201
|
-
).toEqual(['low'])
|
|
218
|
+
).toEqual(['low', 'mid'])
|
|
202
219
|
|
|
203
220
|
await wrapper.setProps({ hiddenSeriesIds: ['low'] })
|
|
204
221
|
await flushPromises()
|
|
205
222
|
expect(
|
|
206
223
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
207
|
-
).toEqual(['high'])
|
|
224
|
+
).toEqual(['high', 'mid'])
|
|
208
225
|
})
|
|
209
226
|
|
|
210
227
|
it('retains uncontrolled visibility by stable ID and clears removed IDs', async () => {
|
|
@@ -223,7 +240,7 @@ describe('WaveformChart', () => {
|
|
|
223
240
|
await flushPromises()
|
|
224
241
|
expect(
|
|
225
242
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
226
|
-
).toEqual(['low'])
|
|
243
|
+
).toEqual(['mid', 'low'])
|
|
227
244
|
|
|
228
245
|
await wrapper.setProps({
|
|
229
246
|
data: {
|
|
@@ -234,12 +251,12 @@ describe('WaveformChart', () => {
|
|
|
234
251
|
await flushPromises()
|
|
235
252
|
await wrapper.setProps({ data: original })
|
|
236
253
|
await flushPromises()
|
|
237
|
-
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(
|
|
254
|
+
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(3)
|
|
238
255
|
})
|
|
239
256
|
|
|
240
257
|
it('keeps a recoverable legend and stops chart interaction when every series is hidden', async () => {
|
|
241
258
|
const wrapper = await mountSizedChart(visibilitySeries(), {
|
|
242
|
-
defaultHiddenSeriesIds: ['low', 'high'],
|
|
259
|
+
defaultHiddenSeriesIds: ['low', 'high', 'mid'],
|
|
243
260
|
grid: { rowCount: 1, columnCount: 1 },
|
|
244
261
|
legend: { interactive: true },
|
|
245
262
|
overlayMode: 'multi-axis',
|
|
@@ -248,7 +265,7 @@ describe('WaveformChart', () => {
|
|
|
248
265
|
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(0)
|
|
249
266
|
expect(wrapper.findAll('.waveform-chart__axis')).toHaveLength(0)
|
|
250
267
|
expect(wrapper.findAll('.waveform-chart__overlay')).toHaveLength(0)
|
|
251
|
-
expect(wrapper.findAll('.waveform-chart__legend-item')).toHaveLength(
|
|
268
|
+
expect(wrapper.findAll('.waveform-chart__legend-item')).toHaveLength(3)
|
|
252
269
|
expect(wrapper.get('.waveform-track__no-visible-series').text()).toBe('暂无可见曲线')
|
|
253
270
|
|
|
254
271
|
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
|
@@ -177,6 +177,18 @@ describe('WaveformChart', () => {
|
|
|
177
177
|
],
|
|
178
178
|
},
|
|
179
179
|
},
|
|
180
|
+
{
|
|
181
|
+
id: 'reference',
|
|
182
|
+
trackId: 'frame-1',
|
|
183
|
+
name: 'REF_CH_1',
|
|
184
|
+
data: {
|
|
185
|
+
kind: 'points',
|
|
186
|
+
points: [
|
|
187
|
+
{ x: 0, y: 0.25 },
|
|
188
|
+
{ x: 1, y: 1.25 },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
180
192
|
],
|
|
181
193
|
},
|
|
182
194
|
{ frameNumber: 1, grid: { rowCount: 2, columnCount: 1 } },
|
|
@@ -186,7 +198,7 @@ describe('WaveformChart', () => {
|
|
|
186
198
|
expect(tracks).toHaveLength(2)
|
|
187
199
|
expect(
|
|
188
200
|
tracks[0].findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
189
|
-
).toEqual(['primary', 'comparison'])
|
|
201
|
+
).toEqual(['primary', 'comparison', 'reference'])
|
|
190
202
|
expect(
|
|
191
203
|
tracks[1].findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
192
204
|
).toEqual(['second-frame'])
|
|
@@ -203,12 +215,13 @@ describe('WaveformChart', () => {
|
|
|
203
215
|
expect(legend.findAll('.waveform-chart__legend-item').map((item) => item.text())).toEqual([
|
|
204
216
|
'BT2_2M',
|
|
205
217
|
'TEST_CH_1',
|
|
218
|
+
'REF_CH_1',
|
|
206
219
|
])
|
|
207
220
|
expect(
|
|
208
221
|
legend
|
|
209
222
|
.findAll('.waveform-legend__swatch')
|
|
210
223
|
.map((swatch) => swatch.get('path').attributes('stroke')),
|
|
211
|
-
).toEqual(['#0960bd', '#2ca02c'])
|
|
224
|
+
).toEqual(['#0960bd', '#2ca02c', '#d62728'])
|
|
212
225
|
expect(wrapper.findAll('.waveform-chart__watermark').map((item) => item.text())).toEqual([
|
|
213
226
|
'1',
|
|
214
227
|
'2',
|
|
@@ -232,10 +245,11 @@ describe('WaveformChart', () => {
|
|
|
232
245
|
await flushPromises()
|
|
233
246
|
|
|
234
247
|
const tooltipSeries = wrapper.findAll('.waveform-chart__tooltip-series')
|
|
235
|
-
expect(tooltipSeries).toHaveLength(
|
|
248
|
+
expect(tooltipSeries).toHaveLength(3)
|
|
236
249
|
expect(tooltipSeries.map((item) => item.text())).toEqual([
|
|
237
250
|
expect.stringContaining('BT2_2M'),
|
|
238
251
|
expect.stringContaining('TEST_CH_1'),
|
|
252
|
+
expect.stringContaining('REF_CH_1'),
|
|
239
253
|
])
|
|
240
254
|
})
|
|
241
255
|
|
|
@@ -268,6 +282,18 @@ describe('WaveformChart', () => {
|
|
|
268
282
|
],
|
|
269
283
|
},
|
|
270
284
|
},
|
|
285
|
+
{
|
|
286
|
+
id: 'third',
|
|
287
|
+
trackId: 'shared',
|
|
288
|
+
name: 'third',
|
|
289
|
+
data: {
|
|
290
|
+
kind: 'points',
|
|
291
|
+
points: [
|
|
292
|
+
{ x: 0, y: 2 },
|
|
293
|
+
{ x: 1, y: 3 },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
271
297
|
],
|
|
272
298
|
},
|
|
273
299
|
{ grid: { rowCount: 1, columnCount: 1 } },
|
|
@@ -42,6 +42,19 @@ const presentationData: WaveformData = {
|
|
|
42
42
|
],
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
id: 'mid',
|
|
47
|
+
trackId: 'shared',
|
|
48
|
+
name: '中量程',
|
|
49
|
+
data: {
|
|
50
|
+
kind: 'points',
|
|
51
|
+
points: [
|
|
52
|
+
{ x: 0, y: 50 },
|
|
53
|
+
{ x: 1, y: 100 },
|
|
54
|
+
{ x: 2, y: 75 },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
45
58
|
{
|
|
46
59
|
id: 'other',
|
|
47
60
|
name: '其他通道',
|
|
@@ -150,9 +163,9 @@ describe('WaveformChart presentation mode', () => {
|
|
|
150
163
|
cancelable: true,
|
|
151
164
|
}),
|
|
152
165
|
)
|
|
153
|
-
wrapper
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
wrapper
|
|
167
|
+
.get('.waveform-chart__svg')
|
|
168
|
+
.element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }))
|
|
156
169
|
await wrapper.get('[data-annotation-id="note"]').trigger('contextmenu', {
|
|
157
170
|
clientX: width / 2,
|
|
158
171
|
clientY: height / 2,
|
|
@@ -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
|
{
|