waveform-analysis 0.1.58 → 0.1.60
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 +9 -6
- package/dist/index.cjs +1 -1
- package/dist/index.js +894 -817
- package/dist/types/components/WaveformChart.vue.d.ts +2 -0
- package/dist/types/components/core/layout.d.ts +9 -2
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +1 -2
- package/dist/types/components/core/waveformChartTypes.d.ts +2 -1
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +10 -0
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/index.d.ts +1 -1
- package/dist/types/components/interaction/useWaveformZoom.d.ts +2 -1
- package/dist/types/components/interaction/zoomEventPayload.d.ts +7 -0
- package/dist/types/core/rendering.d.ts +1 -1
- package/dist/types/core/renderingOptions.d.ts +1 -1
- package/dist/types/core/wasmSampling.d.ts +1 -1
- package/dist/types/core/workerSampling/multiresolution.d.ts +1 -1
- package/dist/types/core/workerSampling/protocol.d.ts +1 -1
- package/dist/types/core/workerSampling/repositoryDataset.d.ts +1 -1
- package/dist/types/core/workerSampling/repositorySupport.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/chart.d.ts +8 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/geometry.d.ts +1 -1
- package/dist/types/wasm/waveform_sampling_wasm.d.ts +74 -0
- package/dist/types/wasm/waveform_sampling_wasm_bg.wasm.d.ts +19 -0
- package/package.json +5 -4
- package/src/components/core/layout.test.ts +24 -0
- package/src/components/core/layout.ts +75 -13
- package/src/components/core/trackLayoutBuilder.ts +7 -13
- package/src/components/core/useWaveformChartController.ts +6 -1
- package/src/components/core/useWaveformChartLifecycle.ts +15 -9
- package/src/components/core/useWaveformLayout.ts +30 -58
- package/src/components/core/waveformChartTypes.ts +2 -0
- package/src/components/core/yAxisLayoutMetrics.ts +75 -0
- package/src/components/core/yAxisSlotLayout.test.ts +37 -1
- package/src/components/data/types.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/components/interaction/useWaveformViewport.ts +2 -0
- package/src/components/interaction/useWaveformZoom.ts +58 -61
- package/src/components/interaction/zoomEventPayload.ts +88 -0
- package/src/components/rendering/WaveformTrackAxes.vue +8 -4
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +23 -24
- package/src/components/waveformChartCases/yAxisLayout.test.ts +103 -0
- package/src/components/waveformChartCases/zoomIntentAndDataRebind.test.ts +213 -0
- package/src/core/rendering.ts +1 -1
- package/src/core/renderingOptions.ts +1 -1
- package/src/core/workerSampling/multiresolution.ts +1 -1
- package/src/core/workerSampling/protocol.ts +1 -1
- package/src/core/workerSampling/repository.ts +1 -1
- package/src/core/workerSampling/repositoryDataset.ts +1 -1
- package/src/core/workerSampling/repositorySupport.ts +1 -1
- package/src/index.ts +1 -0
- package/src/types/chart.ts +9 -0
- package/src/types/index.ts +1 -0
- package/src/utils/geometry.ts +1 -1
|
@@ -3,7 +3,8 @@ import { axisBottom, axisLeft, axisRight, select } from 'd3'
|
|
|
3
3
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
4
4
|
|
|
5
5
|
import type { WaveformAxesOptions } from '../../types'
|
|
6
|
-
import {
|
|
6
|
+
import { formatXAxisLabel } from '../../utils'
|
|
7
|
+
import { formatYAxisTickLabel } from '../core/layout'
|
|
7
8
|
import type { DisplaySeries, TrackLayout, WaveformYAxisLayout } from '../core/types'
|
|
8
9
|
import { Y_AXIS_LABEL_BAND_WIDTH } from '../core/yAxisConstants'
|
|
9
10
|
|
|
@@ -43,12 +44,15 @@ function renderAxes() {
|
|
|
43
44
|
props.track.yAxes.forEach((axis, index) => {
|
|
44
45
|
const element = yAxisElements.value[index]
|
|
45
46
|
if (!element) return
|
|
46
|
-
const [axisMin, axisMax] = axis.scale.domain()
|
|
47
|
-
const topTickValue = Math.max(...axis.tickValues)
|
|
48
47
|
const unit = axis.seriesList[0]?.unit
|
|
49
48
|
const yAxis = (axis.side === 'left' ? axisLeft(axis.scale) : axisRight(axis.scale))
|
|
50
49
|
.tickFormat((value) =>
|
|
51
|
-
|
|
50
|
+
formatYAxisTickLabel(
|
|
51
|
+
Number(value),
|
|
52
|
+
axis.scale.domain() as [number, number],
|
|
53
|
+
axis.tickValues,
|
|
54
|
+
unit,
|
|
55
|
+
),
|
|
52
56
|
)
|
|
53
57
|
.tickSize(-4)
|
|
54
58
|
.tickPadding(7)
|
|
@@ -6,26 +6,6 @@ import { resizeObservers } from '../../test/setup'
|
|
|
6
6
|
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
7
7
|
|
|
8
8
|
describe('WaveformChart', () => {
|
|
9
|
-
it('applies the configured Y-axis split number', async () => {
|
|
10
|
-
const wrapper = await mountSizedChart(
|
|
11
|
-
{
|
|
12
|
-
kind: 'points',
|
|
13
|
-
points: [
|
|
14
|
-
{ x: 0, y: 3 },
|
|
15
|
-
{ x: 1, y: 97 },
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
{ axes: { y: { splitNumber: 5 } } },
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
expect(
|
|
22
|
-
wrapper
|
|
23
|
-
.get('.waveform-chart__axis--y')
|
|
24
|
-
.findAll('.tick text')
|
|
25
|
-
.map((tick) => tick.text()),
|
|
26
|
-
).toEqual(['0', '25', '50', '75', '100'])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
9
|
it('expands the Y-axis label gutter for signed values and long exponents', async () => {
|
|
30
10
|
const wrapper = await mountSizedChart(
|
|
31
11
|
{
|
|
@@ -52,10 +32,13 @@ describe('WaveformChart', () => {
|
|
|
52
32
|
const labelBackgroundX = Number(
|
|
53
33
|
tracks[0].get('.waveform-chart__y-axis-label-bg').attributes('x'),
|
|
54
34
|
)
|
|
35
|
+
const longestTickLabel = Math.max(
|
|
36
|
+
...tracks[0].findAll('.waveform-chart__axis--y .tick text').map((tick) => tick.text().length),
|
|
37
|
+
)
|
|
55
38
|
|
|
56
|
-
expect(labelX).
|
|
39
|
+
expect(Math.abs(labelX)).toBeGreaterThanOrEqual(7 + longestTickLabel * 7 + 6)
|
|
57
40
|
expect(labelBackgroundX).toBe(labelX - 6)
|
|
58
|
-
expect(Number(wrapper.attributes('data-chart-left-margin'))).
|
|
41
|
+
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBeGreaterThanOrEqual(80)
|
|
59
42
|
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(72)
|
|
60
43
|
})
|
|
61
44
|
|
|
@@ -118,12 +101,28 @@ describe('WaveformChart', () => {
|
|
|
118
101
|
{ grid: { rowCount: 1, columnCount: 1 } },
|
|
119
102
|
)
|
|
120
103
|
const initialMargin = wrapper.attributes('data-chart-left-margin')
|
|
121
|
-
|
|
104
|
+
const initialTrack = wrapper.get('.waveform-chart__track')
|
|
105
|
+
const initialLongestTickLabel = Math.max(
|
|
106
|
+
...initialTrack
|
|
107
|
+
.findAll('.waveform-chart__axis--y .tick text')
|
|
108
|
+
.map((tick) => tick.text().length),
|
|
109
|
+
)
|
|
110
|
+
expect(initialTrack.attributes('data-y-axis-label-x')).toBe(
|
|
111
|
+
String(-(7 + initialLongestTickLabel * 7 + 6)),
|
|
112
|
+
)
|
|
122
113
|
|
|
123
114
|
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
124
115
|
|
|
125
116
|
expect(wrapper.attributes('data-chart-left-margin')).toBe(initialMargin)
|
|
126
|
-
|
|
117
|
+
const pagedTrack = wrapper.get('.waveform-chart__track')
|
|
118
|
+
const pagedLongestTickLabel = Math.max(
|
|
119
|
+
...pagedTrack
|
|
120
|
+
.findAll('.waveform-chart__axis--y .tick text')
|
|
121
|
+
.map((tick) => tick.text().length),
|
|
122
|
+
)
|
|
123
|
+
expect(pagedTrack.attributes('data-y-axis-label-x')).toBe(
|
|
124
|
+
String(-(7 + pagedLongestTickLabel * 7 + 6)),
|
|
125
|
+
)
|
|
127
126
|
})
|
|
128
127
|
|
|
129
128
|
it('hides only secondary-column Y-axis labels when the grid is too narrow', async () => {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { flushPromises } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { resizeObservers } from '../../test/setup'
|
|
5
|
+
|
|
6
|
+
import { mountSizedChart } from '../../test/waveformChart'
|
|
7
|
+
|
|
8
|
+
describe('WaveformChart Y-axis layout', () => {
|
|
9
|
+
it('applies the configured Y-axis split number', async () => {
|
|
10
|
+
const wrapper = await mountSizedChart(
|
|
11
|
+
{
|
|
12
|
+
kind: 'points',
|
|
13
|
+
points: [
|
|
14
|
+
{ x: 0, y: 3 },
|
|
15
|
+
{ x: 1, y: 97 },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{ axes: { y: { splitNumber: 5 } } },
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
expect(
|
|
22
|
+
wrapper
|
|
23
|
+
.get('.waveform-chart__axis--y')
|
|
24
|
+
.findAll('.tick text')
|
|
25
|
+
.map((tick) => tick.text()),
|
|
26
|
+
).toEqual(['0', '25', '50', '75', '100'])
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('reserves the rendered endpoint label width across tracks, pages, and resizes', async () => {
|
|
30
|
+
const wrapper = await mountSizedChart(
|
|
31
|
+
{
|
|
32
|
+
kind: 'series',
|
|
33
|
+
series: ['WE_2M', 'IP_VV_2M', 'SX2_7_07', 'BT1_2M'].map((name, index) => ({
|
|
34
|
+
id: name,
|
|
35
|
+
name,
|
|
36
|
+
unit: 'V',
|
|
37
|
+
data: {
|
|
38
|
+
kind: 'points' as const,
|
|
39
|
+
points: [
|
|
40
|
+
{ x: 0, y: -0.7434 - index },
|
|
41
|
+
{ x: 1, y: -0.3434 - index },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
axes: { y: { lineVisible: false, nice: false } },
|
|
48
|
+
grid: { rowCount: 1, columnCount: 1 },
|
|
49
|
+
},
|
|
50
|
+
)
|
|
51
|
+
const firstTrack = wrapper.get('.waveform-chart__track')
|
|
52
|
+
|
|
53
|
+
expect(wrapper.attributes('data-chart-left-margin')).toBe('100')
|
|
54
|
+
expect(firstTrack.attributes('data-y-axis-label-x')).toBe('-90')
|
|
55
|
+
expect(firstTrack.findAll('.waveform-chart__axis--y .tick text').at(-1)?.text()).toBe(
|
|
56
|
+
'(V) -0.3434',
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
resizeObservers.at(-1)?.resize(520, 280)
|
|
60
|
+
await flushPromises()
|
|
61
|
+
expect(wrapper.attributes('data-chart-left-margin')).toBe('100')
|
|
62
|
+
expect(wrapper.get('.waveform-chart__track').attributes('data-y-axis-label-x')).toBe('-90')
|
|
63
|
+
|
|
64
|
+
await wrapper.get('.ant-pagination-next button').trigger('click')
|
|
65
|
+
expect(wrapper.attributes('data-chart-left-margin')).toBe('100')
|
|
66
|
+
expect(wrapper.get('.waveform-chart__track').attributes('data-y-axis-label-x')).toBe('-90')
|
|
67
|
+
expect(wrapper.get('.waveform-chart__axis--y').findAll('.tick text').at(-1)?.text()).toBe(
|
|
68
|
+
'(V) -1.3434',
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('measures the compact-row unit label after omitting the top endpoint', async () => {
|
|
73
|
+
const wrapper = await mountSizedChart(
|
|
74
|
+
{
|
|
75
|
+
kind: 'series',
|
|
76
|
+
series: ['first', 'second'].map((id) => ({
|
|
77
|
+
id,
|
|
78
|
+
name: id,
|
|
79
|
+
unit: 'V',
|
|
80
|
+
data: {
|
|
81
|
+
kind: 'points' as const,
|
|
82
|
+
points: [
|
|
83
|
+
{ x: 0, y: 0.12345 },
|
|
84
|
+
{ x: 1, y: 100 },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
})),
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
displayMode: 'compact',
|
|
91
|
+
axes: { y: { nice: false } },
|
|
92
|
+
grid: { rowCount: 2, columnCount: 1 },
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
const tracks = wrapper.findAll('.waveform-chart__track')
|
|
96
|
+
|
|
97
|
+
expect(wrapper.attributes('data-chart-left-margin')).toBe('93')
|
|
98
|
+
expect(tracks[1]?.attributes('data-y-axis-label-x')).toBe('-83')
|
|
99
|
+
expect(tracks[1]?.findAll('.waveform-chart__axis--y .tick text').at(-1)?.text()).toBe(
|
|
100
|
+
'(V) 75.031',
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
+
import { defineComponent, h, ref } from 'vue'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import WaveformChart from '../WaveformChart.vue'
|
|
6
|
+
import type { WaveformData } from '../waveform'
|
|
7
|
+
import { resizeObservers } from '../../test/setup'
|
|
8
|
+
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
9
|
+
|
|
10
|
+
function seriesWindow(ids: string[]) {
|
|
11
|
+
return {
|
|
12
|
+
kind: 'series' as const,
|
|
13
|
+
series: ids.map((id, index) => ({
|
|
14
|
+
id,
|
|
15
|
+
name: id,
|
|
16
|
+
data: {
|
|
17
|
+
kind: 'points' as const,
|
|
18
|
+
points: [
|
|
19
|
+
{ x: 40, y: index },
|
|
20
|
+
{ x: 50, y: index + 1 },
|
|
21
|
+
{ x: 60, y: index + 2 },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
})),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function fullSeries(ids: string[]): WaveformData {
|
|
29
|
+
return {
|
|
30
|
+
kind: 'series',
|
|
31
|
+
series: ids.map((id, index) => ({
|
|
32
|
+
id,
|
|
33
|
+
name: id,
|
|
34
|
+
data: {
|
|
35
|
+
kind: 'points',
|
|
36
|
+
points: [
|
|
37
|
+
{ x: 0, y: index },
|
|
38
|
+
{ x: 50, y: index + 1 },
|
|
39
|
+
{ x: 100, y: index + 2 },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
})),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function configureOverlay(overlay: ReturnType<typeof mount>['element']) {
|
|
47
|
+
const width = Number(overlay.getAttribute('width'))
|
|
48
|
+
const height = Number(overlay.getAttribute('height'))
|
|
49
|
+
Object.defineProperty(overlay, 'getBoundingClientRect', {
|
|
50
|
+
value: () => ({ left: 0, top: 0, width, height }),
|
|
51
|
+
})
|
|
52
|
+
return { width, height }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function dispatchWheel(overlay: Element, deltaY: number, width: number, height: number) {
|
|
56
|
+
overlay.dispatchEvent(
|
|
57
|
+
new WheelEvent('wheel', {
|
|
58
|
+
deltaY,
|
|
59
|
+
clientX: width / 2,
|
|
60
|
+
clientY: height / 2,
|
|
61
|
+
bubbles: true,
|
|
62
|
+
cancelable: true,
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe('WaveformChart zoom intent and data rebind', () => {
|
|
68
|
+
it('keeps one shared wheel end after an immediate intent-driven data replacement', async () => {
|
|
69
|
+
vi.useFakeTimers()
|
|
70
|
+
try {
|
|
71
|
+
const data = ref<WaveformData>(fullSeries(['shared']))
|
|
72
|
+
const host = mount(
|
|
73
|
+
defineComponent({
|
|
74
|
+
setup() {
|
|
75
|
+
return () =>
|
|
76
|
+
h(WaveformChart, {
|
|
77
|
+
data: data.value,
|
|
78
|
+
displayMode: 'separated',
|
|
79
|
+
initialXDomain: [0, 100],
|
|
80
|
+
onZoomIntent: () => {
|
|
81
|
+
data.value = seriesWindow(['shared'])
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
resizeObservers.at(-1)?.resize(800, 360)
|
|
88
|
+
await flushPromises()
|
|
89
|
+
|
|
90
|
+
const chart = host.findComponent(WaveformChart)
|
|
91
|
+
const overlay = chart.get('.waveform-chart__overlay')
|
|
92
|
+
const { width, height } = configureOverlay(overlay.element)
|
|
93
|
+
dispatchWheel(overlay.element, -4_000, width, height)
|
|
94
|
+
|
|
95
|
+
const firstIntent = chart.emitted('zoom-intent')?.[0]?.[0] as {
|
|
96
|
+
start: number
|
|
97
|
+
end: number
|
|
98
|
+
}
|
|
99
|
+
expect(firstIntent.end - firstIntent.start).toBeCloseTo(2.5)
|
|
100
|
+
expect(chart.emitted('zoom-change')).toBeUndefined()
|
|
101
|
+
|
|
102
|
+
await flushPromises()
|
|
103
|
+
dispatchWheel(overlay.element, 1_000, width, height)
|
|
104
|
+
await flushPromises()
|
|
105
|
+
await vi.advanceTimersByTimeAsync(199)
|
|
106
|
+
expect(chart.emitted('zoom-end')).toBeUndefined()
|
|
107
|
+
|
|
108
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
109
|
+
await flushPromises()
|
|
110
|
+
const zoomEnd = chart.emitted('zoom-end')?.[0]?.[0] as { start: number; end: number }
|
|
111
|
+
expect(chart.emitted('zoom-intent')).toHaveLength(2)
|
|
112
|
+
expect(chart.emitted('zoom-end')).toHaveLength(1)
|
|
113
|
+
expect(zoomEnd.end - zoomEnd.start).toBeGreaterThan(firstIntent.end - firstIntent.start)
|
|
114
|
+
} finally {
|
|
115
|
+
vi.useRealTimers()
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('retains a pending independent wheel gesture by stable series identity', async () => {
|
|
120
|
+
vi.useFakeTimers()
|
|
121
|
+
try {
|
|
122
|
+
const data = ref<WaveformData>(fullSeries(['channel-a', 'channel-b']))
|
|
123
|
+
const host = mount(
|
|
124
|
+
defineComponent({
|
|
125
|
+
setup() {
|
|
126
|
+
return () =>
|
|
127
|
+
h(WaveformChart, {
|
|
128
|
+
data: data.value,
|
|
129
|
+
displayMode: 'independent',
|
|
130
|
+
grid: { rowCount: 1, columnCount: 2 },
|
|
131
|
+
initialXDomain: [0, 100],
|
|
132
|
+
onZoomIntent: () => {
|
|
133
|
+
data.value = seriesWindow(['channel-b', 'channel-a'])
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
},
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
resizeObservers.at(-1)?.resize(800, 360)
|
|
140
|
+
await flushPromises()
|
|
141
|
+
|
|
142
|
+
const chart = host.findComponent(WaveformChart)
|
|
143
|
+
const overlay = chart.findAll('.waveform-chart__overlay--independent')[0]!
|
|
144
|
+
const { width, height } = configureOverlay(overlay.element)
|
|
145
|
+
dispatchWheel(overlay.element, -4_000, width, height)
|
|
146
|
+
await flushPromises()
|
|
147
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
148
|
+
await flushPromises()
|
|
149
|
+
|
|
150
|
+
const zoomEnd = chart.emitted('zoom-end')?.[0]?.[0] as {
|
|
151
|
+
trackIndex: number
|
|
152
|
+
seriesIds: string[]
|
|
153
|
+
}
|
|
154
|
+
expect(chart.emitted('zoom-intent')?.[0]?.[0]).toMatchObject({
|
|
155
|
+
trackIndex: 0,
|
|
156
|
+
seriesIds: ['channel-a'],
|
|
157
|
+
gesture: 'wheel',
|
|
158
|
+
})
|
|
159
|
+
expect(zoomEnd).toMatchObject({ trackIndex: 1, seriesIds: ['channel-a'], gesture: 'wheel' })
|
|
160
|
+
} finally {
|
|
161
|
+
vi.useRealTimers()
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('emits box intent with the selected independent series', async () => {
|
|
166
|
+
const wrapper = await mountSizedChart(fullSeries(['series-a']), { initialXDomain: [0, 100] })
|
|
167
|
+
const overlay = wrapper.get('.waveform-chart__overlay--independent')
|
|
168
|
+
const { width, height } = configureOverlay(overlay.element)
|
|
169
|
+
const dispatchPointer = (type: string, clientX: number, clientY: number) => {
|
|
170
|
+
const event = new MouseEvent(type, { button: 0, clientX, clientY, bubbles: true })
|
|
171
|
+
Object.defineProperty(event, 'pointerId', { value: 11 })
|
|
172
|
+
overlay.element.dispatchEvent(event)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
dispatchPointer('pointerdown', width * 0.25, height / 2)
|
|
176
|
+
dispatchPointer('pointermove', width * 0.75, height / 2)
|
|
177
|
+
dispatchPointer('pointerup', width * 0.75, height / 2)
|
|
178
|
+
await flushPromises()
|
|
179
|
+
|
|
180
|
+
expect(wrapper.emitted('zoom-intent')?.[0]?.[0]).toMatchObject({
|
|
181
|
+
trackIndex: 0,
|
|
182
|
+
seriesIds: ['series-a'],
|
|
183
|
+
gesture: 'box',
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('cancels pending wheel ends when reset or pagination changes the viewport', async () => {
|
|
188
|
+
vi.useFakeTimers()
|
|
189
|
+
try {
|
|
190
|
+
const reset = await mountSizedChart(fullSeries(['reset']), { initialXDomain: [0, 100] })
|
|
191
|
+
const resetOverlay = reset.get('.waveform-chart__overlay--independent')
|
|
192
|
+
const resetBounds = configureOverlay(resetOverlay.element)
|
|
193
|
+
dispatchWheel(resetOverlay.element, -4_000, resetBounds.width, resetBounds.height)
|
|
194
|
+
;(reset.vm as unknown as { resetViewport: () => void }).resetViewport()
|
|
195
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
196
|
+
expect(reset.emitted('zoom-end')).toBeUndefined()
|
|
197
|
+
|
|
198
|
+
const paged = await mountSizedChart(gridSeries(2), {
|
|
199
|
+
displayMode: 'independent',
|
|
200
|
+
grid: { rowCount: 1, columnCount: 1 },
|
|
201
|
+
})
|
|
202
|
+
const pageOverlay = paged.get('.waveform-chart__overlay--independent')
|
|
203
|
+
const pageBounds = configureOverlay(pageOverlay.element)
|
|
204
|
+
dispatchWheel(pageOverlay.element, -4_000, pageBounds.width, pageBounds.height)
|
|
205
|
+
await paged.get('.ant-pagination-next button').trigger('click')
|
|
206
|
+
await flushPromises()
|
|
207
|
+
await vi.advanceTimersByTimeAsync(200)
|
|
208
|
+
expect(paged.emitted('zoom-end')).toBeUndefined()
|
|
209
|
+
} finally {
|
|
210
|
+
vi.useRealTimers()
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
})
|
package/src/core/rendering.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bisector } from 'd3'
|
|
2
2
|
|
|
3
|
-
import type { WaveformPoint } from '
|
|
3
|
+
import type { WaveformPoint } from '../types'
|
|
4
4
|
import { resolveWaveformPointErrors } from './data'
|
|
5
5
|
import type { ResolvedWaveformRenderingOptions } from './renderingOptions'
|
|
6
6
|
import {
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
WaveformSamplingDiagnostics,
|
|
7
7
|
WaveformSamplingMode,
|
|
8
8
|
WaveformSamplingStrategy,
|
|
9
|
-
} from '
|
|
9
|
+
} from '../../types'
|
|
10
10
|
import type { WorkerSamplingDataset } from '../waveformPointSource'
|
|
11
11
|
|
|
12
12
|
export const DEFAULT_WORKER_SAMPLING_AUTO_THRESHOLD = 1_000
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WaveformPoint } from '
|
|
1
|
+
import type { WaveformPoint } from '../../types'
|
|
2
2
|
import type { WorkerSamplingDataset } from '../waveformPointSource'
|
|
3
3
|
import type { WasmDatasetSamplingBackend } from '../wasmSampling'
|
|
4
4
|
import { MultiResolutionSamplingIndex } from './multiresolution'
|
package/src/index.ts
CHANGED
package/src/types/chart.ts
CHANGED
|
@@ -49,6 +49,15 @@ export interface WaveformZoomEndPayload {
|
|
|
49
49
|
gesture?: 'wheel' | 'box'
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** Describes the X-axis viewport targeted by a user zoom gesture before rendering settles. */
|
|
53
|
+
export interface WaveformZoomIntentPayload {
|
|
54
|
+
start: number
|
|
55
|
+
end: number
|
|
56
|
+
gesture: 'wheel' | 'box'
|
|
57
|
+
trackIndex?: number
|
|
58
|
+
seriesIds?: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
52
61
|
/** Identifies the viewport reset by a double-click gesture. */
|
|
53
62
|
export interface WaveformZoomResetPayload {
|
|
54
63
|
trackIndex?: number
|
package/src/types/index.ts
CHANGED
package/src/utils/geometry.ts
CHANGED