waveform-analysis 0.1.74 → 0.1.76
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 +422 -60
- package/dist/assets/workerRuntime-BJiRmIZD.js +2446 -0
- package/dist/index.cjs +1 -2
- package/dist/index.js +1 -7119
- package/dist/style.css +1 -1
- package/dist/types/animation/WaveformAnimationController.d.ts +30 -0
- package/dist/types/animation/core/Animation.d.ts +13 -0
- package/dist/types/animation/core/Animator.d.ts +23 -0
- package/dist/types/animation/core/easing.d.ts +3 -0
- package/dist/types/animation/core/errors.d.ts +4 -0
- package/dist/types/animation/core/handle.d.ts +7 -0
- package/dist/types/animation/core/owner.d.ts +3 -0
- package/dist/types/animation/core/propertyGroups.d.ts +17 -0
- package/dist/types/animation/core/task.d.ts +29 -0
- package/dist/types/animation/core/types.d.ts +74 -0
- package/dist/types/animation/core/validation.d.ts +30 -0
- package/dist/types/animation/core/values.d.ts +8 -0
- package/dist/types/animation/presets/annotation.d.ts +4 -0
- package/dist/types/animation/presets/lineDrawing.d.ts +7 -0
- package/dist/types/animation/presets/shared.d.ts +14 -0
- package/dist/types/animation/presets/transitions.d.ts +4 -0
- package/dist/types/animation/types.d.ts +84 -0
- package/dist/types/components/WaveformChart.vue.d.ts +3 -2
- package/dist/types/components/animation/annotationPresentation.d.ts +17 -0
- package/dist/types/components/animation/chartExportVersion.d.ts +2 -0
- package/dist/types/components/animation/config.d.ts +31 -0
- package/dist/types/components/animation/context.d.ts +20 -0
- package/dist/types/components/animation/geometry.d.ts +10 -0
- package/dist/types/components/animation/paginationPresentation.d.ts +13 -0
- package/dist/types/components/animation/pathBuffer.d.ts +4 -0
- package/dist/types/components/animation/trackPresentation.d.ts +35 -0
- package/dist/types/components/animation/useWaveformAnimations.d.ts +27 -0
- package/dist/types/components/annotation/WaveformAnnotationContextMenu.vue.d.ts +2 -2
- package/dist/types/components/annotation/types.d.ts +7 -0
- package/dist/types/components/controls/useChartImageExport.d.ts +2 -0
- package/dist/types/components/core/amplitudeLimits.d.ts +6 -0
- package/dist/types/components/core/layout.d.ts +6 -9
- package/dist/types/components/core/trackLayoutBuilder.d.ts +3 -1
- package/dist/types/components/core/types.d.ts +8 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +26 -1
- package/dist/types/components/core/useWaveformData.d.ts +2 -2
- package/dist/types/components/core/useWaveformMeasurements.d.ts +4 -1
- package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +6 -3
- package/dist/types/components/core/xAxisTicks.d.ts +2 -0
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
- package/dist/types/components/core/yAxisTicks.d.ts +6 -0
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/interaction/integerZoom.d.ts +3 -3
- package/dist/types/components/interaction/stepZoom.d.ts +5 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +0 -3
- package/dist/types/components/interaction/useWaveformZoom.d.ts +0 -2
- package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
- package/dist/types/components/rendering/updateAxis.d.ts +5 -0
- package/dist/types/components/rendering/updateGrid.d.ts +3 -0
- package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
- package/dist/types/components/utils/assignElement.d.ts +2 -0
- package/dist/types/core/data.d.ts +1 -1
- package/dist/types/demo/DemoChartHost.vue.d.ts +1 -1
- package/dist/types/demo/remoteWaveformLoader.d.ts +23 -0
- package/dist/types/demo/types.d.ts +11 -2
- package/dist/types/demo/useRemoteWaveformWindow.d.ts +22 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/types/chart.d.ts +8 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/formatters.d.ts +4 -4
- package/dist/types/utils/xAxisNumber.d.ts +2 -0
- package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
- package/package.json +5 -2
- package/src/App.vue +34 -100
- package/src/DemoRouterApp.vue +1 -0
- package/src/animation/WaveformAnimationController.ts +328 -0
- package/src/animation/core/Animation.ts +37 -0
- package/src/animation/core/Animator.ts +200 -0
- package/src/animation/core/easing.ts +74 -0
- package/src/animation/core/errors.ts +24 -0
- package/src/animation/core/handle.ts +34 -0
- package/src/animation/core/owner.ts +9 -0
- package/src/animation/core/propertyGroups.ts +143 -0
- package/src/animation/core/task.ts +153 -0
- package/src/animation/core/types.ts +81 -0
- package/src/animation/core/validation.ts +173 -0
- package/src/animation/core/values.ts +38 -0
- package/src/animation/presets/annotation.ts +66 -0
- package/src/animation/presets/lineDrawing.ts +51 -0
- package/src/animation/presets/shared.ts +58 -0
- package/src/animation/presets/transitions.ts +77 -0
- package/src/animation/types.ts +81 -0
- package/src/components/WaveformChart.vue +1 -0
- package/src/components/WaveformChartView.vue +13 -6
- package/src/components/animation/annotationPresentation.ts +145 -0
- package/src/components/animation/chartExportVersion.ts +5 -0
- package/src/components/animation/config.ts +87 -0
- package/src/components/animation/context.ts +56 -0
- package/src/components/animation/geometry.ts +148 -0
- package/src/components/animation/paginationPresentation.ts +114 -0
- package/src/components/animation/pathBuffer.ts +61 -0
- package/src/components/animation/trackPresentation.ts +172 -0
- package/src/components/animation/useWaveformAnimations.ts +420 -0
- package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
- package/src/components/annotation/types.ts +3 -0
- package/src/components/controls/useChartCommands.ts +12 -13
- package/src/components/controls/useChartImageExport.ts +4 -0
- package/src/components/core/amplitudeLimits.ts +44 -0
- package/src/components/core/layout.ts +22 -34
- package/src/components/core/trackLayoutBuilder.ts +21 -23
- package/src/components/core/types.ts +4 -0
- package/src/components/core/useWaveformChartController.ts +27 -23
- package/src/components/core/useWaveformChartLifecycle.ts +11 -2
- package/src/components/core/useWaveformData.ts +11 -5
- package/src/components/core/useWaveformLayout.ts +9 -4
- package/src/components/core/useWaveformMeasurements.ts +3 -0
- package/src/components/core/useWaveformRenderSampling.ts +23 -1
- package/src/components/core/useWaveformYAxisLayout.ts +2 -0
- package/src/components/core/waveformChartTypes.ts +6 -2
- package/src/components/core/xAxisTicks.ts +69 -0
- package/src/components/core/yAxisLayoutMetrics.ts +4 -0
- package/src/components/core/yAxisTicks.ts +39 -0
- package/src/components/data/types.ts +2 -0
- package/src/components/interaction/integerZoom.ts +31 -12
- package/src/components/interaction/remoteViewport.ts +26 -4
- package/src/components/interaction/stepZoom.ts +33 -0
- package/src/components/interaction/useWaveformViewport.ts +3 -26
- package/src/components/interaction/useWaveformZoom.ts +50 -157
- package/src/components/interaction/zoomConstraints.ts +5 -5
- package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
- package/src/components/rendering/WaveformTrack.vue +6 -1
- package/src/components/rendering/WaveformTrackAxes.vue +64 -51
- package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
- package/src/components/rendering/updateAxis.ts +57 -0
- package/src/components/rendering/updateGrid.ts +42 -0
- package/src/components/rendering/yAxisTickVisibility.ts +45 -18
- package/src/components/utils/assignElement.ts +7 -0
- package/src/core/data.ts +5 -2
- package/src/data/simulatedWaveforms.ts +0 -10
- package/src/demo/DemoChartHost.vue +49 -4
- package/src/demo/remoteWaveformLoader.ts +114 -0
- package/src/demo/types.ts +11 -1
- package/src/demo/useRemoteWaveformWindow.ts +229 -0
- package/src/index.ts +54 -0
- package/src/router.ts +1 -0
- package/src/types/chart.ts +10 -1
- package/src/types/index.ts +2 -0
- package/src/utils/formatters.ts +10 -5
- package/src/utils/xAxisNumber.ts +36 -0
- package/src/views/AnimationDemo.vue +129 -0
- package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
- package/dist/types/data/sx2Channel.d.ts +0 -10
- package/src/data/sx2Channel.ts +0 -315
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
WaveformAmplitudeLimit,
|
|
3
|
+
WaveformAmplitudeLimits,
|
|
4
|
+
WaveformOverlayMode,
|
|
5
|
+
} from '../../types'
|
|
2
6
|
import { formatScientificAxisLabel, formatScientificAxisExponent, paddedDomain } from '../../utils'
|
|
3
7
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
4
8
|
import { MAX_MULTI_Y_AXIS_COUNT } from './constants'
|
|
@@ -8,7 +12,9 @@ import {
|
|
|
8
12
|
resolveTrackFixedYDomain,
|
|
9
13
|
type WaveformYDomain,
|
|
10
14
|
} from './yDomain'
|
|
11
|
-
import {
|
|
15
|
+
import { resolveYAxisTicks } from './yAxisTicks'
|
|
16
|
+
export { resolveYAxisTicks, type ResolvedYAxisTicks } from './yAxisTicks'
|
|
17
|
+
import { applyAmplitudeLimits, mergeAmplitudeLimits } from './amplitudeLimits'
|
|
12
18
|
import { applyYAxisPadding, withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
|
|
13
19
|
import { resolveYAxisLabelBandWidth } from './yAxisLabelStyle'
|
|
14
20
|
|
|
@@ -28,36 +34,6 @@ export function resolveYAxisTickCount(_plotHeight: number, splitNumber?: number)
|
|
|
28
34
|
return 5
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export interface ResolvedYAxisTicks {
|
|
32
|
-
domain: [number, number]
|
|
33
|
-
values: number[]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function resolveYAxisTicks(
|
|
37
|
-
domain: [number, number],
|
|
38
|
-
tickCount = 5,
|
|
39
|
-
nice = true,
|
|
40
|
-
): ResolvedYAxisTicks {
|
|
41
|
-
if (!nice) {
|
|
42
|
-
// 如果不需要 nice,使用简单的线性分割
|
|
43
|
-
const effectiveTickCount =
|
|
44
|
-
typeof tickCount === 'number' && Number.isFinite(tickCount)
|
|
45
|
-
? Math.max(2, Math.floor(tickCount))
|
|
46
|
-
: 5
|
|
47
|
-
const [axisStart, axisEnd] = domain
|
|
48
|
-
return {
|
|
49
|
-
domain,
|
|
50
|
-
values: Array.from(
|
|
51
|
-
{ length: effectiveTickCount },
|
|
52
|
-
(_, index) => axisStart + ((axisEnd - axisStart) * index) / (effectiveTickCount - 1),
|
|
53
|
-
),
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 使用 ECharts 兼容的 1/2/5 系列算法
|
|
58
|
-
return resolveYAxisTicksECharts(domain, tickCount)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
37
|
export function formatYAxisTickLabel(
|
|
62
38
|
value: number,
|
|
63
39
|
domain: [number, number],
|
|
@@ -101,6 +77,7 @@ export interface YAxisSeriesGroup {
|
|
|
101
77
|
seriesList: DisplaySeries[]
|
|
102
78
|
domain: [number, number]
|
|
103
79
|
fixed: boolean
|
|
80
|
+
amplitudeLimit?: WaveformAmplitudeLimit
|
|
104
81
|
maximumTick?: number
|
|
105
82
|
minimumTick?: number
|
|
106
83
|
}
|
|
@@ -191,6 +168,7 @@ export function resolveRenderedYAxisSeriesGroups(
|
|
|
191
168
|
yDomains?: Record<string, WaveformYDomain>,
|
|
192
169
|
viewportYDomains?: Record<string, [number, number]>,
|
|
193
170
|
padding?: YAxisPaddingOptions | number,
|
|
171
|
+
amplitudeLimits?: WaveformAmplitudeLimits,
|
|
194
172
|
): YAxisSeriesGroup[] {
|
|
195
173
|
const viewportYDomain = viewportYDomains?.[track.id]
|
|
196
174
|
const axisTrack =
|
|
@@ -198,7 +176,13 @@ export function resolveRenderedYAxisSeriesGroups(
|
|
|
198
176
|
? track
|
|
199
177
|
: { ...track, visibleSeries: track.series }
|
|
200
178
|
return resolveYAxisSeriesGroups(axisTrack, overlayMode, yDomain, yDomains).map((group) => {
|
|
201
|
-
const
|
|
179
|
+
const limit = mergeAmplitudeLimits(group.seriesList, amplitudeLimits)
|
|
180
|
+
const automatic = limit ? buildYAxisSeriesGroups(axisTrack, overlayMode)[group.index]! : group
|
|
181
|
+
const padded = applyYAxisPadding(automatic, limit ? undefined : viewportYDomain, padding)
|
|
182
|
+
if (limit) {
|
|
183
|
+
padded.domain = applyAmplitudeLimits(padded.domain, limit, automatic.domain)
|
|
184
|
+
padded.amplitudeLimit = limit
|
|
185
|
+
}
|
|
202
186
|
return track.visibleSeries.length
|
|
203
187
|
? padded
|
|
204
188
|
: { ...padded, maximumTick: undefined, minimumTick: undefined }
|
|
@@ -215,10 +199,11 @@ export function axisTextMetrics(
|
|
|
215
199
|
measureTextWidth?: (text: string) => number,
|
|
216
200
|
maximumTick?: number,
|
|
217
201
|
minimumTick?: number,
|
|
202
|
+
amplitudeLimit?: WaveformAmplitudeLimit,
|
|
218
203
|
): { tickTextWidth: number; titleTextWidth: number } {
|
|
219
204
|
const resolvedTicks = tickValues
|
|
220
205
|
? { domain, values: tickValues }
|
|
221
|
-
: resolveYAxisTicks(domain, tickCount, nice)
|
|
206
|
+
: resolveYAxisTicks(domain, tickCount, nice, amplitudeLimit)
|
|
222
207
|
const inRange = (value?: number) =>
|
|
223
208
|
value !== undefined && value >= resolvedTicks.domain[0] && value <= resolvedTicks.domain[1]
|
|
224
209
|
? value
|
|
@@ -320,6 +305,7 @@ export function buildYAxisSlots(
|
|
|
320
305
|
padding?: YAxisPaddingOptions | number,
|
|
321
306
|
showUnits = true,
|
|
322
307
|
measureTextWidth?: (text: string) => number,
|
|
308
|
+
amplitudeLimits?: WaveformAmplitudeLimits,
|
|
323
309
|
): { slots: YAxisSlot[]; clearance: { left: number; right: number } } {
|
|
324
310
|
const labelBandWidth = resolveYAxisLabelBandWidth(
|
|
325
311
|
typeof padding === 'object' ? padding.labelTextStyle : undefined,
|
|
@@ -335,6 +321,7 @@ export function buildYAxisSlots(
|
|
|
335
321
|
yDomains,
|
|
336
322
|
viewportYDomains,
|
|
337
323
|
padding,
|
|
324
|
+
amplitudeLimits,
|
|
338
325
|
).forEach((group) => {
|
|
339
326
|
const sideIndex = sideIndexes[group.side]++
|
|
340
327
|
const key = `${group.side}:${sideIndex}`
|
|
@@ -348,6 +335,7 @@ export function buildYAxisSlots(
|
|
|
348
335
|
measureTextWidth,
|
|
349
336
|
group.maximumTick,
|
|
350
337
|
group.minimumTick,
|
|
338
|
+
group.amplitudeLimit,
|
|
351
339
|
)
|
|
352
340
|
widths.set(key, Math.max(widths.get(key) ?? Y_AXIS_CHARACTER_WIDTH, tickTextWidth))
|
|
353
341
|
titleWidths.set(key, Math.max(titleWidths.get(key) ?? Y_AXIS_CHARACTER_WIDTH, titleTextWidth))
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
+
import { generateWaveformPath } from '../animation/geometry'
|
|
1
2
|
import { withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
|
|
2
3
|
import { selectSeriesRenderSourcePoints } from '../../core/rendering'
|
|
3
4
|
import { pointSourceFromPoints } from '../../core/waveformPointSource'
|
|
4
|
-
import {
|
|
5
|
-
curveStep,
|
|
6
|
-
curveStepAfter,
|
|
7
|
-
curveStepBefore,
|
|
8
|
-
line,
|
|
9
|
-
scaleLinear,
|
|
10
|
-
zoomIdentity,
|
|
11
|
-
type ZoomTransform,
|
|
12
|
-
} from 'd3'
|
|
5
|
+
import { scaleLinear, zoomIdentity, type ZoomTransform } from 'd3'
|
|
13
6
|
|
|
14
7
|
import { type ResolvedWaveformRenderingOptions } from '../../core/rendering'
|
|
15
8
|
import type {
|
|
9
|
+
WaveformAmplitudeLimits,
|
|
16
10
|
WaveformDisplayMode,
|
|
17
11
|
WaveformOverlayMode,
|
|
18
12
|
WaveformPoint,
|
|
@@ -41,7 +35,6 @@ import {
|
|
|
41
35
|
alignIntegerZoomDomain,
|
|
42
36
|
normalizeIntegerZoomDomain,
|
|
43
37
|
integerZoomTicks,
|
|
44
|
-
formatIntegerZoomLabel,
|
|
45
38
|
} from '../interaction/integerZoom'
|
|
46
39
|
import {
|
|
47
40
|
Y_AXIS_LABEL_GAP,
|
|
@@ -66,6 +59,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
66
59
|
initialXDomains?: Record<string, [number, number]>
|
|
67
60
|
xDomainStrategy?: WaveformXDomainStrategy
|
|
68
61
|
integerZoom?: boolean
|
|
62
|
+
amplitudeLimits?: WaveformAmplitudeLimits
|
|
69
63
|
fixedYDomain?: [number, number]
|
|
70
64
|
fixedYDomains?: Record<string, [number, number]>
|
|
71
65
|
yDomains?: Record<string, [number, number]>
|
|
@@ -86,6 +80,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
86
80
|
useNonEmptyBottomTracks?: boolean
|
|
87
81
|
compactYAxisLayout?: boolean
|
|
88
82
|
showAxisUnits?: boolean
|
|
83
|
+
domainTimeUnit?: 's' | 'ms'
|
|
89
84
|
}
|
|
90
85
|
|
|
91
86
|
function resolveIndependentXDomain(
|
|
@@ -103,7 +98,7 @@ function resolveIndependentXDomain(
|
|
|
103
98
|
explicitDomain
|
|
104
99
|
? applyXDomainStrategy(explicitDomain, strategy, true)
|
|
105
100
|
: applyXDomainStrategy(track.xDomain, strategy),
|
|
106
|
-
options,
|
|
101
|
+
{ integerZoom: options.integerZoom, timeUnit: options.domainTimeUnit ?? options.timeUnit },
|
|
107
102
|
)
|
|
108
103
|
}
|
|
109
104
|
|
|
@@ -148,13 +143,19 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
148
143
|
options.fixedYDomains,
|
|
149
144
|
options.yDomains,
|
|
150
145
|
options.yAxisPadding ?? options.yAxisUpperPaddingRatio,
|
|
146
|
+
options.amplitudeLimits,
|
|
151
147
|
)
|
|
152
148
|
const sideIndexes = { left: 0, right: 0 }
|
|
153
149
|
const sideOffsets = { left: 0, right: 0 }
|
|
154
150
|
const yAxes: WaveformYAxisLayout[] = yAxisGroups.map((group) => {
|
|
155
151
|
const sideIndex = sideIndexes[group.side]++
|
|
156
152
|
const tickCount = resolveYAxisTickCount(cell.plotHeight, options.yAxisSplitNumber)
|
|
157
|
-
const resolvedTicks = resolveYAxisTicks(
|
|
153
|
+
const resolvedTicks = resolveYAxisTicks(
|
|
154
|
+
group.domain,
|
|
155
|
+
tickCount,
|
|
156
|
+
options.yAxisNice !== false,
|
|
157
|
+
group.amplitudeLimit,
|
|
158
|
+
)
|
|
158
159
|
const scale = scaleLinear(resolvedTicks.domain, [cell.plotHeight, 0])
|
|
159
160
|
const majorTicks = resolvedTicks.values
|
|
160
161
|
const maximumTick =
|
|
@@ -227,8 +228,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
227
228
|
Math.max(2, Math.floor(cell.width / 100)),
|
|
228
229
|
options,
|
|
229
230
|
)
|
|
230
|
-
const xAxisLabelFormatter =
|
|
231
|
-
options.xAxisLabelFormatter ?? (options.integerZoom ? formatIntegerZoomLabel : undefined)
|
|
231
|
+
const xAxisLabelFormatter = options.xAxisLabelFormatter
|
|
232
232
|
const yMajorTicks =
|
|
233
233
|
yAxes[0]?.majorTicks ??
|
|
234
234
|
(isEmpty
|
|
@@ -264,17 +264,15 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
264
264
|
hasErrorPoints: trackSeries.hasErrorPoints,
|
|
265
265
|
},
|
|
266
266
|
)
|
|
267
|
-
const pathGenerator = line<WaveformPoint>()
|
|
268
|
-
.x((point) => xScale(point.x))
|
|
269
|
-
.y((point) => seriesYScale(point.y))
|
|
270
|
-
if (trackSeries.lineType === 'step-start') pathGenerator.curve(curveStepBefore)
|
|
271
|
-
if (trackSeries.lineType === 'step-middle') pathGenerator.curve(curveStep)
|
|
272
|
-
if (trackSeries.lineType === 'step-end' || trackSeries.lineType === 'step-after') {
|
|
273
|
-
pathGenerator.curve(curveStepAfter)
|
|
274
|
-
}
|
|
275
267
|
return {
|
|
276
268
|
series: trackSeries,
|
|
277
|
-
path:
|
|
269
|
+
path: generateWaveformPath(
|
|
270
|
+
renderPoints.linePoints,
|
|
271
|
+
trackSeries.lineType,
|
|
272
|
+
xScale,
|
|
273
|
+
seriesYScale,
|
|
274
|
+
),
|
|
275
|
+
lineRenderPoints: renderPoints.linePoints,
|
|
278
276
|
pointRenderPoints: renderPoints.pointRenderPoints,
|
|
279
277
|
errorBarRenderPoints: renderPoints.errorBarRenderPoints,
|
|
280
278
|
yScale: seriesYScale,
|
|
@@ -47,6 +47,10 @@ export interface DisplayTrack {
|
|
|
47
47
|
export interface TrackSeriesPath {
|
|
48
48
|
series: DisplaySeries
|
|
49
49
|
path: string | null
|
|
50
|
+
lineRenderPoints?: WaveformPoint[]
|
|
51
|
+
presentationKey?: string
|
|
52
|
+
opacity?: number
|
|
53
|
+
animationClip?: { id: string; x: number; width: number }
|
|
50
54
|
pointRenderPoints: WaveformPoint[]
|
|
51
55
|
errorBarRenderPoints: WaveformPoint[]
|
|
52
56
|
yScale: ScaleLinear<number, number>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assignElement } from '../utils/assignElement'
|
|
2
|
+
import { chartExportVersion } from '../animation/chartExportVersion'
|
|
3
|
+
import { useWaveformAnimations } from '../animation/useWaveformAnimations'
|
|
2
4
|
import { useChartImageExport } from '../controls/useChartImageExport'
|
|
3
5
|
import { useControlMode } from '../controls/useControlMode'
|
|
4
6
|
import { useChartCommands } from '../controls/useChartCommands'
|
|
@@ -16,9 +18,7 @@ import {
|
|
|
16
18
|
watch,
|
|
17
19
|
watchEffect,
|
|
18
20
|
type ComponentPublicInstance,
|
|
19
|
-
type Ref,
|
|
20
21
|
} from 'vue'
|
|
21
|
-
|
|
22
22
|
import { useWaveformInstanceId } from '../../utils/waveformId'
|
|
23
23
|
import { useWaveformAnnotationInteraction, type AnnotationSeriesCandidate } from '../annotation'
|
|
24
24
|
import { useWaveformChartAnnotations } from '../annotation/useWaveformChartAnnotations'
|
|
@@ -32,6 +32,7 @@ import { resolveCompactPaginationBand } from './pagination'
|
|
|
32
32
|
import type { WaveformHoverState } from './types'
|
|
33
33
|
import { useWaveformChartLifecycle } from './useWaveformChartLifecycle'
|
|
34
34
|
import { usePreparedWaveformSeries } from './useWaveformData'
|
|
35
|
+
import { remotePanBoundary } from '../interaction/remoteViewport'
|
|
35
36
|
import { useWaveformLayout } from './useWaveformLayout'
|
|
36
37
|
import { useWaveformPresentation } from './useWaveformPresentation'
|
|
37
38
|
import { usePlotLabelLayout } from './usePlotLabelLayout'
|
|
@@ -41,14 +42,6 @@ import type {
|
|
|
41
42
|
ViewportSelectionState,
|
|
42
43
|
WaveformChartEmit,
|
|
43
44
|
} from './waveformChartTypes'
|
|
44
|
-
|
|
45
|
-
function assignElement<T extends Element>(
|
|
46
|
-
target: Ref<T | undefined>,
|
|
47
|
-
element: Element | ComponentPublicInstance | null,
|
|
48
|
-
) {
|
|
49
|
-
target.value = element instanceof Element ? (element as T) : undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
45
|
export function useWaveformChartController(
|
|
53
46
|
inputProps: ResolvedWaveformChartProps,
|
|
54
47
|
emit: WaveformChartEmit,
|
|
@@ -90,7 +83,7 @@ export function useWaveformChartController(
|
|
|
90
83
|
const hoverThrottle = useAnimationFrameThrottle()
|
|
91
84
|
|
|
92
85
|
watch(
|
|
93
|
-
[() => props.yDomain, () => props.yDomains],
|
|
86
|
+
[() => props.yDomain, () => props.yDomains, () => props.amplitudeLimits],
|
|
94
87
|
() => {
|
|
95
88
|
sharedYDomains.value = {}
|
|
96
89
|
independentYDomains.value = {}
|
|
@@ -110,6 +103,7 @@ export function useWaveformChartController(
|
|
|
110
103
|
handleBeforeDataReferenceChange()
|
|
111
104
|
},
|
|
112
105
|
() => handleDataReferenceChange(),
|
|
106
|
+
() => Boolean(remotePanBoundary(props)),
|
|
113
107
|
)
|
|
114
108
|
|
|
115
109
|
const presentation = useWaveformPresentation({
|
|
@@ -186,7 +180,17 @@ export function useWaveformChartController(
|
|
|
186
180
|
renderingOptions,
|
|
187
181
|
linePointOverrides,
|
|
188
182
|
})
|
|
189
|
-
|
|
183
|
+
const animations = useWaveformAnimations({
|
|
184
|
+
props,
|
|
185
|
+
currentPage,
|
|
186
|
+
containerSize: computed(() => [observedWidth.value, observedHeight.value]),
|
|
187
|
+
layoutReady: computed(() => observedWidth.value > 0 && observedHeight.value > 0),
|
|
188
|
+
tracks: trackLayouts,
|
|
189
|
+
preparedSeries,
|
|
190
|
+
innerWidth,
|
|
191
|
+
innerHeight: presentation.innerHeight,
|
|
192
|
+
sampling,
|
|
193
|
+
})
|
|
190
194
|
const paginationVisible = computed(
|
|
191
195
|
() => gridOptions.value.showPagination && pageCount.value > 1 && !isCleanView.value,
|
|
192
196
|
)
|
|
@@ -261,8 +265,6 @@ export function useWaveformChartController(
|
|
|
261
265
|
isPresentationMode,
|
|
262
266
|
editorSeriesOptions,
|
|
263
267
|
resolveInitialTrackDomain,
|
|
264
|
-
canZoomTrack: zoom.canZoomTrack,
|
|
265
|
-
canZoomSharedTracks: zoom.canZoomSharedTracks,
|
|
266
268
|
configureZoom: zoom.configureZoom,
|
|
267
269
|
cancelPendingZoom: zoom.cancelPendingZoom,
|
|
268
270
|
clearHover: () => hover.clearHover(),
|
|
@@ -389,10 +391,8 @@ export function useWaveformChartController(
|
|
|
389
391
|
height: chartHeight.value,
|
|
390
392
|
titleHeight: titleAreaHeight.value,
|
|
391
393
|
}),
|
|
392
|
-
version: () =>
|
|
393
|
-
props
|
|
394
|
-
JSON.stringify({
|
|
395
|
-
...chartExportOptions(props),
|
|
394
|
+
version: () =>
|
|
395
|
+
chartExportVersion(props, {
|
|
396
396
|
hidden: [...hiddenSeriesIdSet.value],
|
|
397
397
|
width: chartWidth.value,
|
|
398
398
|
height: chartHeight.value,
|
|
@@ -404,7 +404,8 @@ export function useWaveformChartController(
|
|
|
404
404
|
independentY: independentYDomains.value,
|
|
405
405
|
boundaries: boundaries.value,
|
|
406
406
|
}),
|
|
407
|
-
|
|
407
|
+
prepare: animations.prepareExport,
|
|
408
|
+
release: animations.releaseExport,
|
|
408
409
|
ready: sampling.ready,
|
|
409
410
|
})
|
|
410
411
|
|
|
@@ -418,7 +419,11 @@ export function useWaveformChartController(
|
|
|
418
419
|
...annotations,
|
|
419
420
|
...viewport,
|
|
420
421
|
...hover,
|
|
421
|
-
...commands,
|
|
422
|
+
...animations.wrap(commands),
|
|
423
|
+
presentationTracks: animations.presentationTracks,
|
|
424
|
+
presentationAnnotations: animations.presentationAnnotations,
|
|
425
|
+
paginationOpacity: animations.paginationOpacity,
|
|
426
|
+
stabilizeAnimations: animations.stabilizeAnimations,
|
|
422
427
|
...imageExport,
|
|
423
428
|
margin,
|
|
424
429
|
getPageSize,
|
|
@@ -431,7 +436,7 @@ export function useWaveformChartController(
|
|
|
431
436
|
contextMenu: annotationInteraction.contextMenu,
|
|
432
437
|
editorSeriesOptions,
|
|
433
438
|
clipPathId,
|
|
434
|
-
goToPage: lifecycle.goToPage,
|
|
439
|
+
goToPage: animations.wrapPagination(lifecycle.goToPage, pageCount),
|
|
435
440
|
setContainer: (element: Element | ComponentPublicInstance | null) =>
|
|
436
441
|
assignElement(container, element),
|
|
437
442
|
setSvgElement: (element: Element | ComponentPublicInstance | null) =>
|
|
@@ -442,5 +447,4 @@ export function useWaveformChartController(
|
|
|
442
447
|
assignElement(sharedOverlayElement, element),
|
|
443
448
|
})
|
|
444
449
|
}
|
|
445
|
-
|
|
446
450
|
export type WaveformChartController = ReturnType<typeof useWaveformChartController>
|
|
@@ -16,6 +16,7 @@ import type { NormalizedWaveformGridOptions } from './grid'
|
|
|
16
16
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
17
17
|
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
|
18
18
|
import { isEditableTarget } from '../interaction/interactionTarget'
|
|
19
|
+
import { remotePanBoundary } from '../interaction/remoteViewport'
|
|
19
20
|
|
|
20
21
|
export interface LifecycleContext {
|
|
21
22
|
preserveFullViewport?: () => boolean
|
|
@@ -64,7 +65,6 @@ export interface LifecycleContext {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
67
|
-
useWaveformMeasurements(context)
|
|
68
68
|
const {
|
|
69
69
|
props,
|
|
70
70
|
emit,
|
|
@@ -135,6 +135,15 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
135
135
|
const { handleBeforeDataReferenceChange, handleDataReferenceChange } =
|
|
136
136
|
useWaveformViewportRestore(context)
|
|
137
137
|
|
|
138
|
+
useWaveformMeasurements(context, {
|
|
139
|
+
beforeResize: () => {
|
|
140
|
+
if (remotePanBoundary(props)) handleBeforeDataReferenceChange()
|
|
141
|
+
},
|
|
142
|
+
afterResize: () => {
|
|
143
|
+
if (remotePanBoundary(props)) handleDataReferenceChange()
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
|
|
138
147
|
watch(
|
|
139
148
|
[
|
|
140
149
|
innerWidth,
|
|
@@ -163,7 +172,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
163
172
|
)
|
|
164
173
|
|
|
165
174
|
watch(
|
|
166
|
-
() => [props.displayMode, props.integerZoom
|
|
175
|
+
() => [props.displayMode, props.integerZoom],
|
|
167
176
|
() => {
|
|
168
177
|
const previousPage = currentPage.value
|
|
169
178
|
currentPage.value = 1
|
|
@@ -31,8 +31,11 @@ export interface PreparedWaveformSeries {
|
|
|
31
31
|
hasErrorPoints: boolean
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function prepareWaveformSeries(
|
|
35
|
-
|
|
34
|
+
export function prepareWaveformSeries(
|
|
35
|
+
data: WaveformData,
|
|
36
|
+
retainEmpty = false,
|
|
37
|
+
): PreparedWaveformSeries[] {
|
|
38
|
+
return normalizeWaveformSeriesSources(data, retainEmpty).map(({ source, ...series }) => {
|
|
36
39
|
const sourceMetrics = source.metrics(series.errorBar.visible)
|
|
37
40
|
return {
|
|
38
41
|
...series,
|
|
@@ -61,11 +64,14 @@ export function usePreparedWaveformSeries(
|
|
|
61
64
|
data: () => WaveformData,
|
|
62
65
|
onBeforeDataChange: () => void,
|
|
63
66
|
onDataChange: () => void,
|
|
67
|
+
retainEmpty: () => boolean = () => false,
|
|
64
68
|
) {
|
|
65
|
-
const preparedSeries = shallowRef<PreparedWaveformSeries[]>(
|
|
66
|
-
|
|
69
|
+
const preparedSeries = shallowRef<PreparedWaveformSeries[]>(
|
|
70
|
+
prepareWaveformSeries(data(), retainEmpty()),
|
|
71
|
+
)
|
|
72
|
+
watch([data, retainEmpty], ([nextData, preserveEmpty]) => {
|
|
67
73
|
onBeforeDataChange()
|
|
68
|
-
preparedSeries.value = prepareWaveformSeries(nextData)
|
|
74
|
+
preparedSeries.value = prepareWaveformSeries(nextData, preserveEmpty)
|
|
69
75
|
onDataChange()
|
|
70
76
|
})
|
|
71
77
|
return preparedSeries
|
|
@@ -52,6 +52,9 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
52
52
|
annotationInteraction,
|
|
53
53
|
linePointOverrides,
|
|
54
54
|
} = context
|
|
55
|
+
// Display-unit switches must not change the base used by existing D3 transforms.
|
|
56
|
+
const domainTimeUnit = props.timeUnit
|
|
57
|
+
const domainOptions = () => ({ integerZoom: props.integerZoom, timeUnit: domainTimeUnit })
|
|
55
58
|
const chartSeries = computed<DisplaySeries[]>(() =>
|
|
56
59
|
preparedSeries.value.map((series, index): DisplaySeries => ({
|
|
57
60
|
...series,
|
|
@@ -174,12 +177,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
174
177
|
props.xDomainStrategy,
|
|
175
178
|
true,
|
|
176
179
|
),
|
|
177
|
-
|
|
180
|
+
domainOptions(),
|
|
178
181
|
)
|
|
179
182
|
}
|
|
180
183
|
return alignIntegerZoomDomain(
|
|
181
184
|
applyXDomainStrategy(sharedXDomain.value, props.xDomainStrategy),
|
|
182
|
-
|
|
185
|
+
domainOptions(),
|
|
183
186
|
)
|
|
184
187
|
})
|
|
185
188
|
const resolveOriginalTrackDomain = (track: TrackLayout): [number, number] => {
|
|
@@ -201,7 +204,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
201
204
|
props.xDomainStrategy,
|
|
202
205
|
true,
|
|
203
206
|
),
|
|
204
|
-
|
|
207
|
+
domainOptions(),
|
|
205
208
|
)
|
|
206
209
|
}
|
|
207
210
|
return alignIntegerZoomDomain(
|
|
@@ -209,7 +212,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
209
212
|
paddedDomain(track.seriesList.flatMap((series) => series.xDomain)),
|
|
210
213
|
props.xDomainStrategy,
|
|
211
214
|
),
|
|
212
|
-
|
|
215
|
+
domainOptions(),
|
|
213
216
|
)
|
|
214
217
|
}
|
|
215
218
|
const initialXDomain = computed(
|
|
@@ -261,10 +264,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
261
264
|
effectiveXDomains: context.boundaries?.value,
|
|
262
265
|
xDomainStrategy: props.xDomainStrategy,
|
|
263
266
|
integerZoom: props.integerZoom,
|
|
267
|
+
amplitudeLimits: props.amplitudeLimits,
|
|
264
268
|
fixedYDomain: props.yDomain,
|
|
265
269
|
fixedYDomains: props.yDomains,
|
|
266
270
|
yDomains: viewportYDomains.value,
|
|
267
271
|
timeUnit: props.timeUnit,
|
|
272
|
+
domainTimeUnit,
|
|
268
273
|
xAxisLabelFormatter: props.axes?.x?.labelFormatter,
|
|
269
274
|
yAxisSplitNumber: props.axes?.y?.splitNumber,
|
|
270
275
|
yAxisNice: props.axes?.y?.nice,
|
|
@@ -15,6 +15,7 @@ export function useWaveformMeasurements(
|
|
|
15
15
|
| 'titleAreaReserved'
|
|
16
16
|
| 'titleMeasureStyle'
|
|
17
17
|
>,
|
|
18
|
+
resizeHooks: { beforeResize?: () => void; afterResize?: () => void } = {},
|
|
18
19
|
) {
|
|
19
20
|
const {
|
|
20
21
|
container,
|
|
@@ -53,8 +54,10 @@ export function useWaveformMeasurements(
|
|
|
53
54
|
onMounted(() => {
|
|
54
55
|
if (!container.value) return
|
|
55
56
|
resizeObserver.value = new ResizeObserver(([entry]) => {
|
|
57
|
+
resizeHooks.beforeResize?.()
|
|
56
58
|
observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
|
|
57
59
|
observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
|
|
60
|
+
resizeHooks.afterResize?.()
|
|
58
61
|
void nextTick(measureTitle)
|
|
59
62
|
})
|
|
60
63
|
resizeObserver.value.observe(container.value)
|
|
@@ -103,6 +103,7 @@ function errorPayload(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export function useWaveformRenderSampling(context: SamplingContext) {
|
|
106
|
+
const resultState = shallowRef<'success' | 'fallback' | 'error'>('success')
|
|
106
107
|
const settledSignature = shallowRef<string>()
|
|
107
108
|
const dataEpoch = shallowRef(0)
|
|
108
109
|
const session = createWaveformSamplingSession()
|
|
@@ -292,10 +293,14 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
292
293
|
requestId: ++session.requestId,
|
|
293
294
|
series: batch,
|
|
294
295
|
})
|
|
295
|
-
if (result.type !== 'sample-viewport-response')
|
|
296
|
+
if (result.type !== 'sample-viewport-response') {
|
|
297
|
+
if (samplingScheduler.isCurrent(token)) resultState.value = 'error'
|
|
298
|
+
return
|
|
299
|
+
}
|
|
296
300
|
response = result
|
|
297
301
|
} catch (error) {
|
|
298
302
|
if (!samplingScheduler.isCurrent(token)) return
|
|
303
|
+
resultState.value = 'error'
|
|
299
304
|
emitErrorOnce(
|
|
300
305
|
errorPayload(
|
|
301
306
|
error instanceof Error ? error.message : String(error),
|
|
@@ -310,8 +315,18 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
310
315
|
return
|
|
311
316
|
}
|
|
312
317
|
if (!samplingScheduler.isCurrent(token)) return
|
|
318
|
+
if (response.workerError || client.workerFailureReason) resultState.value = 'fallback'
|
|
313
319
|
const sourceById = new Map(sampledTargets.map((target) => [target.series.id, target.source]))
|
|
320
|
+
if (
|
|
321
|
+
sampledTargets.some(
|
|
322
|
+
(target) =>
|
|
323
|
+
!response.results.some((result) => result.seriesId === target.series.id && result.output),
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
resultState.value = 'error'
|
|
314
327
|
response.results.forEach((result) => {
|
|
328
|
+
if (result.status !== 'ok' && resultState.value !== 'error')
|
|
329
|
+
resultState.value = result.output ? 'fallback' : 'error'
|
|
315
330
|
const source = sourceById.get(result.seriesId)
|
|
316
331
|
const selected = source ? sourcePoints(result, source) : undefined
|
|
317
332
|
if (selected && (result.status === 'ok' || result.status === 'wasm-unavailable')) {
|
|
@@ -369,6 +384,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
369
384
|
const scheduleSampling = (hysteresis: boolean) => {
|
|
370
385
|
settledSignature.value = undefined
|
|
371
386
|
const signature = targetSignature.value
|
|
387
|
+
resultState.value = 'success'
|
|
372
388
|
samplingScheduler.schedule(async (token) => {
|
|
373
389
|
try {
|
|
374
390
|
await runSampling(token, hysteresis)
|
|
@@ -410,6 +426,12 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
410
426
|
onScopeDispose(() => session.dispose())
|
|
411
427
|
|
|
412
428
|
return {
|
|
429
|
+
requestSignature: targetSignature,
|
|
430
|
+
state: computed(() =>
|
|
431
|
+
settledSignature.value === targetSignature.value && !session.autoModeSettleTimer
|
|
432
|
+
? resultState.value
|
|
433
|
+
: 'pending',
|
|
434
|
+
),
|
|
413
435
|
linePointOverrides: context.linePointOverrides,
|
|
414
436
|
ready: () => settledSignature.value === targetSignature.value && !session.autoModeSettleTimer,
|
|
415
437
|
}
|
|
@@ -49,6 +49,7 @@ export function useWaveformYAxisLayout(context: YAxisLayoutContext) {
|
|
|
49
49
|
props.axes?.y,
|
|
50
50
|
showUnits.value,
|
|
51
51
|
isEdgeCompact.value ? measureYAxisTextWidth : undefined,
|
|
52
|
+
props.amplitudeLimits,
|
|
52
53
|
),
|
|
53
54
|
)
|
|
54
55
|
const hasYAxisLabels = computed(() =>
|
|
@@ -84,6 +85,7 @@ export function useWaveformYAxisLayout(context: YAxisLayoutContext) {
|
|
|
84
85
|
props.axes?.y,
|
|
85
86
|
showUnits.value,
|
|
86
87
|
isEdgeCompact.value ? measureYAxisTextWidth : undefined,
|
|
88
|
+
props.amplitudeLimits,
|
|
87
89
|
),
|
|
88
90
|
)
|
|
89
91
|
const resolvedChartLeftMargin = computed(() =>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { WaveformAnimationProps } from '../../animation/types'
|
|
1
2
|
import type { WaveformToolbarOptions, WaveformControlState } from '../../types/controls'
|
|
2
3
|
import type {
|
|
4
|
+
WaveformAmplitudeLimits,
|
|
3
5
|
WaveformAnnotation,
|
|
4
6
|
WaveformAxesOptions,
|
|
5
7
|
WaveformData,
|
|
@@ -26,7 +28,7 @@ import type {
|
|
|
26
28
|
} from '../data/types'
|
|
27
29
|
import type { WaveformGridOptions } from './grid'
|
|
28
30
|
|
|
29
|
-
export interface WaveformChartProps {
|
|
31
|
+
export interface WaveformChartProps extends WaveformAnimationProps {
|
|
30
32
|
toolbar?: boolean | WaveformToolbarOptions
|
|
31
33
|
data: WaveformData
|
|
32
34
|
displayMode?: WaveformDisplayMode
|
|
@@ -42,7 +44,7 @@ export interface WaveformChartProps {
|
|
|
42
44
|
lineColor?: string
|
|
43
45
|
showTooltip?: boolean
|
|
44
46
|
zoomable?: boolean
|
|
45
|
-
/**
|
|
47
|
+
/** Prefer integer display-unit endpoints; preserve any window narrower than one unit. */
|
|
46
48
|
integerZoom?: boolean
|
|
47
49
|
pannable?: boolean
|
|
48
50
|
/** Global record bounds in seconds; enables horizontal panning beyond loaded data. */
|
|
@@ -53,6 +55,8 @@ export interface WaveformChartProps {
|
|
|
53
55
|
initialXDomain?: [number, number]
|
|
54
56
|
initialXDomains?: Record<string, [number, number]>
|
|
55
57
|
xDomainStrategy?: WaveformXDomainStrategy
|
|
58
|
+
/** Strict bounds per normalized series ID; active bounds override Y viewports. */
|
|
59
|
+
amplitudeLimits?: WaveformAmplitudeLimits
|
|
56
60
|
yDomain?: [number, number]
|
|
57
61
|
yDomains?: Record<string, [number, number]>
|
|
58
62
|
timeUnit?: 's' | 'ms'
|