waveform-analysis 0.1.77 → 0.1.79
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 +14 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +1 -0
- package/dist/types/components/core/useWaveformLayout.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/WaveformChartView.vue +3 -2
- package/src/components/animation/trackPresentation.ts +1 -1
- package/src/components/controls/useChartCommands.ts +9 -7
- package/src/components/core/useWaveformChartController.ts +1 -2
- package/src/components/core/useWaveformData.ts +13 -1
- package/src/components/core/useWaveformLayout.ts +4 -0
- package/src/components/interaction/zoomConstraints.ts +21 -18
- package/src/demo/remoteWaveformLoader.ts +10 -5
|
@@ -274,6 +274,7 @@ export declare function useWaveformChartController(inputProps: ResolvedWaveformC
|
|
|
274
274
|
innerWidth: number;
|
|
275
275
|
hasVisibleWaveformData: boolean;
|
|
276
276
|
hasWaveformData: boolean;
|
|
277
|
+
hasWaveformSamples: boolean;
|
|
277
278
|
hasChartArea: boolean;
|
|
278
279
|
resolvedXLabel: string;
|
|
279
280
|
activeInteractionMode: import("..").WaveformInteractionMode | undefined;
|
|
@@ -35,6 +35,7 @@ export declare function useWaveformLayout(context: LayoutContext): {
|
|
|
35
35
|
innerWidth: ComputedRef<number>;
|
|
36
36
|
hasVisibleWaveformData: ComputedRef<boolean>;
|
|
37
37
|
hasWaveformData: ComputedRef<boolean>;
|
|
38
|
+
hasWaveformSamples: ComputedRef<boolean>;
|
|
38
39
|
hasChartArea: ComputedRef<boolean>;
|
|
39
40
|
resolvedXLabel: ComputedRef<string>;
|
|
40
41
|
activeInteractionMode: ComputedRef<import("..").WaveformInteractionMode | undefined>;
|
package/package.json
CHANGED
|
@@ -46,6 +46,7 @@ const {
|
|
|
46
46
|
chartWidth,
|
|
47
47
|
drawingHeight,
|
|
48
48
|
hasWaveformData,
|
|
49
|
+
hasWaveformSamples,
|
|
49
50
|
trackLayouts,
|
|
50
51
|
presentationTracks,
|
|
51
52
|
presentationAnnotations,
|
|
@@ -186,7 +187,7 @@ function handleChartPointerLeave() {
|
|
|
186
187
|
:width="chartWidth"
|
|
187
188
|
:height="drawingHeight"
|
|
188
189
|
role="img"
|
|
189
|
-
:aria-label="
|
|
190
|
+
:aria-label="hasWaveformSamples ? '波形折线图' : '暂无波形数据'"
|
|
190
191
|
>
|
|
191
192
|
<defs>
|
|
192
193
|
<clipPath
|
|
@@ -343,7 +344,7 @@ function handleChartPointerLeave() {
|
|
|
343
344
|
</text>
|
|
344
345
|
|
|
345
346
|
<text
|
|
346
|
-
v-if="hasChartArea && !
|
|
347
|
+
v-if="hasChartArea && !hasWaveformSamples"
|
|
347
348
|
class="waveform-chart__empty"
|
|
348
349
|
:x="chartWidth / 2"
|
|
349
350
|
:y="drawingHeight / 2"
|
|
@@ -92,7 +92,7 @@ export function transitionTrack(
|
|
|
92
92
|
const series = new Map<string, SeriesGeometry>()
|
|
93
93
|
for (const [id, next] of to.series) {
|
|
94
94
|
const previous = from.series.get(id)
|
|
95
|
-
if (!previous) {
|
|
95
|
+
if (!previous || (next.path.series.source?.length ?? next.path.series.points.length) === 0) {
|
|
96
96
|
series.set(id, next)
|
|
97
97
|
continue
|
|
98
98
|
}
|
|
@@ -122,13 +122,15 @@ export function useChartCommands(c: Context) {
|
|
|
122
122
|
? tracks.some((t) => c.independentYDomains.value[t.index])
|
|
123
123
|
: Object.keys(c.sharedYDomains.value).length > 0)
|
|
124
124
|
const boundaryChanged = tracks.length && !same(bounds, boundary(tracks))
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? '
|
|
129
|
-
:
|
|
130
|
-
? '
|
|
131
|
-
:
|
|
125
|
+
const hasSeries = tracks.some((t) => t.seriesList.length > 0)
|
|
126
|
+
const status: WaveformTargetResult['status'] =
|
|
127
|
+
!hasSeries || (action === 'fit' && !nonempty)
|
|
128
|
+
? 'empty-data'
|
|
129
|
+
: c.props.presentationMode || !c.props.zoomable
|
|
130
|
+
? 'disabled'
|
|
131
|
+
: before && same(before.xDomain, xDomain) && !hasY && !boundaryChanged
|
|
132
|
+
? 'unchanged'
|
|
133
|
+
: 'applied'
|
|
132
134
|
return { id, tracks, before, bounds, xDomain, status, resetsY }
|
|
133
135
|
})
|
|
134
136
|
return { entries }
|
|
@@ -32,7 +32,6 @@ 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'
|
|
36
35
|
import { useWaveformLayout } from './useWaveformLayout'
|
|
37
36
|
import { useWaveformPresentation } from './useWaveformPresentation'
|
|
38
37
|
import { usePlotLabelLayout } from './usePlotLabelLayout'
|
|
@@ -103,7 +102,7 @@ export function useWaveformChartController(
|
|
|
103
102
|
handleBeforeDataReferenceChange()
|
|
104
103
|
},
|
|
105
104
|
() => handleDataReferenceChange(),
|
|
106
|
-
() =>
|
|
105
|
+
() => true,
|
|
107
106
|
)
|
|
108
107
|
|
|
109
108
|
const presentation = useWaveformPresentation({
|
|
@@ -71,7 +71,19 @@ export function usePreparedWaveformSeries(
|
|
|
71
71
|
)
|
|
72
72
|
watch([data, retainEmpty], ([nextData, preserveEmpty]) => {
|
|
73
73
|
onBeforeDataChange()
|
|
74
|
-
preparedSeries.value
|
|
74
|
+
const previousById = new Map(preparedSeries.value.map((series) => [series.id, series]))
|
|
75
|
+
preparedSeries.value = prepareWaveformSeries(nextData, preserveEmpty).map((series) => {
|
|
76
|
+
const previous = previousById.get(series.id)
|
|
77
|
+
// Preserve axes, never geometry, across empty windows of the same channel/track.
|
|
78
|
+
if (series.source.length || !previous || series.trackId !== previous.trackId) return series
|
|
79
|
+
return {
|
|
80
|
+
...series,
|
|
81
|
+
xDomain: previous.xDomain,
|
|
82
|
+
yDomain: previous.yDomain,
|
|
83
|
+
rawYMaximum: previous.rawYMaximum,
|
|
84
|
+
rawYMinimum: previous.rawYMinimum,
|
|
85
|
+
}
|
|
86
|
+
})
|
|
75
87
|
onDataChange()
|
|
76
88
|
})
|
|
77
89
|
return preparedSeries
|
|
@@ -149,6 +149,9 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
149
149
|
yAxisTickCount,
|
|
150
150
|
})
|
|
151
151
|
const hasWaveformData = computed(() => chartSeries.value.length > 0)
|
|
152
|
+
const hasWaveformSamples = computed(() =>
|
|
153
|
+
chartSeries.value.some((series) => (series.source?.length ?? series.points.length) > 0),
|
|
154
|
+
)
|
|
152
155
|
const hasChartArea = computed(() => innerWidth.value > 0 && innerHeight.value > 0)
|
|
153
156
|
const resolvedXLabel = computed(() => props.xLabel ?? `时间(${props.timeUnit})`)
|
|
154
157
|
const activeInteractionMode = computed(() => props.interactionMode)
|
|
@@ -349,6 +352,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
349
352
|
innerWidth,
|
|
350
353
|
hasVisibleWaveformData,
|
|
351
354
|
hasWaveformData,
|
|
355
|
+
hasWaveformSamples,
|
|
352
356
|
hasChartArea,
|
|
353
357
|
resolvedXLabel,
|
|
354
358
|
activeInteractionMode,
|
|
@@ -53,19 +53,22 @@ function minimumPointSpan(
|
|
|
53
53
|
required: number,
|
|
54
54
|
): number {
|
|
55
55
|
if (required <= 1) return 0
|
|
56
|
-
let minimumSpan =
|
|
56
|
+
let minimumSpan = Number.POSITIVE_INFINITY
|
|
57
|
+
let hasData = false
|
|
57
58
|
for (const group of groups) {
|
|
58
59
|
const values = uniqueXValues(group, boundary)
|
|
59
|
-
if (
|
|
60
|
+
if (!group.some((series) => (series.source?.length ?? series.points.length) > 0)) continue
|
|
61
|
+
hasData = true
|
|
62
|
+
if (values.length < required) continue
|
|
60
63
|
let groupSpan = Number.POSITIVE_INFINITY
|
|
61
64
|
for (let index = 0; index + required <= values.length; index += 1) {
|
|
62
65
|
groupSpan = Math.min(groupSpan, values[index + required - 1] - values[index])
|
|
63
66
|
}
|
|
64
67
|
const endpointTolerance =
|
|
65
68
|
Number.EPSILON * Math.max(1, Math.abs(boundary[0]), Math.abs(boundary[1])) * 16
|
|
66
|
-
minimumSpan = Math.
|
|
69
|
+
minimumSpan = Math.min(minimumSpan, groupSpan + endpointTolerance)
|
|
67
70
|
}
|
|
68
|
-
return minimumSpan
|
|
71
|
+
return Number.isFinite(minimumSpan) ? minimumSpan : hasData ? boundary[1] - boundary[0] : 0
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export function resolveMinimumZoomSpan(
|
|
@@ -101,24 +104,25 @@ function expandToMinimumPoints(
|
|
|
101
104
|
if (required <= 0) return domain
|
|
102
105
|
const endpointTolerance =
|
|
103
106
|
Number.EPSILON * Math.max(1, Math.abs(boundary[0]), Math.abs(boundary[1])) * 16
|
|
104
|
-
let
|
|
107
|
+
let best: [number, number] | undefined
|
|
108
|
+
let bestSpan = Number.POSITIVE_INFINITY
|
|
109
|
+
let bestCenterDistance = Number.POSITIVE_INFINITY
|
|
110
|
+
let hasData = false
|
|
111
|
+
const requestedCenter = (domain[0] + domain[1]) / 2
|
|
105
112
|
for (const group of groups) {
|
|
106
113
|
const values = uniqueXValues(group, boundary)
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
).length
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
let bestSpan = Number.POSITIVE_INFINITY
|
|
114
|
-
let bestCenterDistance = Number.POSITIVE_INFINITY
|
|
115
|
-
const requestedCenter = (expanded[0] + expanded[1]) / 2
|
|
114
|
+
if (!group.some((series) => (series.source?.length ?? series.points.length) > 0)) continue
|
|
115
|
+
hasData = true
|
|
116
|
+
if (values.length < required) continue
|
|
117
|
+
const visibleCount = values.filter((value) => value >= domain[0] && value <= domain[1]).length
|
|
118
|
+
// Shared tracks need one supporting frame, rather than every frame.
|
|
119
|
+
if (visibleCount >= required) return domain
|
|
116
120
|
for (let index = 0; index + required <= values.length; index += 1) {
|
|
117
121
|
const candidate: [number, number] = [
|
|
118
|
-
Math.max(boundary[0], Math.min(
|
|
122
|
+
Math.max(boundary[0], Math.min(domain[0], values[index] - endpointTolerance)),
|
|
119
123
|
Math.min(
|
|
120
124
|
boundary[1],
|
|
121
|
-
Math.max(
|
|
125
|
+
Math.max(domain[1], values[index + required - 1] + endpointTolerance),
|
|
122
126
|
),
|
|
123
127
|
]
|
|
124
128
|
const span = candidate[1] - candidate[0]
|
|
@@ -129,9 +133,8 @@ function expandToMinimumPoints(
|
|
|
129
133
|
bestCenterDistance = centerDistance
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
|
-
expanded = best ?? [...boundary]
|
|
133
136
|
}
|
|
134
|
-
return
|
|
137
|
+
return best ?? (hasData ? [...boundary] : domain)
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
export function constrainZoomDomain(
|
|
@@ -52,14 +52,19 @@ export function mergeWaveformWindow(
|
|
|
52
52
|
response: WaveformData,
|
|
53
53
|
seriesIds?: string[],
|
|
54
54
|
): WaveformData {
|
|
55
|
-
if (
|
|
56
|
-
const selected = new Set(seriesIds)
|
|
55
|
+
if (current.kind !== 'series' || response.kind !== 'series') return response
|
|
56
|
+
const selected = seriesIds ? new Set(seriesIds) : undefined
|
|
57
57
|
const byId = new Map(response.series.map((series) => [series.id, series]))
|
|
58
|
+
const knownIds = new Set(current.series.map((series) => series.id))
|
|
58
59
|
return {
|
|
59
60
|
kind: 'series',
|
|
60
|
-
series:
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
series: [
|
|
62
|
+
...current.series.map((series) => {
|
|
63
|
+
if (selected && (!series.id || !selected.has(series.id))) return series
|
|
64
|
+
return byId.get(series.id) ?? { ...series, data: { kind: 'points' as const, points: [] } }
|
|
65
|
+
}),
|
|
66
|
+
...response.series.filter((series) => !knownIds.has(series.id)),
|
|
67
|
+
],
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
70
|
|