waveform-analysis 0.1.36 → 0.1.38
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 +34 -33
- package/dist/index.cjs +1 -1
- package/dist/index.js +902 -806
- package/dist/style.css +1 -1
- package/dist/types/components/annotation/WaveformAnnotationEditor.vue.d.ts +4 -0
- package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +3 -1
- package/dist/types/components/core/constants.d.ts +3 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +2 -0
- package/package.json +1 -1
- package/src/App.test.ts +4 -3
- package/src/App.vue +1 -1
- package/src/components/WaveformChartView.vue +10 -1
- package/src/components/annotation/WaveformAnnotationEditor.css +35 -104
- package/src/components/annotation/WaveformAnnotationEditor.vue +87 -37
- package/src/components/annotation/components.test.ts +134 -20
- package/src/components/annotation/markup.test.ts +18 -2
- package/src/components/annotation/markup.ts +33 -0
- package/src/components/annotation/useWaveformChartAnnotations.ts +53 -12
- package/src/components/core/constants.test.ts +29 -0
- package/src/components/core/constants.ts +31 -9
- package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +13 -3
- package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +40 -0
- package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +24 -14
- package/src/components/waveformChartCases/legendVisibility.test.ts +44 -19
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +29 -3
- package/src/components/waveformChartCases/presentationMode.test.ts +26 -5
- package/src/components/waveformChartCases/renderingStyles.test.ts +1 -1
- package/src/components/waveformChartCases/xDomainStrategy.test.ts +60 -0
- package/src/data/simulatedWaveforms.test.ts +7 -3
- package/src/data/simulatedWaveforms.ts +14 -1
- package/src/test/waveformChart.ts +12 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import { flushPromises } from '@vue/test-utils'
|
|
1
|
+
import { DOMWrapper, flushPromises } from '@vue/test-utils'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
3
|
|
|
4
4
|
import { flushAnimationFrames, pendingAnimationFrameCount } from '../../test/setup'
|
|
5
5
|
|
|
6
6
|
import { mountSizedChart } from '../../test/waveformChart'
|
|
7
7
|
|
|
8
|
+
function getAnnotationEditor() {
|
|
9
|
+
const editor = Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
|
|
10
|
+
.filter((element) => element.closest('.waveform-annotation-editor'))
|
|
11
|
+
.at(-1)
|
|
12
|
+
if (!editor) throw new Error('Expected annotation editor modal to be mounted')
|
|
13
|
+
return new DOMWrapper(editor)
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
describe('WaveformChart', () => {
|
|
9
17
|
it('zooms only the active independent track and resets when the mode changes', async () => {
|
|
10
18
|
const wrapper = await mountSizedChart({
|
|
@@ -172,9 +180,10 @@ describe('WaveformChart', () => {
|
|
|
172
180
|
)
|
|
173
181
|
await flushPromises()
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
await
|
|
177
|
-
await
|
|
183
|
+
const editor = getAnnotationEditor()
|
|
184
|
+
await editor.get('textarea[aria-label="标注文本"]').setValue('峰值点')
|
|
185
|
+
await editor.get('button.ant-btn-primary').trigger('click')
|
|
186
|
+
await flushPromises()
|
|
178
187
|
|
|
179
188
|
const annotations = wrapper.emitted('update:annotations')?.at(-1)?.[0] as
|
|
180
189
|
Array<{ seriesId: string; x: number; y: number; text: string }> | undefined
|
|
@@ -209,10 +218,10 @@ describe('WaveformChart', () => {
|
|
|
209
218
|
}),
|
|
210
219
|
)
|
|
211
220
|
await flushPromises()
|
|
212
|
-
|
|
213
|
-
expect(
|
|
214
|
-
expect(
|
|
215
|
-
const textarea =
|
|
221
|
+
const editor = getAnnotationEditor()
|
|
222
|
+
expect(editor.attributes('role')).toBe('dialog')
|
|
223
|
+
expect(editor.find('.waveform-annotation-editor__content').exists()).toBe(true)
|
|
224
|
+
const textarea = editor.get('textarea[aria-label="标注文本"]')
|
|
216
225
|
const textareaContextMenu = new MouseEvent('contextmenu', {
|
|
217
226
|
bubbles: true,
|
|
218
227
|
cancelable: true,
|
|
@@ -220,7 +229,8 @@ describe('WaveformChart', () => {
|
|
|
220
229
|
expect(textarea.element.dispatchEvent(textareaContextMenu)).toBe(true)
|
|
221
230
|
expect(textareaContextMenu.defaultPrevented).toBe(false)
|
|
222
231
|
await textarea.setValue('右键标注')
|
|
223
|
-
await
|
|
232
|
+
await editor.get('button.ant-btn-primary').trigger('click')
|
|
233
|
+
await flushPromises()
|
|
224
234
|
|
|
225
235
|
// Annotation snaps to nearest sample point (x=1, y=5)
|
|
226
236
|
expect(wrapper.emitted('update:annotations')?.at(-1)?.[0]).toMatchObject([
|
|
@@ -278,20 +288,20 @@ describe('WaveformChart', () => {
|
|
|
278
288
|
)
|
|
279
289
|
await flushPromises()
|
|
280
290
|
expect(
|
|
281
|
-
|
|
282
|
-
.
|
|
291
|
+
getAnnotationEditor()
|
|
292
|
+
.get('select[aria-label="选择标注波形"]')
|
|
283
293
|
.findAll('option')
|
|
284
294
|
.map((item) => item.text()),
|
|
285
295
|
).toEqual(['通道 A'])
|
|
286
296
|
|
|
287
|
-
await
|
|
297
|
+
await getAnnotationEditor().get('.ant-modal-close').trigger('click')
|
|
288
298
|
overlays[0].element.dispatchEvent(
|
|
289
299
|
new MouseEvent('contextmenu', { clientX: 356, clientY: 230, bubbles: true }),
|
|
290
300
|
)
|
|
291
301
|
await flushPromises()
|
|
292
302
|
expect(
|
|
293
|
-
|
|
294
|
-
.
|
|
303
|
+
getAnnotationEditor()
|
|
304
|
+
.get('select[aria-label="选择标注波形"]')
|
|
295
305
|
.findAll('option')
|
|
296
306
|
.map((item) => item.text()),
|
|
297
307
|
).toEqual(['通道 B'])
|
|
@@ -7,14 +7,39 @@ import WaveformChartView from '../WaveformChartView.vue'
|
|
|
7
7
|
|
|
8
8
|
import { gridSeries, mountSizedChart, visibilitySeries } from '../../test/waveformChart'
|
|
9
9
|
|
|
10
|
+
function annotationEditorExists() {
|
|
11
|
+
return Boolean(
|
|
12
|
+
Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
|
|
13
|
+
.filter((element) => element.closest('.waveform-annotation-editor'))
|
|
14
|
+
.at(-1),
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
10
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
|
+
|
|
11
36
|
it('resolves legend positions by stable track id across pages', async () => {
|
|
12
37
|
const wrapper = await mountSizedChart(
|
|
13
38
|
{
|
|
14
39
|
kind: 'series',
|
|
15
|
-
series: Array.from({ length:
|
|
40
|
+
series: Array.from({ length: 12 }, (_, index) => ({
|
|
16
41
|
id: `series-${index}`,
|
|
17
|
-
trackId: `frame-${Math.floor(index /
|
|
42
|
+
trackId: `frame-${Math.floor(index / 3)}`,
|
|
18
43
|
name: `series ${index}`,
|
|
19
44
|
data: {
|
|
20
45
|
kind: 'points' as const,
|
|
@@ -67,9 +92,9 @@ describe('WaveformChart', () => {
|
|
|
67
92
|
const wrapper = await mountSizedChart(
|
|
68
93
|
{
|
|
69
94
|
kind: 'series',
|
|
70
|
-
series: Array.from({ length:
|
|
95
|
+
series: Array.from({ length: 6 }, (_, index) => ({
|
|
71
96
|
id: `series-${index}`,
|
|
72
|
-
trackId: `frame-${Math.floor(index /
|
|
97
|
+
trackId: `frame-${Math.floor(index / 3)}`,
|
|
73
98
|
name: `series ${index}`,
|
|
74
99
|
data: {
|
|
75
100
|
kind: 'points',
|
|
@@ -104,7 +129,7 @@ describe('WaveformChart', () => {
|
|
|
104
129
|
})
|
|
105
130
|
|
|
106
131
|
const items = wrapper.findAll('.waveform-chart__legend-item')
|
|
107
|
-
expect(items).toHaveLength(
|
|
132
|
+
expect(items).toHaveLength(3)
|
|
108
133
|
expect(items.every((item) => item.attributes('disabled') !== undefined)).toBe(true)
|
|
109
134
|
expect(wrapper.get('.waveform-legend__panel').classes()).not.toContain(
|
|
110
135
|
'waveform-legend__panel--interactive',
|
|
@@ -120,7 +145,7 @@ describe('WaveformChart', () => {
|
|
|
120
145
|
overlayMode: 'multi-axis',
|
|
121
146
|
})
|
|
122
147
|
|
|
123
|
-
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(
|
|
148
|
+
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(3)
|
|
124
149
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
|
125
150
|
const annotationLayer = wrapper.get('.waveform-annotation-layer').element
|
|
126
151
|
const legendLayer = wrapper.get('.waveform-chart__legend-layer').element
|
|
@@ -135,8 +160,8 @@ describe('WaveformChart', () => {
|
|
|
135
160
|
|
|
136
161
|
expect(
|
|
137
162
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
138
|
-
).toEqual(['low'])
|
|
139
|
-
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(
|
|
163
|
+
).toEqual(['low', 'mid'])
|
|
164
|
+
expect(wrapper.findAll('.waveform-chart__axis--y')).toHaveLength(2)
|
|
140
165
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(false)
|
|
141
166
|
expect(wrapper.findAll('.waveform-chart__legend-item')[1].classes()).toContain('is-hidden')
|
|
142
167
|
expect(wrapper.findAll('.waveform-chart__legend-item')[1].attributes('aria-pressed')).toBe(
|
|
@@ -162,12 +187,12 @@ describe('WaveformChart', () => {
|
|
|
162
187
|
)
|
|
163
188
|
flushAnimationFrames()
|
|
164
189
|
await flushPromises()
|
|
165
|
-
expect(wrapper.findAll('.waveform-chart__tooltip-series')).toHaveLength(
|
|
190
|
+
expect(wrapper.findAll('.waveform-chart__tooltip-series')).toHaveLength(2)
|
|
166
191
|
expect(wrapper.get('.waveform-chart__tooltip-series').text()).toContain('低量程')
|
|
167
192
|
|
|
168
193
|
await wrapper.findAll('.waveform-chart__legend-item')[1].trigger('click')
|
|
169
194
|
await flushPromises()
|
|
170
|
-
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(
|
|
195
|
+
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(3)
|
|
171
196
|
expect(wrapper.find('[data-annotation-id="high-note"]').exists()).toBe(true)
|
|
172
197
|
expect(wrapper.emitted('series-visibility-change')?.at(-1)).toEqual([
|
|
173
198
|
{ seriesId: 'high', visible: true, hiddenSeriesIds: [] },
|
|
@@ -183,20 +208,20 @@ describe('WaveformChart', () => {
|
|
|
183
208
|
|
|
184
209
|
expect(
|
|
185
210
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
186
|
-
).toEqual(['low'])
|
|
211
|
+
).toEqual(['low', 'mid'])
|
|
187
212
|
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
|
188
213
|
expect(wrapper.emitted('update:hidden-series-ids')?.at(-1)).toEqual([
|
|
189
214
|
['high', 'temporarily-absent', 'low'],
|
|
190
215
|
])
|
|
191
216
|
expect(
|
|
192
217
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
193
|
-
).toEqual(['low'])
|
|
218
|
+
).toEqual(['low', 'mid'])
|
|
194
219
|
|
|
195
220
|
await wrapper.setProps({ hiddenSeriesIds: ['low'] })
|
|
196
221
|
await flushPromises()
|
|
197
222
|
expect(
|
|
198
223
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
199
|
-
).toEqual(['high'])
|
|
224
|
+
).toEqual(['high', 'mid'])
|
|
200
225
|
})
|
|
201
226
|
|
|
202
227
|
it('retains uncontrolled visibility by stable ID and clears removed IDs', async () => {
|
|
@@ -215,7 +240,7 @@ describe('WaveformChart', () => {
|
|
|
215
240
|
await flushPromises()
|
|
216
241
|
expect(
|
|
217
242
|
wrapper.findAll('.waveform-chart__line').map((line) => line.attributes('data-series-id')),
|
|
218
|
-
).toEqual(['low'])
|
|
243
|
+
).toEqual(['mid', 'low'])
|
|
219
244
|
|
|
220
245
|
await wrapper.setProps({
|
|
221
246
|
data: {
|
|
@@ -226,12 +251,12 @@ describe('WaveformChart', () => {
|
|
|
226
251
|
await flushPromises()
|
|
227
252
|
await wrapper.setProps({ data: original })
|
|
228
253
|
await flushPromises()
|
|
229
|
-
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(
|
|
254
|
+
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(3)
|
|
230
255
|
})
|
|
231
256
|
|
|
232
257
|
it('keeps a recoverable legend and stops chart interaction when every series is hidden', async () => {
|
|
233
258
|
const wrapper = await mountSizedChart(visibilitySeries(), {
|
|
234
|
-
defaultHiddenSeriesIds: ['low', 'high'],
|
|
259
|
+
defaultHiddenSeriesIds: ['low', 'high', 'mid'],
|
|
235
260
|
grid: { rowCount: 1, columnCount: 1 },
|
|
236
261
|
legend: { interactive: true },
|
|
237
262
|
overlayMode: 'multi-axis',
|
|
@@ -240,7 +265,7 @@ describe('WaveformChart', () => {
|
|
|
240
265
|
expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(0)
|
|
241
266
|
expect(wrapper.findAll('.waveform-chart__axis')).toHaveLength(0)
|
|
242
267
|
expect(wrapper.findAll('.waveform-chart__overlay')).toHaveLength(0)
|
|
243
|
-
expect(wrapper.findAll('.waveform-chart__legend-item')).toHaveLength(
|
|
268
|
+
expect(wrapper.findAll('.waveform-chart__legend-item')).toHaveLength(3)
|
|
244
269
|
expect(wrapper.get('.waveform-track__no-visible-series').text()).toBe('暂无可见曲线')
|
|
245
270
|
|
|
246
271
|
await wrapper.findAll('.waveform-chart__legend-item')[0].trigger('click')
|
|
@@ -273,7 +298,7 @@ describe('WaveformChart', () => {
|
|
|
273
298
|
)
|
|
274
299
|
flushAnimationFrames()
|
|
275
300
|
await flushPromises()
|
|
276
|
-
expect(
|
|
301
|
+
expect(annotationEditorExists()).toBe(true)
|
|
277
302
|
|
|
278
303
|
const controller = wrapper.getComponent(WaveformChartView).props('controller') as unknown as {
|
|
279
304
|
annotationInteraction: { editorDraft: { value: { annotation: { seriesId: string } } | null } }
|
|
@@ -287,7 +312,7 @@ describe('WaveformChart', () => {
|
|
|
287
312
|
expect(item).toBeDefined()
|
|
288
313
|
await item!.trigger('click')
|
|
289
314
|
await flushPromises()
|
|
290
|
-
expect(
|
|
315
|
+
expect(annotationEditorExists()).toBe(false)
|
|
291
316
|
})
|
|
292
317
|
|
|
293
318
|
it('renders independent cells with separate x axes and overlays', async () => {
|
|
@@ -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', '#
|
|
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 } },
|
|
@@ -5,6 +5,14 @@ import { flushAnimationFrames } from '../../test/setup'
|
|
|
5
5
|
import { mountSizedChart } from '../../test/waveformChart'
|
|
6
6
|
import type { WaveformData, WaveformDisplayMode } from '../data/types'
|
|
7
7
|
|
|
8
|
+
function annotationEditorExists() {
|
|
9
|
+
return Boolean(
|
|
10
|
+
Array.from(document.body.querySelectorAll<HTMLElement>('[role="dialog"]'))
|
|
11
|
+
.filter((element) => element.closest('.waveform-annotation-editor'))
|
|
12
|
+
.at(-1),
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
8
16
|
const presentationData: WaveformData = {
|
|
9
17
|
kind: 'series',
|
|
10
18
|
series: [
|
|
@@ -34,6 +42,19 @@ const presentationData: WaveformData = {
|
|
|
34
42
|
],
|
|
35
43
|
},
|
|
36
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
|
+
},
|
|
37
58
|
{
|
|
38
59
|
id: 'other',
|
|
39
60
|
name: '其他通道',
|
|
@@ -142,9 +163,9 @@ describe('WaveformChart presentation mode', () => {
|
|
|
142
163
|
cancelable: true,
|
|
143
164
|
}),
|
|
144
165
|
)
|
|
145
|
-
wrapper
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
wrapper
|
|
167
|
+
.get('.waveform-chart__svg')
|
|
168
|
+
.element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }))
|
|
148
169
|
await wrapper.get('[data-annotation-id="note"]').trigger('contextmenu', {
|
|
149
170
|
clientX: width / 2,
|
|
150
171
|
clientY: height / 2,
|
|
@@ -282,13 +303,13 @@ describe('WaveformChart presentation mode', () => {
|
|
|
282
303
|
}),
|
|
283
304
|
)
|
|
284
305
|
await flushPromises()
|
|
285
|
-
expect(
|
|
306
|
+
expect(annotationEditorExists()).toBe(true)
|
|
286
307
|
|
|
287
308
|
await wrapper.setProps({ presentationMode: true })
|
|
288
309
|
await flushPromises()
|
|
289
310
|
|
|
290
311
|
expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
|
|
291
|
-
expect(
|
|
312
|
+
expect(annotationEditorExists()).toBe(false)
|
|
292
313
|
expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
|
|
293
314
|
|
|
294
315
|
await wrapper.setProps({ presentationMode: false })
|
|
@@ -181,7 +181,7 @@ describe('WaveformChart', () => {
|
|
|
181
181
|
])
|
|
182
182
|
expect(stepSwatchPaths[0]?.attributes()).toMatchObject({
|
|
183
183
|
d: 'M1 8H25',
|
|
184
|
-
stroke: '#
|
|
184
|
+
stroke: '#2ca02c',
|
|
185
185
|
'stroke-width': '1.5',
|
|
186
186
|
})
|
|
187
187
|
expect(stepSwatchPaths[1]?.attributes()).toMatchObject({
|
|
@@ -93,4 +93,64 @@ describe('WaveformChart x domain strategy', () => {
|
|
|
93
93
|
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('10000')
|
|
94
94
|
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('15000')
|
|
95
95
|
})
|
|
96
|
+
|
|
97
|
+
it('keeps an explicit viewport wider than the data and leaves uncovered time blank', async () => {
|
|
98
|
+
const wrapper = await mountSizedChart(
|
|
99
|
+
{
|
|
100
|
+
kind: 'points',
|
|
101
|
+
points: [
|
|
102
|
+
{ x: 0, y: 0 },
|
|
103
|
+
{ x: 10, y: 1 },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
{ timeUnit: 's', initialXDomain: [-5, 20] },
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const track = wrapper.get('.waveform-chart__track')
|
|
110
|
+
const width = Number(track.attributes('data-track-width'))
|
|
111
|
+
const path = track.get('.waveform-chart__line').attributes('d') ?? ''
|
|
112
|
+
const xCoordinates = Array.from(path.matchAll(/(?:M|L)([\d.-]+)/g)).map((match) =>
|
|
113
|
+
Number(match[1]),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
expect(track.get('.waveform-chart__axis-endpoint--start').text()).toBe('-5')
|
|
117
|
+
expect(track.get('.waveform-chart__axis-endpoint--end').text()).toBe('20')
|
|
118
|
+
expect(xCoordinates.length).toBeGreaterThan(0)
|
|
119
|
+
expect(Math.min(...xCoordinates)).toBeGreaterThanOrEqual(0)
|
|
120
|
+
expect(Math.max(...xCoordinates)).toBeLessThanOrEqual(width)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('applies independent explicit viewports per track, including empty time regions', async () => {
|
|
124
|
+
const wrapper = await mountSizedChart(
|
|
125
|
+
{
|
|
126
|
+
kind: 'series',
|
|
127
|
+
series: [
|
|
128
|
+
{
|
|
129
|
+
id: 'first',
|
|
130
|
+
name: '第一轨',
|
|
131
|
+
data: { kind: 'points', points: [{ x: 0, y: 0 }, { x: 10, y: 1 }] },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'second',
|
|
135
|
+
name: '第二轨',
|
|
136
|
+
data: { kind: 'points', points: [{ x: 100, y: 0 }, { x: 110, y: 1 }] },
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
displayMode: 'independent',
|
|
142
|
+
grid: { rowCount: 1, columnCount: 2 },
|
|
143
|
+
timeUnit: 's',
|
|
144
|
+
initialXDomains: { first: [-5, 20], second: [95, 120] },
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const tracks = wrapper.findAll('.waveform-chart__track')
|
|
149
|
+
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-5')
|
|
150
|
+
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('20')
|
|
151
|
+
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('95')
|
|
152
|
+
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('120')
|
|
153
|
+
expect(tracks[0]?.find('.waveform-chart__line').exists()).toBe(true)
|
|
154
|
+
expect(tracks[1]?.find('.waveform-chart__line').exists()).toBe(true)
|
|
155
|
+
})
|
|
96
156
|
})
|
|
@@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
3
3
|
import { createSimulatedWaveformData } from './simulatedWaveforms'
|
|
4
4
|
|
|
5
5
|
describe('simulated waveform data', () => {
|
|
6
|
-
it('creates deterministic, finite
|
|
6
|
+
it('creates deterministic, finite seven-channel data with a two-series second frame', () => {
|
|
7
7
|
const first = createSimulatedWaveformData()
|
|
8
8
|
const second = createSimulatedWaveformData()
|
|
9
9
|
|
|
@@ -11,8 +11,12 @@ describe('simulated waveform data', () => {
|
|
|
11
11
|
expect(first.kind).toBe('series')
|
|
12
12
|
if (first.kind !== 'series') return
|
|
13
13
|
|
|
14
|
-
expect(first.series).toHaveLength(
|
|
15
|
-
expect(new Set(first.series.map((series) => series.id)).size).toBe(
|
|
14
|
+
expect(first.series).toHaveLength(7)
|
|
15
|
+
expect(new Set(first.series.map((series) => series.id)).size).toBe(7)
|
|
16
|
+
const secondFrame = first.series.filter(
|
|
17
|
+
(series) => series.trackId === 'simulated-harmonic-frame',
|
|
18
|
+
)
|
|
19
|
+
expect(secondFrame).toHaveLength(2)
|
|
16
20
|
expect(new Set(first.series.map((series) => series.shotNo))).toEqual(new Set(['13300']))
|
|
17
21
|
first.series.forEach((series) => {
|
|
18
22
|
expect(series.data.kind).toBe('points')
|
|
@@ -13,7 +13,7 @@ type ErrorGenerator = (
|
|
|
13
13
|
|
|
14
14
|
interface SimulatedSeriesDefinition extends Pick<
|
|
15
15
|
WaveformSeries,
|
|
16
|
-
'id' | 'shotNo' | 'name' | 'unit' | 'lineType' | 'pointType' | 'errorBar'
|
|
16
|
+
'id' | 'trackId' | 'shotNo' | 'name' | 'unit' | 'color' | 'lineType' | 'pointType' | 'errorBar'
|
|
17
17
|
> {
|
|
18
18
|
signal: SignalGenerator
|
|
19
19
|
errors?: ErrorGenerator
|
|
@@ -60,6 +60,7 @@ const seriesDefinitions: SimulatedSeriesDefinition[] = [
|
|
|
60
60
|
},
|
|
61
61
|
{
|
|
62
62
|
id: 'simulated-harmonic',
|
|
63
|
+
trackId: 'simulated-harmonic-frame',
|
|
63
64
|
shotNo: '13300',
|
|
64
65
|
name: '谐波扰动',
|
|
65
66
|
unit: 'V',
|
|
@@ -68,6 +69,18 @@ const seriesDefinitions: SimulatedSeriesDefinition[] = [
|
|
|
68
69
|
signal: (time) =>
|
|
69
70
|
0.9 * Math.sin(TWO_PI * 0.55 * time) + 0.28 * Math.sin(TWO_PI * 2.2 * time + 0.4),
|
|
70
71
|
},
|
|
72
|
+
{
|
|
73
|
+
id: 'simulated-harmonic-reference',
|
|
74
|
+
trackId: 'simulated-harmonic-frame',
|
|
75
|
+
shotNo: '13300',
|
|
76
|
+
name: '谐波对比',
|
|
77
|
+
unit: 'V',
|
|
78
|
+
color: '#2ca02c',
|
|
79
|
+
lineType: 'linear',
|
|
80
|
+
pointType: 'none',
|
|
81
|
+
signal: (time) =>
|
|
82
|
+
0.62 * Math.sin(TWO_PI * 0.55 * time + 0.55) + 0.18 * Math.sin(TWO_PI * 2.2 * time - 0.2),
|
|
83
|
+
},
|
|
71
84
|
{
|
|
72
85
|
id: 'simulated-damped',
|
|
73
86
|
shotNo: '13300',
|
|
@@ -58,6 +58,18 @@ export function visibilitySeries(): WaveformData {
|
|
|
58
58
|
],
|
|
59
59
|
},
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
id: 'mid',
|
|
63
|
+
trackId: 'shared-frame',
|
|
64
|
+
name: '中量程',
|
|
65
|
+
data: {
|
|
66
|
+
kind: 'points',
|
|
67
|
+
points: [
|
|
68
|
+
{ x: 0, y: 100 },
|
|
69
|
+
{ x: 1, y: 200 },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
61
73
|
],
|
|
62
74
|
}
|
|
63
75
|
}
|