waveform-analysis 0.1.55 → 0.1.58
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 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +565 -539
- package/dist/types/components/core/grid.d.ts +3 -0
- package/dist/types/components/core/layout.d.ts +18 -0
- package/dist/types/components/core/trackLayoutBuilder.d.ts +2 -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/useWaveformLayout.d.ts +2 -1
- package/dist/types/components/core/waveformChartTypes.d.ts +2 -0
- package/dist/types/components/core/yAxisConstants.d.ts +1 -0
- package/package.json +1 -1
- package/src/App.test.ts +2 -1
- 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/layout.test.ts +1 -1
- package/src/components/core/layout.ts +63 -0
- package/src/components/core/trackLayoutBuilder.ts +24 -4
- package/src/components/core/trackPagination.ts +40 -0
- package/src/components/core/useWaveformLayout.ts +28 -31
- package/src/components/core/waveformChartTypes.ts +2 -0
- package/src/components/core/yAxisConstants.ts +1 -0
- package/src/components/core/yAxisSlotLayout.test.ts +129 -0
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +3 -5
- package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +61 -0
- package/src/components/waveformChartCases/trackOrder.test.ts +62 -0
- package/src/data/simulatedWaveforms.test.ts +4 -4
- package/src/data/simulatedWaveforms.ts +12 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { zoomIdentity } from 'd3'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_WAVEFORM_RENDERING_OPTIONS } from '../../core'
|
|
5
|
+
import { buildTrackLayouts, buildYAxisSlots } from './layout'
|
|
6
|
+
import type { DisplaySeries, DisplayTrack } from './types'
|
|
7
|
+
import { Y_AXIS_RIGHT_LABEL_OFFSET } from './yAxisConstants'
|
|
8
|
+
|
|
9
|
+
function series(id: string, minimum: number, maximum: number): DisplaySeries {
|
|
10
|
+
return {
|
|
11
|
+
id,
|
|
12
|
+
name: id,
|
|
13
|
+
color: '#1677ff',
|
|
14
|
+
lineType: 'linear',
|
|
15
|
+
lineStyle: 'solid',
|
|
16
|
+
pointType: 'none',
|
|
17
|
+
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
|
18
|
+
points: [
|
|
19
|
+
{ x: 0, y: minimum },
|
|
20
|
+
{ x: 1, y: maximum },
|
|
21
|
+
],
|
|
22
|
+
xDomain: [0, 1],
|
|
23
|
+
yDomain: [minimum, maximum],
|
|
24
|
+
hasErrorPoints: false,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function track(seriesList: DisplaySeries[]): DisplayTrack {
|
|
29
|
+
return {
|
|
30
|
+
id: seriesList[0]!.id,
|
|
31
|
+
series: seriesList,
|
|
32
|
+
visibleSeries: seriesList,
|
|
33
|
+
xDomain: [0, 1],
|
|
34
|
+
yDomain: [0, 1],
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('Y-axis slots', () => {
|
|
39
|
+
it('aligns matching slots across single and multi-axis tracks', () => {
|
|
40
|
+
const tracks = [
|
|
41
|
+
track([series('single', -1e120, 1e120)]),
|
|
42
|
+
track([series('left', 0, 1), series('right', 1000, 3000)]),
|
|
43
|
+
track([series('left-two', 0, 1), series('right-two', 1000, 3000)]),
|
|
44
|
+
]
|
|
45
|
+
const yAxisSlots = buildYAxisSlots(tracks, 'multi-axis')
|
|
46
|
+
const layouts = buildTrackLayouts({
|
|
47
|
+
cells: tracks.map((series, index) => ({
|
|
48
|
+
slotIndex: index,
|
|
49
|
+
row: index,
|
|
50
|
+
column: 0,
|
|
51
|
+
left: 0,
|
|
52
|
+
top: index * 100,
|
|
53
|
+
width: 600,
|
|
54
|
+
height: 100,
|
|
55
|
+
plotHeight: 100,
|
|
56
|
+
cellHeight: 130,
|
|
57
|
+
xAxisBand: 30,
|
|
58
|
+
series,
|
|
59
|
+
})),
|
|
60
|
+
grid: {
|
|
61
|
+
rowCount: 3,
|
|
62
|
+
columnCount: 1,
|
|
63
|
+
showPagination: false,
|
|
64
|
+
fillIncompleteLastRow: false,
|
|
65
|
+
trackLines: {},
|
|
66
|
+
},
|
|
67
|
+
displayMode: 'independent',
|
|
68
|
+
overlayMode: 'multi-axis',
|
|
69
|
+
independentTransforms: [zoomIdentity, zoomIdentity, zoomIdentity],
|
|
70
|
+
sharedZoomDomain: [0, 1],
|
|
71
|
+
timeUnit: 'ms',
|
|
72
|
+
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
73
|
+
hideSecondaryLabels: false,
|
|
74
|
+
yAxisLabelX: yAxisSlots.slots.find((slot) => slot.side === 'left')!.labelOffset,
|
|
75
|
+
yAxisSlots: yAxisSlots.slots,
|
|
76
|
+
showCompactEmptyTracks: false,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
expect(layouts.map((layout) => layout.yAxes[0]?.labelX)).toEqual([
|
|
80
|
+
layouts[0]?.yAxes[0]?.labelX,
|
|
81
|
+
layouts[0]?.yAxes[0]?.labelX,
|
|
82
|
+
layouts[0]?.yAxes[0]?.labelX,
|
|
83
|
+
])
|
|
84
|
+
expect(layouts[1]?.yAxes[1]?.labelX).toBe(layouts[2]?.yAxes[1]?.labelX)
|
|
85
|
+
expect(layouts[1]?.yAxes[1]?.labelX).toBe(
|
|
86
|
+
600 +
|
|
87
|
+
yAxisSlots.slots.find((slot) => slot.side === 'right')!.labelOffset -
|
|
88
|
+
Y_AXIS_RIGHT_LABEL_OFFSET,
|
|
89
|
+
)
|
|
90
|
+
expect(yAxisSlots.slots).toMatchObject([
|
|
91
|
+
{ side: 'left', sideIndex: 0, axisOffset: 0 },
|
|
92
|
+
{ side: 'right', sideIndex: 0, axisOffset: 0 },
|
|
93
|
+
])
|
|
94
|
+
expect(yAxisSlots.clearance.left).toBeGreaterThan(yAxisSlots.clearance.right)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('reserves outward second slots for three and four value axes', () => {
|
|
98
|
+
const threeAxisSlots = buildYAxisSlots(
|
|
99
|
+
[track([series('left', 0, 1), series('right-one', 10, 20), series('right-two', 30, 40)])],
|
|
100
|
+
'multi-axis',
|
|
101
|
+
)
|
|
102
|
+
const fourAxisSlots = buildYAxisSlots(
|
|
103
|
+
[
|
|
104
|
+
track([
|
|
105
|
+
series('left-one', 0, 1),
|
|
106
|
+
series('left-two', 10, 20),
|
|
107
|
+
series('right-one', 30, 40),
|
|
108
|
+
series('right-two', 50, 60),
|
|
109
|
+
]),
|
|
110
|
+
],
|
|
111
|
+
'multi-axis',
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expect(threeAxisSlots.slots).toMatchObject([
|
|
115
|
+
{ side: 'left', sideIndex: 0, axisOffset: 0 },
|
|
116
|
+
{ side: 'right', sideIndex: 0, axisOffset: 0 },
|
|
117
|
+
{ side: 'right', sideIndex: 1 },
|
|
118
|
+
])
|
|
119
|
+
expect(threeAxisSlots.slots[2]!.axisOffset).toBeGreaterThan(0)
|
|
120
|
+
expect(fourAxisSlots.slots).toMatchObject([
|
|
121
|
+
{ side: 'left', sideIndex: 0, axisOffset: 0 },
|
|
122
|
+
{ side: 'left', sideIndex: 1 },
|
|
123
|
+
{ side: 'right', sideIndex: 0, axisOffset: 0 },
|
|
124
|
+
{ side: 'right', sideIndex: 1 },
|
|
125
|
+
])
|
|
126
|
+
expect(fourAxisSlots.slots[1]!.axisOffset).toBeLessThan(0)
|
|
127
|
+
expect(fourAxisSlots.slots[3]!.axisOffset).toBeGreaterThan(0)
|
|
128
|
+
})
|
|
129
|
+
})
|
|
@@ -86,7 +86,7 @@ describe('WaveformChart', () => {
|
|
|
86
86
|
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(60)
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
-
it('
|
|
89
|
+
it('updates the Y-axis label position while preserving the chart gutter between pages', async () => {
|
|
90
90
|
const wrapper = await mountSizedChart(
|
|
91
91
|
{
|
|
92
92
|
kind: 'series',
|
|
@@ -118,14 +118,12 @@ describe('WaveformChart', () => {
|
|
|
118
118
|
{ grid: { rowCount: 1, columnCount: 1 } },
|
|
119
119
|
)
|
|
120
120
|
const initialMargin = wrapper.attributes('data-chart-left-margin')
|
|
121
|
-
|
|
121
|
+
expect(wrapper.get('.waveform-chart__track').attributes('data-y-axis-label-x')).toBe('-34')
|
|
122
122
|
|
|
123
123
|
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
124
124
|
|
|
125
125
|
expect(wrapper.attributes('data-chart-left-margin')).toBe(initialMargin)
|
|
126
|
-
expect(wrapper.get('.waveform-chart__track').attributes('data-y-axis-label-x')).toBe(
|
|
127
|
-
initialLabelX,
|
|
128
|
-
)
|
|
126
|
+
expect(wrapper.get('.waveform-chart__track').attributes('data-y-axis-label-x')).toBe('-62')
|
|
129
127
|
})
|
|
130
128
|
|
|
131
129
|
it('hides only secondary-column Y-axis labels when the grid is too narrow', async () => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { mountSizedChart } from '../../test/waveformChart'
|
|
4
|
+
|
|
5
|
+
describe('WaveformChart multi-axis labels', () => {
|
|
6
|
+
it('aligns single-axis and multi-axis Y-axis titles in one column', async () => {
|
|
7
|
+
const wrapper = await mountSizedChart(
|
|
8
|
+
{
|
|
9
|
+
kind: 'series',
|
|
10
|
+
series: [
|
|
11
|
+
{
|
|
12
|
+
id: 'single',
|
|
13
|
+
trackId: 'single-track',
|
|
14
|
+
name: '单轴',
|
|
15
|
+
data: {
|
|
16
|
+
kind: 'points',
|
|
17
|
+
points: [
|
|
18
|
+
{ x: 0, y: -1e120 },
|
|
19
|
+
{ x: 1, y: 1e120 },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'left',
|
|
25
|
+
trackId: 'multi-track',
|
|
26
|
+
name: '左轴',
|
|
27
|
+
data: {
|
|
28
|
+
kind: 'points',
|
|
29
|
+
points: [
|
|
30
|
+
{ x: 0, y: 0 },
|
|
31
|
+
{ x: 1, y: 1 },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'right',
|
|
37
|
+
trackId: 'multi-track',
|
|
38
|
+
name: '右轴',
|
|
39
|
+
data: {
|
|
40
|
+
kind: 'points',
|
|
41
|
+
points: [
|
|
42
|
+
{ x: 0, y: 1000 },
|
|
43
|
+
{ x: 1, y: 3000 },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{ overlayMode: 'multi-axis', grid: { rowCount: 2, columnCount: 1 } },
|
|
50
|
+
)
|
|
51
|
+
const tracks = wrapper.findAll('.waveform-chart__track')
|
|
52
|
+
const titleX = (transform: string | undefined) =>
|
|
53
|
+
Number(transform?.match(/translate\(([^,]+)/)?.[1])
|
|
54
|
+
|
|
55
|
+
expect(titleX(tracks[0]!.get('.waveform-chart__y-axis-label').attributes('transform'))).toBe(
|
|
56
|
+
titleX(
|
|
57
|
+
tracks[1]!.findAll('.waveform-track__multi-axis-title text')[0]!.attributes('transform'),
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -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
|
{
|
|
@@ -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 eight-channel data with a three-series second frame', () => {
|
|
7
7
|
const first = createSimulatedWaveformData()
|
|
8
8
|
const second = createSimulatedWaveformData()
|
|
9
9
|
|
|
@@ -11,12 +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(8)
|
|
15
|
+
expect(new Set(first.series.map((series) => series.id)).size).toBe(8)
|
|
16
16
|
const secondFrame = first.series.filter(
|
|
17
17
|
(series) => series.trackId === 'simulated-harmonic-frame',
|
|
18
18
|
)
|
|
19
|
-
expect(secondFrame).toHaveLength(
|
|
19
|
+
expect(secondFrame).toHaveLength(3)
|
|
20
20
|
expect(new Set(first.series.map((series) => series.shotNo))).toEqual(new Set(['13300']))
|
|
21
21
|
const firstSeries = first.series[0]
|
|
22
22
|
expect(firstSeries).toMatchObject({
|
|
@@ -84,6 +84,18 @@ const seriesDefinitions: SimulatedSeriesDefinition[] = [
|
|
|
84
84
|
signal: (time) =>
|
|
85
85
|
0.62 * Math.sin(TWO_PI * 0.55 * time + 0.55) + 0.18 * Math.sin(TWO_PI * 2.2 * time - 0.2),
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
id: 'simulated-harmonic-correction',
|
|
89
|
+
trackId: 'simulated-harmonic-frame',
|
|
90
|
+
shotNo: '13300',
|
|
91
|
+
name: '谐波校正',
|
|
92
|
+
unit: 'V',
|
|
93
|
+
color: '#722ed1',
|
|
94
|
+
lineType: 'linear',
|
|
95
|
+
pointType: 'none',
|
|
96
|
+
signal: (time) =>
|
|
97
|
+
0.48 * Math.sin(TWO_PI * 0.55 * time - 0.35) + 0.12 * Math.sin(TWO_PI * 2.2 * time + 0.7),
|
|
98
|
+
},
|
|
87
99
|
{
|
|
88
100
|
id: 'simulated-damped',
|
|
89
101
|
shotNo: '13300',
|