waveform-analysis 0.1.71 → 0.1.73
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 +260 -40
- package/dist/assets/{workerRuntime-o5An0M-W.js → workerRuntime-5p-EI-GL.js} +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.js +3874 -2778
- package/dist/style.css +1 -1
- package/dist/types/components/WaveformChart.vue.d.ts +24 -6
- package/dist/types/components/annotation/WaveformAnnotationLayer.vue.d.ts +2 -0
- package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +0 -1
- package/dist/types/components/controls/WaveformToolbar.vue.d.ts +7 -0
- package/dist/types/components/controls/chartExportOptions.d.ts +5 -0
- package/dist/types/components/controls/chartSnapshot.d.ts +2 -0
- package/dist/types/components/controls/snapshotTitle.d.ts +2 -0
- package/dist/types/components/controls/toolbarRegistry.d.ts +21 -0
- package/dist/types/components/controls/useChartCommands.d.ts +42 -0
- package/dist/types/components/controls/useChartImageExport.d.ts +17 -0
- package/dist/types/components/controls/useControlMode.d.ts +8 -0
- package/dist/types/components/core/latestTaskScheduler.d.ts +1 -1
- package/dist/types/components/core/layout.d.ts +6 -3
- package/dist/types/components/core/niceScale.d.ts +34 -0
- package/dist/types/components/core/trackLayoutBuilder.d.ts +4 -1
- package/dist/types/components/core/trackPagination.d.ts +1 -1
- package/dist/types/components/core/types.d.ts +5 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +24 -6
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +2 -2
- package/dist/types/components/core/useWaveformData.d.ts +2 -0
- package/dist/types/components/core/useWaveformLayout.d.ts +4 -1
- package/dist/types/components/core/useWaveformMeasurements.d.ts +2 -0
- package/dist/types/components/core/useWaveformRenderSampling.d.ts +1 -0
- package/dist/types/components/core/useWaveformViewportRestore.d.ts +5 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +5 -0
- package/dist/types/components/core/waveformSamplingSession.d.ts +24 -0
- package/dist/types/components/core/yAxisLabelStyle.d.ts +3 -0
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
- package/dist/types/components/core/yAxisPadding.d.ts +7 -0
- package/dist/types/components/core/yAxisTitleLayout.d.ts +2 -0
- package/dist/types/components/interaction/useExtremaTickViewport.d.ts +5 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -0
- package/dist/types/components/rendering/WaveformLegend.vue.d.ts +2 -2
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +2 -3
- package/dist/types/components/rendering/WaveformTrackAxes.vue.d.ts +1 -1
- package/dist/types/components/rendering/WaveformTrackBackdrop.vue.d.ts +1 -0
- package/dist/types/components/rendering/compactFrame.d.ts +5 -0
- package/dist/types/components/rendering/yAxisTickVisibility.d.ts +11 -0
- package/dist/types/core/lineBoundary.d.ts +4 -0
- package/dist/types/core/rendering.d.ts +4 -0
- package/dist/types/core/renderingStrategies.d.ts +2 -0
- package/dist/types/core/workerSampling/outputBoundaries.d.ts +4 -0
- package/dist/types/data/simulatedWaveforms.d.ts +1 -1
- package/dist/types/data/sx2Channel.d.ts +10 -0
- package/dist/types/demo/DemoYAxisPaddingControls.vue.d.ts +11 -0
- package/dist/types/demo/types.d.ts +7 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types/chart.d.ts +32 -8
- package/dist/types/types/controls.d.ts +69 -0
- package/dist/types/types/index.d.ts +2 -1
- package/dist/types/utils/formatters.d.ts +1 -0
- package/package.json +7 -6
- package/src/App.vue +61 -14
- package/src/components/WaveformChart.css +10 -1
- package/src/components/WaveformChart.vue +6 -0
- package/src/components/WaveformChartView.vue +27 -26
- package/src/components/annotation/WaveformAnnotationLayer.vue +6 -1
- package/src/components/annotation/useWaveformChartAnnotations.ts +7 -18
- package/src/components/controls/WaveformToolbar.vue +247 -0
- package/src/components/controls/chartExportOptions.ts +32 -0
- package/src/components/controls/chartSnapshot.ts +132 -0
- package/src/components/controls/snapshotTitle.ts +73 -0
- package/src/components/controls/toolbarRegistry.ts +92 -0
- package/src/components/controls/useChartCommands.ts +294 -0
- package/src/components/controls/useChartImageExport.ts +155 -0
- package/src/components/controls/useControlMode.ts +30 -0
- package/src/components/core/latestTaskScheduler.ts +14 -2
- package/src/components/core/layout.ts +49 -36
- package/src/components/core/niceScale.ts +151 -0
- package/src/components/core/trackLayoutBuilder.ts +41 -17
- package/src/components/core/trackPagination.ts +3 -3
- package/src/components/core/types.ts +5 -0
- package/src/components/core/useWaveformChartController.ts +94 -3
- package/src/components/core/useWaveformChartLifecycle.ts +11 -111
- package/src/components/core/useWaveformData.ts +4 -0
- package/src/components/core/useWaveformLayout.ts +25 -5
- package/src/components/core/useWaveformMeasurements.ts +63 -0
- package/src/components/core/useWaveformRenderSampling.ts +80 -59
- package/src/components/core/useWaveformViewportRestore.ts +105 -0
- package/src/components/core/useWaveformYAxisLayout.ts +3 -2
- package/src/components/core/waveformChartTypes.ts +5 -0
- package/src/components/core/waveformSamplingSession.ts +49 -0
- package/src/components/core/workerSamplingClient.ts +38 -6
- package/src/components/core/yAxisLabelStyle.ts +10 -0
- package/src/components/core/yAxisLayoutMetrics.ts +11 -9
- package/src/components/core/yAxisPadding.ts +60 -0
- package/src/components/core/yAxisTitleLayout.ts +11 -12
- package/src/components/interaction/useExtremaTickViewport.ts +43 -0
- package/src/components/interaction/useWaveformViewport.ts +11 -2
- package/src/components/rendering/WaveformTrack.css +1 -2
- package/src/components/rendering/WaveformTrack.vue +5 -12
- package/src/components/rendering/WaveformTrackAxes.vue +66 -19
- package/src/components/rendering/WaveformTrackBackdrop.vue +8 -3
- package/src/components/rendering/compactFrame.ts +31 -0
- package/src/components/rendering/yAxisTickVisibility.ts +156 -0
- package/src/core/lineBoundary.ts +24 -0
- package/src/core/rendering.ts +48 -25
- package/src/core/renderingStrategies.ts +19 -13
- package/src/core/waveformPointSource.ts +23 -1
- package/src/core/workerSampling/outputBoundaries.ts +32 -0
- package/src/core/workerSampling/repository.ts +26 -18
- package/src/core/workerSampling/repositoryDataset.ts +33 -1
- package/src/core/workerSampling/workerRuntime.ts +13 -3
- package/src/data/simulatedWaveforms.ts +67 -2
- package/src/data/sx2Channel.ts +315 -0
- package/src/demo/DemoChartHost.vue +5 -1
- package/src/demo/DemoGridFrameControls.vue +2 -0
- package/src/demo/DemoViewControls.vue +5 -2
- package/src/demo/DemoYAxisPaddingControls.vue +53 -0
- package/src/demo/embedBridge.ts +85 -0
- package/src/demo/types.ts +7 -0
- package/src/index.ts +19 -0
- package/src/types/chart.ts +34 -8
- package/src/types/controls.ts +80 -0
- package/src/types/index.ts +18 -0
- package/src/utils/formatters.ts +2 -1
- package/src/views/WasmSamplingDemo.vue +8 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useWaveformMeasurements } from './useWaveformMeasurements'
|
|
2
|
+
import { useWaveformViewportRestore } from './useWaveformViewportRestore'
|
|
1
3
|
import { zoomIdentity, type ZoomTransform } from 'd3'
|
|
2
4
|
import {
|
|
3
5
|
nextTick,
|
|
@@ -13,12 +15,10 @@ import type { AnnotationSeriesCandidate, useWaveformAnnotationInteraction } from
|
|
|
13
15
|
import type { NormalizedWaveformGridOptions } from './grid'
|
|
14
16
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
15
17
|
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
|
16
|
-
import { transformForDomain } from '../interaction/zoomConstraints'
|
|
17
|
-
import { remotePanBoundary, restoreViewportDomain } from '../interaction/remoteViewport'
|
|
18
18
|
import { isEditableTarget } from '../interaction/interactionTarget'
|
|
19
|
-
import { seriesIdentity } from '../interaction/zoomEventPayload'
|
|
20
19
|
|
|
21
|
-
interface LifecycleContext {
|
|
20
|
+
export interface LifecycleContext {
|
|
21
|
+
preserveFullViewport?: () => boolean
|
|
22
22
|
props: ResolvedWaveformChartProps
|
|
23
23
|
emit: WaveformChartEmit
|
|
24
24
|
container: Ref<HTMLDivElement | undefined>
|
|
@@ -64,19 +64,10 @@ interface LifecycleContext {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
67
|
+
useWaveformMeasurements(context)
|
|
67
68
|
const {
|
|
68
69
|
props,
|
|
69
70
|
emit,
|
|
70
|
-
container,
|
|
71
|
-
titleMeasureElement,
|
|
72
|
-
resizeObserver,
|
|
73
|
-
observedWidth,
|
|
74
|
-
observedHeight,
|
|
75
|
-
measuredTitleWidth,
|
|
76
|
-
measuredTitleHeight,
|
|
77
|
-
resolvedTitleText,
|
|
78
|
-
titleAreaReserved,
|
|
79
|
-
titleMeasureStyle,
|
|
80
71
|
spacePressed,
|
|
81
72
|
pointerInsideChart,
|
|
82
73
|
currentPage,
|
|
@@ -85,18 +76,13 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
85
76
|
gridOptions,
|
|
86
77
|
chartSeries,
|
|
87
78
|
chartTracks,
|
|
88
|
-
trackLayouts,
|
|
89
79
|
innerWidth,
|
|
90
80
|
innerHeight,
|
|
91
|
-
sharedZoomDomain,
|
|
92
|
-
initialXDomain,
|
|
93
|
-
sharedTransform,
|
|
94
81
|
activeInteractionMode,
|
|
95
82
|
hiddenSeriesIdSet,
|
|
96
83
|
internalHiddenSeriesIds,
|
|
97
84
|
independentTransforms,
|
|
98
85
|
independentYDomains,
|
|
99
|
-
resolveInitialTrackDomain,
|
|
100
86
|
annotationInteraction,
|
|
101
87
|
editorSeriesOptions,
|
|
102
88
|
isPresentationMode,
|
|
@@ -110,6 +96,10 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
110
96
|
} = context
|
|
111
97
|
|
|
112
98
|
function handleInteractionKeyDown(event: KeyboardEvent) {
|
|
99
|
+
if (event.key === 'Escape') {
|
|
100
|
+
cancelViewportDrag()
|
|
101
|
+
cancelAnnotation()
|
|
102
|
+
}
|
|
113
103
|
if (
|
|
114
104
|
isPresentationMode.value ||
|
|
115
105
|
event.code !== 'Space' ||
|
|
@@ -142,68 +132,8 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
142
132
|
emit('page-change', nextPage, pageCount.value)
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
function handleBeforeDataReferenceChange() {
|
|
149
|
-
if (props.displayMode === 'independent') {
|
|
150
|
-
pendingSharedXDomain = undefined
|
|
151
|
-
pendingIndependentXDomains = new Map(
|
|
152
|
-
trackLayouts.value.flatMap((track) => {
|
|
153
|
-
const current = track.xScale.domain() as [number, number]
|
|
154
|
-
const boundary = resolveInitialTrackDomain(track)
|
|
155
|
-
return remotePanBoundary(props) ||
|
|
156
|
-
current[1] - current[0] < boundary[1] - boundary[0] - 1e-12
|
|
157
|
-
? [[seriesIdentity(track.seriesList.map((series) => series.id)), current]]
|
|
158
|
-
: []
|
|
159
|
-
}),
|
|
160
|
-
)
|
|
161
|
-
return
|
|
162
|
-
}
|
|
163
|
-
pendingIndependentXDomains = undefined
|
|
164
|
-
const current = sharedZoomDomain.value
|
|
165
|
-
const boundary = initialXDomain.value
|
|
166
|
-
pendingSharedXDomain =
|
|
167
|
-
remotePanBoundary(props) || current[1] - current[0] < boundary[1] - boundary[0] - 1e-12
|
|
168
|
-
? [...current]
|
|
169
|
-
: undefined
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function handleDataReferenceChange() {
|
|
173
|
-
if (props.displayMode === 'independent') {
|
|
174
|
-
const currentTransforms = independentTransforms.value
|
|
175
|
-
independentTransforms.value = chartTracks.value.map(
|
|
176
|
-
(_track, index) => currentTransforms[index] ?? zoomIdentity,
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
clearHover()
|
|
180
|
-
editorSeriesOptions.value = []
|
|
181
|
-
void nextTick(() => {
|
|
182
|
-
if (props.displayMode === 'independent' && pendingIndependentXDomains) {
|
|
183
|
-
const nextTransforms = chartTracks.value.map(() => zoomIdentity)
|
|
184
|
-
trackLayouts.value.forEach((track) => {
|
|
185
|
-
const previousDomain = pendingIndependentXDomains?.get(
|
|
186
|
-
seriesIdentity(track.seriesList.map((series) => series.id)),
|
|
187
|
-
)
|
|
188
|
-
if (!previousDomain) return
|
|
189
|
-
const boundary = resolveInitialTrackDomain(track)
|
|
190
|
-
const domain = restoreViewportDomain(previousDomain, boundary, [track.seriesList], props)
|
|
191
|
-
nextTransforms[track.index] = transformForDomain(domain, boundary, track.width)
|
|
192
|
-
})
|
|
193
|
-
independentTransforms.value = nextTransforms
|
|
194
|
-
pendingIndependentXDomains = undefined
|
|
195
|
-
} else if (props.displayMode !== 'independent' && pendingSharedXDomain) {
|
|
196
|
-
const boundary = initialXDomain.value
|
|
197
|
-
const groups = trackLayouts.value
|
|
198
|
-
.filter((track) => track.hasVisibleSeries)
|
|
199
|
-
.map((track) => track.seriesList)
|
|
200
|
-
const domain = restoreViewportDomain(pendingSharedXDomain, boundary, groups, props)
|
|
201
|
-
sharedTransform.value = transformForDomain(domain, boundary, innerWidth.value)
|
|
202
|
-
pendingSharedXDomain = undefined
|
|
203
|
-
}
|
|
204
|
-
configureZoom()
|
|
205
|
-
})
|
|
206
|
-
}
|
|
135
|
+
const { handleBeforeDataReferenceChange, handleDataReferenceChange } =
|
|
136
|
+
useWaveformViewportRestore(context)
|
|
207
137
|
|
|
208
138
|
watch(
|
|
209
139
|
[
|
|
@@ -347,45 +277,15 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
347
277
|
{ deep: true },
|
|
348
278
|
)
|
|
349
279
|
|
|
350
|
-
function measureTitle() {
|
|
351
|
-
if (!titleAreaReserved.value || !titleMeasureElement.value) {
|
|
352
|
-
measuredTitleWidth.value = 0
|
|
353
|
-
measuredTitleHeight.value = 0
|
|
354
|
-
return
|
|
355
|
-
}
|
|
356
|
-
const bounds = titleMeasureElement.value.getBoundingClientRect()
|
|
357
|
-
measuredTitleWidth.value = titleMeasureElement.value.scrollWidth || bounds.width
|
|
358
|
-
measuredTitleHeight.value = titleMeasureElement.value.scrollHeight || bounds.height
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
watch(
|
|
362
|
-
[resolvedTitleText, titleAreaReserved, titleMeasureStyle],
|
|
363
|
-
async () => {
|
|
364
|
-
measuredTitleWidth.value = 0
|
|
365
|
-
measuredTitleHeight.value = 0
|
|
366
|
-
await nextTick()
|
|
367
|
-
measureTitle()
|
|
368
|
-
},
|
|
369
|
-
{ immediate: true },
|
|
370
|
-
)
|
|
371
|
-
|
|
372
280
|
onMounted(() => {
|
|
373
281
|
window.addEventListener('keydown', handleInteractionKeyDown)
|
|
374
282
|
window.addEventListener('keyup', handleInteractionKeyUp)
|
|
375
|
-
if (!container.value) return
|
|
376
|
-
resizeObserver.value = new ResizeObserver(([entry]) => {
|
|
377
|
-
observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
|
|
378
|
-
observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
|
|
379
|
-
void nextTick(measureTitle)
|
|
380
|
-
})
|
|
381
|
-
resizeObserver.value.observe(container.value)
|
|
382
283
|
})
|
|
383
284
|
|
|
384
285
|
onBeforeUnmount(() => {
|
|
385
286
|
window.removeEventListener('keydown', handleInteractionKeyDown)
|
|
386
287
|
window.removeEventListener('keyup', handleInteractionKeyUp)
|
|
387
288
|
cancelPendingHover()
|
|
388
|
-
resizeObserver.value?.disconnect()
|
|
389
289
|
clearZoomBindings()
|
|
390
290
|
editorSeriesOptions.value = []
|
|
391
291
|
})
|
|
@@ -25,6 +25,8 @@ export interface PreparedWaveformSeries {
|
|
|
25
25
|
points: WaveformPoint[]
|
|
26
26
|
source: WaveformPointSource
|
|
27
27
|
xDomain: [number, number]
|
|
28
|
+
rawYMaximum?: number
|
|
29
|
+
rawYMinimum?: number
|
|
28
30
|
yDomain: [number, number]
|
|
29
31
|
hasErrorPoints: boolean
|
|
30
32
|
}
|
|
@@ -48,6 +50,8 @@ export function prepareWaveformSeries(data: WaveformData): PreparedWaveformSerie
|
|
|
48
50
|
? [sourceMetrics.yMinimum, sourceMetrics.yMaximum]
|
|
49
51
|
: [],
|
|
50
52
|
),
|
|
53
|
+
rawYMaximum: Number.isFinite(sourceMetrics.yMaximum) ? sourceMetrics.yMaximum : undefined,
|
|
54
|
+
rawYMinimum: Number.isFinite(sourceMetrics.yMinimum) ? sourceMetrics.yMinimum : undefined,
|
|
51
55
|
hasErrorPoints: sourceMetrics.hasErrorPoints,
|
|
52
56
|
}
|
|
53
57
|
})
|
|
@@ -17,9 +17,11 @@ import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
|
17
17
|
import { applyXDomainStrategy } from './xDomain'
|
|
18
18
|
import { alignIntegerZoomDomain, normalizeIntegerZoomDomain } from '../interaction/integerZoom'
|
|
19
19
|
import { resolveViewportYDomains } from './yAxisLayoutMetrics'
|
|
20
|
+
import { useExtremaTickViewport } from '../interaction/useExtremaTickViewport'
|
|
20
21
|
import { useWaveformYAxisLayout } from './useWaveformYAxisLayout'
|
|
21
22
|
import type { useWaveformAnnotationInteraction } from '../annotation'
|
|
22
23
|
interface LayoutContext {
|
|
24
|
+
boundaries?: ShallowRef<Record<string, [number, number]>>
|
|
23
25
|
props: ResolvedWaveformChartProps
|
|
24
26
|
preparedSeries: ShallowRef<PreparedWaveformSeries[]>
|
|
25
27
|
currentPage: Ref<number>
|
|
@@ -147,7 +149,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
147
149
|
const hasChartArea = computed(() => innerWidth.value > 0 && innerHeight.value > 0)
|
|
148
150
|
const resolvedXLabel = computed(() => props.xLabel ?? `时间(${props.timeUnit})`)
|
|
149
151
|
const activeInteractionMode = computed(() => props.interactionMode)
|
|
150
|
-
const isZoomMode = computed(() => activeInteractionMode.value
|
|
152
|
+
const isZoomMode = computed(() => activeInteractionMode.value === 'zoom')
|
|
151
153
|
const sharedXDomain = computed(() => {
|
|
152
154
|
const values: number[] = []
|
|
153
155
|
const tracks = chartTracks.value.some((track) => track.visibleSeries.length)
|
|
@@ -158,7 +160,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
158
160
|
})
|
|
159
161
|
return paddedDomain(values)
|
|
160
162
|
})
|
|
161
|
-
const
|
|
163
|
+
const originalXDomain = computed<[number, number]>(() => {
|
|
162
164
|
const domain = props.initialXDomain
|
|
163
165
|
if (
|
|
164
166
|
domain &&
|
|
@@ -180,7 +182,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
180
182
|
props,
|
|
181
183
|
)
|
|
182
184
|
})
|
|
183
|
-
const
|
|
185
|
+
const resolveOriginalTrackDomain = (track: TrackLayout): [number, number] => {
|
|
184
186
|
const configuredDomain =
|
|
185
187
|
props.initialXDomains?.[track.series?.trackId ?? track.series?.id ?? track.id] ??
|
|
186
188
|
(track.series ? props.initialXDomains?.[track.series.id] : undefined) ??
|
|
@@ -210,6 +212,13 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
210
212
|
props,
|
|
211
213
|
)
|
|
212
214
|
}
|
|
215
|
+
const initialXDomain = computed(
|
|
216
|
+
() =>
|
|
217
|
+
(props.displayMode !== 'independent' ? context.boundaries?.value.shared : undefined) ??
|
|
218
|
+
originalXDomain.value,
|
|
219
|
+
)
|
|
220
|
+
const resolveInitialTrackDomain = (track: TrackLayout): [number, number] =>
|
|
221
|
+
context.boundaries?.value[track.id] ?? resolveOriginalTrackDomain(track)
|
|
213
222
|
const sharedZoomDomain = computed(() =>
|
|
214
223
|
normalizeIntegerZoomDomain(
|
|
215
224
|
sharedTransform.value
|
|
@@ -240,6 +249,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
240
249
|
useNonEmptyBottomTracks: props.layoutPreset === 'edge-compact',
|
|
241
250
|
compactYAxisLayout: props.layoutPreset === 'edge-compact',
|
|
242
251
|
showAxisUnits:
|
|
252
|
+
props.unitDisplayMode !== 'channel-label' &&
|
|
243
253
|
props.unitDisplayMode !== 'channel-label-or-legend' &&
|
|
244
254
|
(props.layoutPreset !== 'edge-compact' || props.unitDisplayMode === 'axis'),
|
|
245
255
|
displayMode: props.displayMode,
|
|
@@ -248,6 +258,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
248
258
|
sharedZoomDomain: sharedZoomDomain.value,
|
|
249
259
|
initialXDomain: props.initialXDomain ? initialXDomain.value : undefined,
|
|
250
260
|
initialXDomains: props.initialXDomains,
|
|
261
|
+
effectiveXDomains: context.boundaries?.value,
|
|
251
262
|
xDomainStrategy: props.xDomainStrategy,
|
|
252
263
|
integerZoom: props.integerZoom,
|
|
253
264
|
fixedYDomain: props.yDomain,
|
|
@@ -257,7 +268,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
257
268
|
xAxisLabelFormatter: props.axes?.x?.labelFormatter,
|
|
258
269
|
yAxisSplitNumber: props.axes?.y?.splitNumber,
|
|
259
270
|
yAxisNice: props.axes?.y?.nice,
|
|
260
|
-
|
|
271
|
+
yAxisPadding: props.axes?.y,
|
|
261
272
|
rendering: renderingOptions.value,
|
|
262
273
|
linePointOverrides: linePointOverrides?.value,
|
|
263
274
|
hideSecondaryLabels: isCleanView.value || yAxisLayout.value.hideSecondaryLabels,
|
|
@@ -266,9 +277,16 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
266
277
|
yAxisSlots.value.slots.find((slot) => slot.side === 'left' && slot.sideIndex === 0)
|
|
267
278
|
?.labelOffset ?? yAxisMetrics.value.labelCenterX,
|
|
268
279
|
yAxisSlots: yAxisSlots.value.slots,
|
|
269
|
-
|
|
280
|
+
showEmptyTracks: hasWaveformData.value,
|
|
270
281
|
}),
|
|
271
282
|
)
|
|
283
|
+
useExtremaTickViewport(
|
|
284
|
+
() => props.axes?.y?.extremaTicksVisible !== false,
|
|
285
|
+
innerWidth,
|
|
286
|
+
trackLayouts,
|
|
287
|
+
sharedTransform,
|
|
288
|
+
independentTransforms,
|
|
289
|
+
)
|
|
272
290
|
const annotationLayoutsForTrack = (track: TrackLayout): AnnotationTrackLayout[] =>
|
|
273
291
|
track.seriesList.map((series) => ({
|
|
274
292
|
...track,
|
|
@@ -329,6 +347,8 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
329
347
|
activeInteractionMode,
|
|
330
348
|
isZoomMode,
|
|
331
349
|
initialXDomain,
|
|
350
|
+
originalXDomain,
|
|
351
|
+
resolveOriginalTrackDomain,
|
|
332
352
|
sharedZoomDomain,
|
|
333
353
|
resolveInitialTrackDomain,
|
|
334
354
|
gridCells,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { nextTick, watch, onMounted, onBeforeUnmount } from 'vue'
|
|
2
|
+
import type { LifecycleContext } from './useWaveformChartLifecycle'
|
|
3
|
+
|
|
4
|
+
export function useWaveformMeasurements(
|
|
5
|
+
context: Pick<
|
|
6
|
+
LifecycleContext,
|
|
7
|
+
| 'container'
|
|
8
|
+
| 'titleMeasureElement'
|
|
9
|
+
| 'resizeObserver'
|
|
10
|
+
| 'observedWidth'
|
|
11
|
+
| 'observedHeight'
|
|
12
|
+
| 'measuredTitleWidth'
|
|
13
|
+
| 'measuredTitleHeight'
|
|
14
|
+
| 'resolvedTitleText'
|
|
15
|
+
| 'titleAreaReserved'
|
|
16
|
+
| 'titleMeasureStyle'
|
|
17
|
+
>,
|
|
18
|
+
) {
|
|
19
|
+
const {
|
|
20
|
+
container,
|
|
21
|
+
titleMeasureElement,
|
|
22
|
+
resizeObserver,
|
|
23
|
+
observedWidth,
|
|
24
|
+
observedHeight,
|
|
25
|
+
measuredTitleWidth,
|
|
26
|
+
measuredTitleHeight,
|
|
27
|
+
resolvedTitleText,
|
|
28
|
+
titleAreaReserved,
|
|
29
|
+
titleMeasureStyle,
|
|
30
|
+
} = context
|
|
31
|
+
function measureTitle() {
|
|
32
|
+
if (!titleAreaReserved.value || !titleMeasureElement.value) {
|
|
33
|
+
measuredTitleWidth.value = 0
|
|
34
|
+
measuredTitleHeight.value = 0
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
const bounds = titleMeasureElement.value.getBoundingClientRect()
|
|
38
|
+
measuredTitleWidth.value = titleMeasureElement.value.scrollWidth || bounds.width
|
|
39
|
+
measuredTitleHeight.value = titleMeasureElement.value.scrollHeight || bounds.height
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
watch(
|
|
43
|
+
[resolvedTitleText, titleAreaReserved, titleMeasureStyle],
|
|
44
|
+
async () => {
|
|
45
|
+
measuredTitleWidth.value = 0
|
|
46
|
+
measuredTitleHeight.value = 0
|
|
47
|
+
await nextTick()
|
|
48
|
+
measureTitle()
|
|
49
|
+
},
|
|
50
|
+
{ immediate: true },
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
onMounted(() => {
|
|
54
|
+
if (!container.value) return
|
|
55
|
+
resizeObserver.value = new ResizeObserver(([entry]) => {
|
|
56
|
+
observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
|
|
57
|
+
observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
|
|
58
|
+
void nextTick(measureTitle)
|
|
59
|
+
})
|
|
60
|
+
resizeObserver.value.observe(container.value)
|
|
61
|
+
})
|
|
62
|
+
onBeforeUnmount(() => resizeObserver.value?.disconnect())
|
|
63
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createWaveformSamplingSession } from './waveformSamplingSession'
|
|
2
|
+
import { lineBoundaryIndexes, withLineBoundaries } from '../../core/lineBoundary'
|
|
1
3
|
import { computed, onScopeDispose, shallowRef, watch, type ComputedRef, type ShallowRef } from 'vue'
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -11,12 +13,8 @@ import { pointSourceFromPoints, type WaveformPointSource } from '../../core/wave
|
|
|
11
13
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
12
14
|
import type { DisplaySeries, TrackLayout } from './types'
|
|
13
15
|
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
createWorkerSamplingClient,
|
|
17
|
-
isDatasetResponse,
|
|
18
|
-
type WorkerSamplingClient,
|
|
19
|
-
} from './workerSamplingClient'
|
|
16
|
+
import { resolveAutoSelectedMode } from './latestTaskScheduler'
|
|
17
|
+
import { isDatasetResponse, type WorkerSamplingClient } from './workerSamplingClient'
|
|
20
18
|
|
|
21
19
|
interface SamplingTarget {
|
|
22
20
|
series: DisplaySeries
|
|
@@ -62,7 +60,8 @@ function rawDiagnostics(target: SamplingTarget, requestId: number): WorkerSampli
|
|
|
62
60
|
strategy: sampling.strategy === 'auto' ? 'peak' : sampling.strategy,
|
|
63
61
|
sourcePointCount: target.source.length,
|
|
64
62
|
visiblePointCount,
|
|
65
|
-
renderedPointCount:
|
|
63
|
+
renderedPointCount:
|
|
64
|
+
visiblePointCount + lineBoundaryIndexes(target.visibleRange, target.source.length).length,
|
|
66
65
|
durationMs: 0,
|
|
67
66
|
cacheHit: false,
|
|
68
67
|
requestId,
|
|
@@ -75,7 +74,7 @@ function rawDiagnostics(target: SamplingTarget, requestId: number): WorkerSampli
|
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
function
|
|
77
|
+
function fallbackInteriorPoints(target: SamplingTarget) {
|
|
79
78
|
const { source } = target
|
|
80
79
|
const { start, end } = target.visibleRange
|
|
81
80
|
const count = end - start
|
|
@@ -104,18 +103,17 @@ function errorPayload(
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
export function useWaveformRenderSampling(context: SamplingContext) {
|
|
106
|
+
const settledSignature = shallowRef<string>()
|
|
107
107
|
const dataEpoch = shallowRef(0)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let autoModeSettleTimer: ReturnType<typeof setTimeout> | undefined
|
|
118
|
-
let hasInitialSamplingRun = false
|
|
108
|
+
const session = createWaveformSamplingSession()
|
|
109
|
+
const {
|
|
110
|
+
revisions,
|
|
111
|
+
backendBySeries,
|
|
112
|
+
selectedModeBySeries,
|
|
113
|
+
emittedErrors,
|
|
114
|
+
pendingDiagnostics,
|
|
115
|
+
scheduler: samplingScheduler,
|
|
116
|
+
} = session
|
|
119
117
|
|
|
120
118
|
const targets = computed(() => {
|
|
121
119
|
const sampling = context.renderingOptions.value.sampling
|
|
@@ -169,7 +167,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
169
167
|
)
|
|
170
168
|
|
|
171
169
|
const flushDiagnostics = () => {
|
|
172
|
-
diagnosticsTimer = undefined
|
|
170
|
+
session.diagnosticsTimer = undefined
|
|
173
171
|
pendingDiagnostics.forEach((diagnostic) => {
|
|
174
172
|
const previousBackend = backendBySeries.get(diagnostic.seriesId)
|
|
175
173
|
if (previousBackend && previousBackend !== diagnostic.backend) {
|
|
@@ -193,7 +191,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
193
191
|
maxPendingRequestCount: samplingScheduler.metrics.maxPending,
|
|
194
192
|
}),
|
|
195
193
|
)
|
|
196
|
-
if (!diagnosticsTimer) diagnosticsTimer = setTimeout(flushDiagnostics, 100)
|
|
194
|
+
if (!session.diagnosticsTimer) session.diagnosticsTimer = setTimeout(flushDiagnostics, 100)
|
|
197
195
|
}
|
|
198
196
|
|
|
199
197
|
const emitErrorOnce = (payload: WaveformSamplingError) => {
|
|
@@ -213,7 +211,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
213
211
|
if (revisions.has(target.request.datasetId)) return
|
|
214
212
|
const response = await workerClient.send({
|
|
215
213
|
type: 'register-dataset',
|
|
216
|
-
requestId: ++requestId,
|
|
214
|
+
requestId: ++session.requestId,
|
|
217
215
|
datasetId: target.request.datasetId,
|
|
218
216
|
revision: 0,
|
|
219
217
|
dataset: target.source.toWorkerDataset(),
|
|
@@ -249,15 +247,22 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
249
247
|
previousSelectedMode,
|
|
250
248
|
) === 'raw'
|
|
251
249
|
if (target.request.mode === 'raw' || autoRaw) {
|
|
252
|
-
nextOverrides[target.series.id] =
|
|
253
|
-
target.visibleRange.start,
|
|
254
|
-
target.visibleRange
|
|
250
|
+
nextOverrides[target.series.id] = withLineBoundaries(
|
|
251
|
+
target.source.pointsInRange(target.visibleRange.start, target.visibleRange.end),
|
|
252
|
+
target.visibleRange,
|
|
253
|
+
target.source.length,
|
|
254
|
+
(index) => target.source.pointAt(index)!,
|
|
255
255
|
)
|
|
256
256
|
selectedModeBySeries.set(target.series.id, 'raw')
|
|
257
257
|
diagnostics.push(rawDiagnostics(target, token))
|
|
258
258
|
} else {
|
|
259
259
|
if (!nextOverrides[target.series.id]?.length) {
|
|
260
|
-
nextOverrides[target.series.id] =
|
|
260
|
+
nextOverrides[target.series.id] = withLineBoundaries(
|
|
261
|
+
fallbackInteriorPoints(target),
|
|
262
|
+
target.visibleRange,
|
|
263
|
+
target.source.length,
|
|
264
|
+
(index) => target.source.pointAt(index)!,
|
|
265
|
+
)
|
|
261
266
|
}
|
|
262
267
|
sampledTargets.push({
|
|
263
268
|
...target,
|
|
@@ -273,18 +278,18 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
273
278
|
}
|
|
274
279
|
|
|
275
280
|
context.linePointOverrides.value = nextOverrides
|
|
276
|
-
client
|
|
277
|
-
await registerTargets(client, sampledTargets, token)
|
|
278
|
-
if (!samplingScheduler.isCurrent(token)) return
|
|
279
|
-
const batch = sampledTargets.map((target) => ({
|
|
280
|
-
...target.request,
|
|
281
|
-
revision: revisions.get(target.request.datasetId) ?? 1,
|
|
282
|
-
}))
|
|
281
|
+
const client = session.getClient()
|
|
283
282
|
let response: SampleViewportResponse
|
|
284
283
|
try {
|
|
284
|
+
await registerTargets(client, sampledTargets, token)
|
|
285
|
+
if (!samplingScheduler.isCurrent(token)) return
|
|
286
|
+
const batch = sampledTargets.map((target) => ({
|
|
287
|
+
...target.request,
|
|
288
|
+
revision: revisions.get(target.request.datasetId) ?? 1,
|
|
289
|
+
}))
|
|
285
290
|
const result = await client.send({
|
|
286
291
|
type: 'sample-viewport',
|
|
287
|
-
requestId: ++requestId,
|
|
292
|
+
requestId: ++session.requestId,
|
|
288
293
|
series: batch,
|
|
289
294
|
})
|
|
290
295
|
if (result.type !== 'sample-viewport-response') return
|
|
@@ -295,7 +300,10 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
295
300
|
errorPayload(
|
|
296
301
|
error instanceof Error ? error.message : String(error),
|
|
297
302
|
context.renderingOptions.value.sampling.mode,
|
|
298
|
-
'
|
|
303
|
+
context.renderingOptions.value.sampling.mode === 'wasm' &&
|
|
304
|
+
context.renderingOptions.value.sampling.wasmFailureFallback === 'error'
|
|
305
|
+
? 'none'
|
|
306
|
+
: 'javascript',
|
|
299
307
|
sampledTargets.map((target) => target.series.id),
|
|
300
308
|
),
|
|
301
309
|
)
|
|
@@ -322,12 +330,18 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
322
330
|
)
|
|
323
331
|
}
|
|
324
332
|
})
|
|
325
|
-
if (
|
|
333
|
+
if (
|
|
334
|
+
response.workerError &&
|
|
335
|
+
response.results.every((result) => result.status !== 'wasm-unavailable')
|
|
336
|
+
) {
|
|
326
337
|
emitErrorOnce(
|
|
327
338
|
errorPayload(
|
|
328
339
|
response.workerError,
|
|
329
340
|
context.renderingOptions.value.sampling.mode,
|
|
330
|
-
'
|
|
341
|
+
context.renderingOptions.value.sampling.mode === 'wasm' &&
|
|
342
|
+
context.renderingOptions.value.sampling.wasmFailureFallback === 'error'
|
|
343
|
+
? 'none'
|
|
344
|
+
: 'javascript',
|
|
331
345
|
sampledTargets.map((target) => target.series.id),
|
|
332
346
|
),
|
|
333
347
|
)
|
|
@@ -340,7 +354,10 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
340
354
|
errorPayload(
|
|
341
355
|
client.workerFailureReason,
|
|
342
356
|
context.renderingOptions.value.sampling.mode,
|
|
343
|
-
'
|
|
357
|
+
context.renderingOptions.value.sampling.mode === 'wasm' &&
|
|
358
|
+
context.renderingOptions.value.sampling.wasmFailureFallback === 'error'
|
|
359
|
+
? 'none'
|
|
360
|
+
: 'javascript',
|
|
344
361
|
sampledTargets.map((target) => target.series.id),
|
|
345
362
|
),
|
|
346
363
|
)
|
|
@@ -349,21 +366,33 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
349
366
|
emitDiagnostics(diagnostics)
|
|
350
367
|
}
|
|
351
368
|
|
|
369
|
+
const scheduleSampling = (hysteresis: boolean) => {
|
|
370
|
+
settledSignature.value = undefined
|
|
371
|
+
const signature = targetSignature.value
|
|
372
|
+
samplingScheduler.schedule(async (token) => {
|
|
373
|
+
try {
|
|
374
|
+
await runSampling(token, hysteresis)
|
|
375
|
+
} finally {
|
|
376
|
+
if (samplingScheduler.isCurrent(token)) settledSignature.value = signature
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
352
381
|
watch(
|
|
353
382
|
targetSignature,
|
|
354
383
|
() => {
|
|
355
|
-
if (autoModeSettleTimer) clearTimeout(autoModeSettleTimer)
|
|
356
|
-
const useAutoHysteresis = hasInitialSamplingRun
|
|
357
|
-
hasInitialSamplingRun = true
|
|
358
|
-
|
|
384
|
+
if (session.autoModeSettleTimer) clearTimeout(session.autoModeSettleTimer)
|
|
385
|
+
const useAutoHysteresis = session.hasInitialSamplingRun
|
|
386
|
+
session.hasInitialSamplingRun = true
|
|
387
|
+
scheduleSampling(useAutoHysteresis)
|
|
359
388
|
if (
|
|
360
389
|
useAutoHysteresis &&
|
|
361
390
|
context.renderingOptions.value.sampling.mode === 'auto' &&
|
|
362
391
|
context.renderingOptions.value.sampling.autoHysteresis > 0
|
|
363
392
|
) {
|
|
364
|
-
autoModeSettleTimer = setTimeout(() => {
|
|
365
|
-
autoModeSettleTimer = undefined
|
|
366
|
-
|
|
393
|
+
session.autoModeSettleTimer = setTimeout(() => {
|
|
394
|
+
session.autoModeSettleTimer = undefined
|
|
395
|
+
scheduleSampling(false)
|
|
367
396
|
}, AUTO_MODE_SETTLE_DELAY_MS)
|
|
368
397
|
}
|
|
369
398
|
},
|
|
@@ -372,24 +401,16 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
372
401
|
watch(
|
|
373
402
|
context.preparedSeries,
|
|
374
403
|
() => {
|
|
375
|
-
|
|
404
|
+
session.invalidate()
|
|
376
405
|
dataEpoch.value += 1
|
|
377
406
|
context.linePointOverrides.value = {}
|
|
378
|
-
revisions.clear()
|
|
379
|
-
backendBySeries.clear()
|
|
380
|
-
selectedModeBySeries.clear()
|
|
381
|
-
emittedErrors.clear()
|
|
382
|
-
client?.dispose()
|
|
383
|
-
client = undefined
|
|
384
407
|
},
|
|
385
408
|
{ flush: 'sync' },
|
|
386
409
|
)
|
|
387
|
-
onScopeDispose(() =>
|
|
388
|
-
if (diagnosticsTimer) clearTimeout(diagnosticsTimer)
|
|
389
|
-
if (autoModeSettleTimer) clearTimeout(autoModeSettleTimer)
|
|
390
|
-
samplingScheduler.dispose()
|
|
391
|
-
client?.dispose()
|
|
392
|
-
})
|
|
410
|
+
onScopeDispose(() => session.dispose())
|
|
393
411
|
|
|
394
|
-
return {
|
|
412
|
+
return {
|
|
413
|
+
linePointOverrides: context.linePointOverrides,
|
|
414
|
+
ready: () => settledSignature.value === targetSignature.value && !session.autoModeSettleTimer,
|
|
415
|
+
}
|
|
395
416
|
}
|