waveform-analysis 0.1.36 → 0.1.37
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 +3 -3
- 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 +3 -2
- 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 +10 -2
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +1 -1
- package/src/components/waveformChartCases/presentationMode.test.ts +10 -2
- package/src/components/waveformChartCases/renderingStyles.test.ts +1 -1
- package/src/components/waveformChartCases/xDomainStrategy.test.ts +60 -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,6 +7,14 @@ 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', () => {
|
|
11
19
|
it('resolves legend positions by stable track id across pages', async () => {
|
|
12
20
|
const wrapper = await mountSizedChart(
|
|
@@ -273,7 +281,7 @@ describe('WaveformChart', () => {
|
|
|
273
281
|
)
|
|
274
282
|
flushAnimationFrames()
|
|
275
283
|
await flushPromises()
|
|
276
|
-
expect(
|
|
284
|
+
expect(annotationEditorExists()).toBe(true)
|
|
277
285
|
|
|
278
286
|
const controller = wrapper.getComponent(WaveformChartView).props('controller') as unknown as {
|
|
279
287
|
annotationInteraction: { editorDraft: { value: { annotation: { seriesId: string } } | null } }
|
|
@@ -287,7 +295,7 @@ describe('WaveformChart', () => {
|
|
|
287
295
|
expect(item).toBeDefined()
|
|
288
296
|
await item!.trigger('click')
|
|
289
297
|
await flushPromises()
|
|
290
|
-
expect(
|
|
298
|
+
expect(annotationEditorExists()).toBe(false)
|
|
291
299
|
})
|
|
292
300
|
|
|
293
301
|
it('renders independent cells with separate x axes and overlays', async () => {
|
|
@@ -208,7 +208,7 @@ describe('WaveformChart', () => {
|
|
|
208
208
|
legend
|
|
209
209
|
.findAll('.waveform-legend__swatch')
|
|
210
210
|
.map((swatch) => swatch.get('path').attributes('stroke')),
|
|
211
|
-
).toEqual(['#0960bd', '#
|
|
211
|
+
).toEqual(['#0960bd', '#2ca02c'])
|
|
212
212
|
expect(wrapper.findAll('.waveform-chart__watermark').map((item) => item.text())).toEqual([
|
|
213
213
|
'1',
|
|
214
214
|
'2',
|
|
@@ -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: [
|
|
@@ -282,13 +290,13 @@ describe('WaveformChart presentation mode', () => {
|
|
|
282
290
|
}),
|
|
283
291
|
)
|
|
284
292
|
await flushPromises()
|
|
285
|
-
expect(
|
|
293
|
+
expect(annotationEditorExists()).toBe(true)
|
|
286
294
|
|
|
287
295
|
await wrapper.setProps({ presentationMode: true })
|
|
288
296
|
await flushPromises()
|
|
289
297
|
|
|
290
298
|
expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(false)
|
|
291
|
-
expect(
|
|
299
|
+
expect(annotationEditorExists()).toBe(false)
|
|
292
300
|
expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([null])
|
|
293
301
|
|
|
294
302
|
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
|
})
|