waveform-analysis 0.1.62 → 0.1.64

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.
Files changed (83) hide show
  1. package/README.md +9 -2
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +953 -946
  4. package/dist/style.css +1 -1
  5. package/dist/types/types/chart.d.ts +2 -1
  6. package/package.json +1 -1
  7. package/src/components/WaveformChart.vue +1 -1
  8. package/src/components/core/xDomain.ts +26 -2
  9. package/src/components/rendering/WaveformLegend.vue +1 -1
  10. package/src/components/rendering/WaveformSeriesLayer.vue +1 -1
  11. package/src/demo/DemoChartHost.vue +1 -0
  12. package/src/types/chart.ts +2 -1
  13. package/src/App.test.ts +0 -400
  14. package/src/components/annotation/components.test.ts +0 -372
  15. package/src/components/annotation/markup.test.ts +0 -371
  16. package/src/components/annotation/serialization.test.ts +0 -114
  17. package/src/components/core/constants.test.ts +0 -29
  18. package/src/components/core/fixedYDomainLayout.test.ts +0 -204
  19. package/src/components/core/grid.test.ts +0 -197
  20. package/src/components/core/latestTaskScheduler.test.ts +0 -51
  21. package/src/components/core/layout.test.ts +0 -373
  22. package/src/components/core/title.test.ts +0 -76
  23. package/src/components/core/useWaveformData.test.ts +0 -95
  24. package/src/components/core/useWaveformRenderSampling.test.ts +0 -17
  25. package/src/components/core/xDomain.test.ts +0 -28
  26. package/src/components/core/yAxisNice.test.ts +0 -174
  27. package/src/components/core/yAxisSlotLayout.test.ts +0 -165
  28. package/src/components/core/yDomain.test.ts +0 -56
  29. package/src/components/interaction/WaveformTooltip.test.ts +0 -276
  30. package/src/components/interaction/viewportInteractionState.test.ts +0 -101
  31. package/src/components/utils/useAnimationFrameThrottle.test.ts +0 -53
  32. package/src/components/waveformChartCases/annotationEditingAndReprojection.test.ts +0 -257
  33. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +0 -358
  34. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +0 -358
  35. package/src/components/waveformChartCases/dataLifecycle.test.ts +0 -310
  36. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +0 -389
  37. package/src/components/waveformChartCases/gridFill.test.ts +0 -95
  38. package/src/components/waveformChartCases/incompleteDataHover.test.ts +0 -155
  39. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +0 -328
  40. package/src/components/waveformChartCases/layoutAndDomains.test.ts +0 -371
  41. package/src/components/waveformChartCases/legendVisibility.test.ts +0 -350
  42. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +0 -400
  43. package/src/components/waveformChartCases/multiAxisLabelAlignment.test.ts +0 -61
  44. package/src/components/waveformChartCases/presentationAndAxes.test.ts +0 -345
  45. package/src/components/waveformChartCases/presentationMode.test.ts +0 -327
  46. package/src/components/waveformChartCases/renderingStyles.test.ts +0 -283
  47. package/src/components/waveformChartCases/sizingAndTitles.test.ts +0 -370
  48. package/src/components/waveformChartCases/trackOrder.test.ts +0 -323
  49. package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +0 -365
  50. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +0 -311
  51. package/src/components/waveformChartCases/viewportLifecycle.test.ts +0 -132
  52. package/src/components/waveformChartCases/viewportReset.test.ts +0 -251
  53. package/src/components/waveformChartCases/wheelZoomOut.test.ts +0 -114
  54. package/src/components/waveformChartCases/workerSamplingIntegration.test.ts +0 -117
  55. package/src/components/waveformChartCases/xDomainStrategy.test.ts +0 -168
  56. package/src/components/waveformChartCases/yAxisLayout.test.ts +0 -103
  57. package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +0 -213
  58. package/src/components/waveformChartCases/zoomPointConstraints.test.ts +0 -308
  59. package/src/components/waveformLegendGeometry.test.ts +0 -21
  60. package/src/components/waveformNormalization.test.ts +0 -170
  61. package/src/core/data.test.ts +0 -241
  62. package/src/core/rendering.test.ts +0 -267
  63. package/src/core/renderingStrategies.test.ts +0 -90
  64. package/src/core/samplingReference.test.ts +0 -124
  65. package/src/core/wasmSampling.test.ts +0 -205
  66. package/src/core/workerSampling/multiresolution.test.ts +0 -69
  67. package/src/core/workerSampling/protocol.test.ts +0 -34
  68. package/src/core/workerSampling/repository.test.ts +0 -400
  69. package/src/core/workerSampling/repositoryCache.test.ts +0 -163
  70. package/src/core/workerSampling/repositorySampling.test.ts +0 -106
  71. package/src/core/workerSampling/repositoryTypedDataset.test.ts +0 -89
  72. package/src/core/workerSampling/repositoryWasmDataset.test.ts +0 -70
  73. package/src/data/simulatedWaveforms.test.ts +0 -60
  74. package/src/demo/plotMarginControls.test.ts +0 -29
  75. package/src/demo/xAxisLabelControls.test.ts +0 -86
  76. package/src/test/setup.ts +0 -82
  77. package/src/test/waveformChart.ts +0 -75
  78. package/src/utils/domain.test.ts +0 -28
  79. package/src/utils/formatters.test.ts +0 -131
  80. package/src/utils/sampling.test.ts +0 -226
  81. package/src/views/FixedYDomainDemo.test.ts +0 -44
  82. package/src/views/WasmSamplingDemo.test.ts +0 -13
  83. package/src/views/wasmSamplingDemoSampling.test.ts +0 -27
@@ -1,174 +0,0 @@
1
- import { zoomIdentity } from 'd3'
2
- import { describe, expect, it } from 'vitest'
3
-
4
- import { DEFAULT_WAVEFORM_RENDERING_OPTIONS } from '../../core'
5
- import type { WaveformData } from '../../types'
6
- import { buildTrackLayouts } from './trackLayoutBuilder'
7
- import type { DisplaySeries, DisplayTrack } from './types'
8
- import { prepareWaveformSeries } from './useWaveformData'
9
-
10
- function displaySeries(id: string, domain: [number, number]): DisplaySeries {
11
- return {
12
- id,
13
- name: id,
14
- color: '#1677ff',
15
- lineType: 'linear',
16
- lineStyle: 'solid',
17
- pointType: 'none',
18
- errorBar: { visible: false, width: 1.5, capWidth: 8 },
19
- points: [
20
- { x: 0, y: domain[0] },
21
- { x: 1, y: domain[1] },
22
- ],
23
- xDomain: [0, 1],
24
- yDomain: domain,
25
- hasErrorPoints: false,
26
- }
27
- }
28
-
29
- function layout(
30
- series: DisplaySeries[],
31
- options: { overlayMode?: 'single-axis' | 'multi-axis'; fixedYDomain?: [number, number] } = {},
32
- ) {
33
- const track: DisplayTrack = {
34
- id: 'track',
35
- series,
36
- visibleSeries: series,
37
- xDomain: [0, 1],
38
- yDomain: [
39
- Math.min(...series.map((item) => item.yDomain[0])),
40
- Math.max(...series.map((item) => item.yDomain[1])),
41
- ],
42
- }
43
- return buildTrackLayouts({
44
- cells: [
45
- {
46
- slotIndex: 0,
47
- row: 0,
48
- column: 0,
49
- left: 0,
50
- top: 0,
51
- width: 120,
52
- height: 100,
53
- plotHeight: 100,
54
- cellHeight: 130,
55
- xAxisBand: 30,
56
- series: track,
57
- },
58
- ],
59
- grid: {
60
- rowCount: 1,
61
- columnCount: 1,
62
- showPagination: false,
63
- fillIncompleteLastRow: false,
64
- trackLines: {},
65
- },
66
- displayMode: 'independent',
67
- overlayMode: options.overlayMode ?? 'single-axis',
68
- independentTransforms: [zoomIdentity],
69
- sharedZoomDomain: [0, 1],
70
- fixedYDomain: options.fixedYDomain,
71
- timeUnit: 'ms',
72
- rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
73
- hideSecondaryLabels: false,
74
- yAxisLabelX: -50,
75
- yAxisNice: false,
76
- showCompactEmptyTracks: false,
77
- })[0]!
78
- }
79
-
80
- describe('Y-axis nice domains', () => {
81
- it('defaults to a nice domain and can preserve the actual data endpoints', () => {
82
- const source = displaySeries('source', [3, 97])
83
- const track: DisplayTrack = {
84
- id: 'track',
85
- series: [source],
86
- visibleSeries: [source],
87
- xDomain: [0, 1],
88
- yDomain: [3, 97],
89
- }
90
- const defaults = buildTrackLayouts({
91
- cells: [
92
- {
93
- slotIndex: 0,
94
- row: 0,
95
- column: 0,
96
- left: 0,
97
- top: 0,
98
- width: 120,
99
- height: 100,
100
- plotHeight: 100,
101
- cellHeight: 130,
102
- xAxisBand: 30,
103
- series: track,
104
- },
105
- ],
106
- grid: {
107
- rowCount: 1,
108
- columnCount: 1,
109
- showPagination: false,
110
- fillIncompleteLastRow: false,
111
- trackLines: {},
112
- },
113
- displayMode: 'independent',
114
- overlayMode: 'single-axis',
115
- independentTransforms: [zoomIdentity],
116
- sharedZoomDomain: [0, 1],
117
- timeUnit: 'ms',
118
- rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
119
- hideSecondaryLabels: false,
120
- yAxisLabelX: -50,
121
- showCompactEmptyTracks: false,
122
- })[0]!
123
-
124
- expect(defaults.yScale.domain()).toEqual([0, 100])
125
- expect(layout([source]).yScale.domain()).toEqual([3, 97])
126
- })
127
-
128
- it('merges visible single-axis series but keeps multi-axis series independent', () => {
129
- const first = displaySeries('first', [3, 40])
130
- const second = displaySeries('second', [10, 97])
131
-
132
- expect(layout([first, second]).yScale.domain()).toEqual([3, 97])
133
- expect(
134
- layout([first, second], { overlayMode: 'multi-axis' }).yAxes.map((axis) =>
135
- axis.scale.domain(),
136
- ),
137
- ).toEqual([
138
- [3, 40],
139
- [10, 97],
140
- ])
141
- })
142
-
143
- it('keeps explicit domains and constant-data safety ranges unchanged', () => {
144
- expect(
145
- layout([displaySeries('source', [3, 97])], { fixedYDomain: [3, 97] }).yScale.domain(),
146
- ).toEqual([3, 97])
147
- expect(layout([displaySeries('constant', [9.5, 10.5])]).yScale.domain()).toEqual([9.5, 10.5])
148
- })
149
-
150
- it('continues to include visible error-bar bounds in the preserved domain', () => {
151
- const input: WaveformData = {
152
- kind: 'series',
153
- series: [
154
- {
155
- id: 'error-series',
156
- name: 'error series',
157
- errorBar: { visible: true },
158
- data: {
159
- kind: 'points',
160
- points: [
161
- { x: 0, y: 3, lowerError: 2 },
162
- { x: 1, y: 7, upperError: 4 },
163
- ],
164
- },
165
- },
166
- ],
167
- }
168
- const [prepared] = prepareWaveformSeries(input)
169
- const errorSeries: DisplaySeries = { ...prepared!, color: prepared!.color ?? '#1677ff' }
170
-
171
- expect(prepared?.yDomain).toEqual([1, 11])
172
- expect(layout([errorSeries]).yScale.domain()).toEqual([1, 11])
173
- })
174
- })
@@ -1,165 +0,0 @@
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, resolveRenderedYAxisSeriesGroups } 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
-
130
- it('uses dynamic Y domains when measuring multi-axis slots', () => {
131
- const negativeVoltage = series('negative-voltage', 0, 1)
132
- negativeVoltage.unit = 'V'
133
- const longUnit = series('long-unit', 0, 1)
134
- longUnit.unit = 'volt-amperes-per-phase'
135
- const sourceTrack = track([negativeVoltage, longUnit])
136
- const viewportYDomains = { [sourceTrack.id]: [-0.7434, -0.3434] as [number, number] }
137
-
138
- expect(
139
- resolveRenderedYAxisSeriesGroups(
140
- sourceTrack,
141
- 'multi-axis',
142
- undefined,
143
- undefined,
144
- viewportYDomains,
145
- ).map((group) => group.domain),
146
- ).toEqual([
147
- [-0.7434, -0.3434],
148
- [-0.7434, -0.3434],
149
- ])
150
-
151
- const slots = buildYAxisSlots(
152
- [sourceTrack],
153
- 'multi-axis',
154
- undefined,
155
- undefined,
156
- 5,
157
- false,
158
- false,
159
- viewportYDomains,
160
- )
161
-
162
- expect(slots.clearance.left).toBe(100)
163
- expect(slots.clearance.right).toBeGreaterThan(180)
164
- })
165
- })
@@ -1,56 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import type { DisplayTrack } from './types'
4
- import { hasFixedYDomainForTrack, mergeYDomains, normalizeYDomain } from './yDomain'
5
-
6
- const track: DisplayTrack = {
7
- id: 'shared-track',
8
- series: [],
9
- visibleSeries: [
10
- {
11
- id: 'channel-a',
12
- name: 'A',
13
- color: '#1677ff',
14
- lineType: 'linear',
15
- lineStyle: 'solid',
16
- pointType: 'none',
17
- errorBar: { visible: false, width: 1.5, capWidth: 8 },
18
- points: [],
19
- xDomain: [0, 1],
20
- yDomain: [0, 1],
21
- hasErrorPoints: false,
22
- },
23
- ],
24
- xDomain: [0, 1],
25
- yDomain: [0, 1],
26
- }
27
-
28
- describe('fixed Y domains', () => {
29
- it('normalizes valid reversed domains', () => {
30
- expect(normalizeYDomain([97, 3])).toEqual([3, 97])
31
- expect(normalizeYDomain([-10, 10])).toEqual([-10, 10])
32
- })
33
-
34
- it.each([undefined, [1, 1], [Number.NaN, 1], [0, Number.POSITIVE_INFINITY]])(
35
- 'rejects an invalid domain: %j',
36
- (domain) => {
37
- expect(normalizeYDomain(domain as [number, number] | undefined)).toBeUndefined()
38
- },
39
- )
40
-
41
- it('detects valid track, series, and global configuration only', () => {
42
- expect(hasFixedYDomainForTrack(track, [-1, 1])).toBe(true)
43
- expect(hasFixedYDomainForTrack(track, undefined, { 'shared-track': [-2, 2] })).toBe(true)
44
- expect(hasFixedYDomainForTrack(track, undefined, { 'channel-a': [-3, 3] })).toBe(true)
45
- expect(hasFixedYDomainForTrack(track, [0, 0], { 'channel-a': [1, 1] })).toBe(false)
46
- })
47
-
48
- it('merges configured and automatic series domains without padding', () => {
49
- expect(
50
- mergeYDomains([
51
- [3, 97],
52
- [-20, 40],
53
- ]),
54
- ).toEqual([-20, 97])
55
- })
56
- })
@@ -1,276 +0,0 @@
1
- import { mount } from '@vue/test-utils'
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
3
- import { nextTick } from 'vue'
4
- import { resizeObservers } from '../../test/setup'
5
-
6
- import WaveformTooltip from './WaveformTooltip.vue'
7
-
8
- describe('WaveformTooltip', () => {
9
- const point = { x: 1, y: 12 }
10
- let measuredWidth = 238
11
- let measuredHeight = 40
12
-
13
- beforeEach(() => {
14
- measuredWidth = 238
15
- measuredHeight = 40
16
- vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function (
17
- this: HTMLElement,
18
- ) {
19
- return {
20
- width: Math.min(measuredWidth, Number.parseFloat(this.style.maxWidth) || measuredWidth),
21
- height: measuredHeight,
22
- } as DOMRect
23
- })
24
- })
25
- afterEach(() => vi.restoreAllMocks())
26
-
27
- function mountTooltip(positionX: number, containerWidth = 400) {
28
- return mount(WaveformTooltip, {
29
- props: {
30
- visible: true,
31
- position: { x: positionX, y: 100 },
32
- timeUnit: 's',
33
- hoveredPoint: point,
34
- seriesPoints: [{ trackIndex: 0, name: 'Temperature', color: '#f00', point }],
35
- containerWidth,
36
- containerHeight: 300,
37
- },
38
- })
39
- }
40
-
41
- it('positions the tooltip to the right when enough space remains', async () => {
42
- const wrapper = mountTooltip(100)
43
- await nextTick()
44
- const tooltip = wrapper.get('.waveform-tooltip')
45
-
46
- expect(tooltip.attributes('style')).toContain('left: 112px')
47
- expect(tooltip.attributes('style')).not.toContain('right:')
48
- })
49
-
50
- it('flips the tooltip to the left near the right boundary', async () => {
51
- const wrapper = mountTooltip(370)
52
- await nextTick()
53
- const tooltip = wrapper.get('.waveform-tooltip')
54
-
55
- expect(tooltip.attributes('style')).toContain('right: 42px')
56
- expect(tooltip.attributes('style')).not.toContain('left:')
57
- })
58
-
59
- it('keeps the tooltip inside the left boundary when neither side has enough space', async () => {
60
- const wrapper = mountTooltip(100, 200)
61
- await nextTick()
62
- const tooltip = wrapper.get('.waveform-tooltip')
63
-
64
- expect(tooltip.attributes('style')).toContain('left: 8px')
65
- expect(tooltip.attributes('style')).toContain('max-width: 184px')
66
- expect(tooltip.attributes('style')).not.toContain('right:')
67
- })
68
-
69
- it('keeps short content content-sized while exposing the available width cap', async () => {
70
- const wrapper = mountTooltip(100)
71
- await nextTick()
72
- const tooltip = wrapper.get('.waveform-tooltip')
73
-
74
- expect(tooltip.attributes('style')).toContain('left: 112px')
75
- expect(tooltip.attributes('style')).toContain('max-width: 384px')
76
- })
77
-
78
- it('keeps long series content in a wrapping content container', () => {
79
- const longName = 'ENG8KJXAc-very-long-series-name-10001'
80
- const pointWithErrors = { x: 1, y: 12, error: 1, upperError: 2 }
81
- const wrapper = mount(WaveformTooltip, {
82
- props: {
83
- visible: true,
84
- position: { x: 100, y: 100 },
85
- timeUnit: 'ms',
86
- hoveredPoint: pointWithErrors,
87
- seriesPoints: [
88
- {
89
- trackIndex: 0,
90
- name: longName,
91
- color: '#f00',
92
- unit: 'very-long-unit',
93
- point: pointWithErrors,
94
- },
95
- ],
96
- containerWidth: 400,
97
- containerHeight: 300,
98
- },
99
- })
100
-
101
- const tooltip = wrapper.get('.waveform-tooltip')
102
- expect(tooltip.get('.waveform-tooltip__series-content').text()).toContain(longName)
103
- expect(tooltip.get('.waveform-tooltip__series-content').classes()).toContain(
104
- 'waveform-tooltip__series-content',
105
- )
106
- expect(tooltip.attributes('style')).toContain('max-width: 384px')
107
- })
108
-
109
- it('flips before long content is squeezed and restores the right side when moving back', async () => {
110
- measuredWidth = 420
111
- const wrapper = mountTooltip(500, 800)
112
- await nextTick()
113
- const tooltip = wrapper.get('.waveform-tooltip')
114
- expect(tooltip.attributes('style')).toContain('right: 312px')
115
- expect(tooltip.attributes('style')).toContain('max-width: 560px')
116
- await wrapper.setProps({ position: { x: 100, y: 100 } })
117
- expect(tooltip.attributes('style')).toContain('left: 112px')
118
- expect(tooltip.attributes('style')).toContain('max-width: 560px')
119
- })
120
-
121
- it('remeasures changing series, formatted values, container size and visibility', async () => {
122
- const wrapper = mountTooltip(500, 800)
123
- await nextTick()
124
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('left: 512px')
125
- measuredWidth = 450
126
- measuredHeight = 90
127
- await wrapper.setProps({
128
- timeUnit: 'ms',
129
- position: { x: 500, y: 290 },
130
- seriesPoints: [
131
- { trackIndex: 0, name: 'very-long-channel-name', color: '#f00', point },
132
- { trackIndex: 1, name: 'second-channel', color: '#0f0', point: { x: 100, y: 123456 } },
133
- ],
134
- })
135
- await nextTick()
136
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('right: 312px')
137
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('top: 202px')
138
- await wrapper.setProps({ containerWidth: 200, position: { x: 100, y: 290 } })
139
- await nextTick()
140
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('left: 8px')
141
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('max-width: 184px')
142
- const observer = resizeObservers.at(-1)!
143
- await wrapper.setProps({ visible: false })
144
- expect(observer.disconnect).toHaveBeenCalled()
145
- measuredWidth = 100
146
- await wrapper.setProps({ visible: true, containerWidth: 800 })
147
- await nextTick()
148
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('left: 112px')
149
- wrapper.unmount()
150
- expect(resizeObservers.at(-1)!.disconnect).toHaveBeenCalled()
151
- })
152
-
153
- it('updates placement when rendered dimensions change without a prop update', async () => {
154
- const wrapper = mountTooltip(500, 800)
155
- await nextTick()
156
- measuredWidth = 420
157
- measuredHeight = 250
158
- resizeObservers.at(-1)!.resize(420, 250)
159
- await nextTick()
160
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('right: 312px')
161
- expect(wrapper.get('.waveform-tooltip').attributes('style')).toContain('top: 42px')
162
- wrapper.unmount()
163
- })
164
-
165
- it('omits units and errors from the strict x/y value format', () => {
166
- const pointWithErrors = { x: 1, y: 12, error: 1, upperError: 2 }
167
- const wrapper = mount(WaveformTooltip, {
168
- props: {
169
- visible: true,
170
- position: { x: 10, y: 10 },
171
- timeUnit: 's',
172
- hoveredPoint: pointWithErrors,
173
- seriesPoints: [
174
- {
175
- trackIndex: 0,
176
- name: '温度',
177
- color: '#f00',
178
- unit: 'C',
179
- point: pointWithErrors,
180
- },
181
- ],
182
- containerWidth: 400,
183
- containerHeight: 300,
184
- },
185
- })
186
-
187
- expect(wrapper.get('.waveform-tooltip__value').text()).toBe('(x:1 y:12)')
188
- expect(wrapper.get('.waveform-tooltip__value').text()).not.toContain('C')
189
- expect(wrapper.get('.waveform-tooltip__value').text()).not.toContain('+2')
190
- })
191
-
192
- it('renders the configured shot number, channel, and formatted coordinates', () => {
193
- const hoveredPoint = { x: 1, y: -1405.4932 }
194
- const wrapper = mount(WaveformTooltip, {
195
- props: {
196
- visible: true,
197
- position: { x: 10, y: 10 },
198
- timeUnit: 'ms',
199
- hoveredPoint,
200
- seriesPoints: [
201
- {
202
- trackIndex: 0,
203
- name: 'ENG8KJXAc(10001)',
204
- shotNo: '炮 7',
205
- color: '#ffb43b',
206
- unit: 'A',
207
- point: hoveredPoint,
208
- },
209
- ],
210
- containerWidth: 400,
211
- containerHeight: 300,
212
- },
213
- })
214
-
215
- expect(wrapper.get('.waveform-tooltip__series-label').text()).toBe('炮 7: ENG8KJXAc(10001)(A)')
216
- expect(wrapper.get('.waveform-tooltip__value').text()).toBe('(x:1,000 y:-1,405.4932)')
217
- })
218
-
219
- it('renders each series row with the formatted x coordinate and series label', () => {
220
- const wrapper = mount(WaveformTooltip, {
221
- props: {
222
- visible: true,
223
- position: { x: 10, y: 10 },
224
- timeUnit: 's',
225
- hoveredPoint: { x: 1.234567, y: 12 },
226
- seriesPoints: [
227
- { trackIndex: 0, name: '温度', color: '#f00', point: { x: 1.234567, y: 12 } },
228
- ],
229
- containerWidth: 400,
230
- containerHeight: 300,
231
- },
232
- })
233
-
234
- expect(wrapper.find('.waveform-tooltip__time').exists()).toBe(false)
235
- expect(wrapper.get('.waveform-tooltip__series-label').text()).toBe('未配置炮号: 温度')
236
- expect(wrapper.get('.waveform-tooltip__value').text()).toBe('(x:1.2346 y:12)')
237
- })
238
-
239
- it('uses the fallback shot number when shotNo is blank', () => {
240
- const point = { x: 1, y: 12 }
241
- const wrapper = mount(WaveformTooltip, {
242
- props: {
243
- visible: true,
244
- position: { x: 10, y: 10 },
245
- timeUnit: 's',
246
- hoveredPoint: point,
247
- seriesPoints: [{ trackIndex: 0, name: '温度', color: '#f00', point }],
248
- containerWidth: 400,
249
- containerHeight: 300,
250
- },
251
- })
252
-
253
- expect(wrapper.get('.waveform-tooltip__series-label').text()).toBe('未配置炮号: 温度')
254
- })
255
-
256
- it('uses each series sample time and marks unavailable series without synthesizing values', () => {
257
- const wrapper = mount(WaveformTooltip, {
258
- props: {
259
- visible: true,
260
- position: { x: 10, y: 10 },
261
- timeUnit: 's',
262
- hoveredPoint: { x: 1, y: 12 },
263
- seriesPoints: [
264
- { trackIndex: 0, name: '完整数据', color: '#f00', point: { x: 1, y: 12 } },
265
- { trackIndex: 1, name: '异步采样', color: '#0a0', point: { x: 1.25, y: 8 } },
266
- { trackIndex: 2, name: '缺失数据', color: '#00f', point: null },
267
- ],
268
- containerWidth: 400,
269
- containerHeight: 300,
270
- },
271
- })
272
-
273
- const values = wrapper.findAll('.waveform-tooltip__value').map((value) => value.text())
274
- expect(values).toEqual(['(x:1 y:12)', '(x:1.25 y:8)', '无数据'])
275
- })
276
- })