waveform-analysis 0.1.75 → 0.1.77
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 +183 -42
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/dist/types/components/animation/context.d.ts +1 -0
- package/dist/types/components/animation/paginationPresentation.d.ts +7 -7
- package/dist/types/components/animation/useWaveformAnimations.d.ts +1 -18
- 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/useWaveformChartController.d.ts +2 -19
- 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/waveformChartTypes.d.ts +4 -2
- 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 +0 -1
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +0 -1
- 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 +1 -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/package.json +1 -1
- package/src/App.vue +34 -100
- package/src/components/WaveformChartView.vue +0 -4
- package/src/components/animation/config.ts +1 -1
- package/src/components/animation/context.ts +9 -1
- package/src/components/animation/paginationPresentation.ts +55 -64
- package/src/components/animation/useWaveformAnimations.ts +36 -13
- package/src/components/controls/useChartCommands.ts +12 -13
- package/src/components/core/amplitudeLimits.ts +44 -0
- package/src/components/core/layout.ts +22 -34
- package/src/components/core/trackLayoutBuilder.ts +12 -5
- package/src/components/core/useWaveformChartController.ts +3 -4
- 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/useWaveformYAxisLayout.ts +2 -0
- package/src/components/core/waveformChartTypes.ts +4 -1
- 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 +1 -2
- package/src/components/rendering/WaveformTrack.vue +1 -6
- 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 +2 -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 +14 -5
- package/dist/types/data/sx2Channel.d.ts +0 -10
- package/src/data/sx2Channel.ts +0 -315
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InjectionKey } from 'vue';
|
|
2
|
+
import type { WaveformData } from '../types';
|
|
3
|
+
export interface RemoteWindowRequest {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
seriesIds?: string[];
|
|
7
|
+
}
|
|
8
|
+
export type RemoteWindowLoader = (request: RemoteWindowRequest, signal: AbortSignal) => Promise<WaveformData>;
|
|
9
|
+
export declare const remoteWindowLoaderKey: InjectionKey<RemoteWindowLoader>;
|
|
10
|
+
export interface MockWindowOptions {
|
|
11
|
+
delayMs?: number | ((request: RemoteWindowRequest) => number);
|
|
12
|
+
empty?: boolean | ((request: RemoteWindowRequest) => boolean);
|
|
13
|
+
fail?: boolean | ((request: RemoteWindowRequest) => boolean);
|
|
14
|
+
}
|
|
15
|
+
/** Preserve series identities even when a window has no samples. */
|
|
16
|
+
export declare function selectWaveformWindow(data: WaveformData, request: RemoteWindowRequest, empty?: boolean): WaveformData;
|
|
17
|
+
export declare function mergeWaveformWindow(current: WaveformData, response: WaveformData, seriesIds?: string[]): WaveformData;
|
|
18
|
+
/** Metadata is derived from the complete record, never from a loaded window. */
|
|
19
|
+
export declare function waveformRecordMetadata(data: WaveformData): {
|
|
20
|
+
domain: [number, number];
|
|
21
|
+
minZoomSpan: number | undefined;
|
|
22
|
+
};
|
|
23
|
+
export declare function createMockWindowLoader(source: () => WaveformData, options?: MockWindowOptions): RemoteWindowLoader;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformPlotMargin, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
|
|
1
|
+
import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformPlotMargin, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload, WaveformZoomIntentPayload } from '../components';
|
|
2
|
+
import type { WaveformZoomResetPayload, WaveformPanEndPayload } from '../types';
|
|
2
3
|
import type { DemoXAxisLabelFormat, DemoXAxisLabelTimeZone } from './useDemoXAxisLabelControls';
|
|
3
4
|
interface SelectOption<T> {
|
|
4
5
|
label: string;
|
|
@@ -78,6 +79,11 @@ export interface DemoChartModel {
|
|
|
78
79
|
toolbarVisible: boolean;
|
|
79
80
|
data: WaveformData;
|
|
80
81
|
initialXDomain?: [number, number];
|
|
82
|
+
recordDomain: [number, number];
|
|
83
|
+
minZoomSpan?: number;
|
|
84
|
+
loading: boolean;
|
|
85
|
+
errors: string[];
|
|
86
|
+
retry: () => Promise<void[]>;
|
|
81
87
|
displayMode: WaveformDisplayMode;
|
|
82
88
|
overlayMode: WaveformOverlayMode;
|
|
83
89
|
rowCount: number;
|
|
@@ -101,6 +107,9 @@ export interface DemoChartModel {
|
|
|
101
107
|
interactionMode: WaveformInteractionMode;
|
|
102
108
|
hiddenSeriesIds: string[];
|
|
103
109
|
handleZoomEnd: (payload: WaveformZoomEndPayload) => void | Promise<void>;
|
|
104
|
-
|
|
110
|
+
handleZoomIntent: (payload: WaveformZoomIntentPayload) => void;
|
|
111
|
+
handleZoomChange: (domain: [number, number]) => void;
|
|
112
|
+
handlePanEnd: (payload: WaveformPanEndPayload) => void | Promise<void>;
|
|
113
|
+
resetWaveformViewport: (payload?: WaveformZoomResetPayload) => void | Promise<void>;
|
|
105
114
|
}
|
|
106
115
|
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import type { WaveformData, WaveformPanEndPayload, WaveformZoomEndPayload, WaveformZoomIntentPayload, WaveformZoomResetPayload } from '../types';
|
|
3
|
+
import { type RemoteWindowLoader } from './remoteWaveformLoader';
|
|
4
|
+
interface RemoteWindowOptions {
|
|
5
|
+
loadWindow?: RemoteWindowLoader;
|
|
6
|
+
resetViewport?: (trackIndex?: number) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function useRemoteWaveformWindow(source: Ref<WaveformData>, options?: RemoteWindowOptions): {
|
|
9
|
+
chartData: import("vue").ShallowRef<WaveformData, WaveformData>;
|
|
10
|
+
recordDomain: import("vue").ComputedRef<[number, number]>;
|
|
11
|
+
initialXDomain: import("vue").ComputedRef<[number, number]>;
|
|
12
|
+
minZoomSpan: import("vue").ComputedRef<number | undefined>;
|
|
13
|
+
loading: import("vue").ComputedRef<boolean>;
|
|
14
|
+
errors: import("vue").ComputedRef<string[]>;
|
|
15
|
+
handleZoomIntent: (payload: WaveformZoomIntentPayload) => void;
|
|
16
|
+
handleZoomChange: (domain: [number, number]) => void;
|
|
17
|
+
handleZoomEnd: (payload: WaveformZoomEndPayload) => Promise<void>;
|
|
18
|
+
handlePanEnd: (payload: WaveformPanEndPayload) => Promise<void>;
|
|
19
|
+
resetWaveformViewport: (payload?: WaveformZoomResetPayload) => Promise<void>;
|
|
20
|
+
retry: () => Promise<void[]>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { Animation } from './animation/core/Animation';
|
|
|
13
13
|
export { Animator, globalAnimator } from './animation/core/Animator';
|
|
14
14
|
export type { AnimationValue, AnimationDirection, AnimationState, AnimationFrame, AnimationTrack, AnimationConfig, AnimationHandle, AnimationScheduler, EasingFunction, EasingFamily, EasingType, } from './animation/core/types';
|
|
15
15
|
export type { LineDrawingDirection, AnnotationAnimationType, LineDrawingOptions, StaggeredLineDrawingOptions, DataUpdateOptions, AnnotationAnimationOptions, WaveformEnterAnimationOptions, WaveformUpdateAnimationOptions, WaveformAnnotationAnimationOptions, WaveformAnimationProps, ClipAnimationState, AnimationBinding, NumericGeometry, GeometryTransitionInput, GeometryTransitionFrame, AnnotationAnimationState, AnimationScope, AnimationObjectKey, WaveformAnimationControllerOptions, } from './animation/types';
|
|
16
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformYAxisLabelTextStyle, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
|
|
16
|
+
export type { WaveformAmplitudeLimit, WaveformAmplitudeLimits, WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformYAxisLabelTextStyle, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
|
|
17
17
|
export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './components/core/grid';
|
|
18
18
|
export { normalizeWaveformData, normalizeWaveformSeries } from './core';
|
|
19
19
|
export { paddedDomain, buildMinorTicks, displayTime, formatEndpointTime, formatAxisTime, formatTooltipTime, resolveTrackGeometry, clamp, type TimeUnit, type TrackGeometry, } from './utils';
|
|
@@ -68,7 +68,7 @@ export interface WaveformZoomIntentPayload extends WaveformCommandMetadata {
|
|
|
68
68
|
trackIndex?: number;
|
|
69
69
|
seriesIds?: string[];
|
|
70
70
|
}
|
|
71
|
-
/** Identifies the viewport reset by a
|
|
71
|
+
/** Identifies the viewport reset by a toolbar or API command. */
|
|
72
72
|
export interface WaveformZoomResetPayload extends WaveformCommandMetadata {
|
|
73
73
|
trackIndex?: number;
|
|
74
74
|
seriesIds?: string[];
|
|
@@ -279,3 +279,10 @@ export interface WaveformZeroLineOptions {
|
|
|
279
279
|
/** Hide within this fraction of a Y-axis tick interval from either boundary. Defaults to 0.02. */
|
|
280
280
|
boundaryThreshold?: number;
|
|
281
281
|
}
|
|
282
|
+
/** Strict amplitude display bounds, in the series' original Y units. */
|
|
283
|
+
export interface WaveformAmplitudeLimit {
|
|
284
|
+
lower?: number;
|
|
285
|
+
upper?: number;
|
|
286
|
+
}
|
|
287
|
+
/** Keyed by stable normalized series IDs (not track IDs). */
|
|
288
|
+
export type WaveformAmplitudeLimits = Record<string, WaveformAmplitudeLimit>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 类型定义统一导出
|
|
3
3
|
*/
|
|
4
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformYAxisLabelTextStyle, WaveformZeroLineOptions, } from './chart';
|
|
4
|
+
export type { WaveformAmplitudeLimit, WaveformAmplitudeLimits, WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformYAxisLabelTextStyle, WaveformZeroLineOptions, } from './chart';
|
|
5
5
|
export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
|
|
6
6
|
export type { WaveformToolbarItem, WaveformToolbarOptions, WaveformControlTarget, WaveformControlAction, WaveformCommandMetadata, WaveformTargetStatus, WaveformViewportSnapshot, WaveformTargetResult, WaveformControlResult, WaveformControlState, WaveformImageExportOptions, WaveformImageExportErrorCode, WaveformImageExportError, WaveformChartHandle, } from './controls';
|
|
@@ -46,18 +46,18 @@ export declare function formatXAxisLabel(rawValue: number, domain: [number, numb
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function endpointFractionDigits(domain: [number, number], timeUnit: TimeUnit): number;
|
|
48
48
|
/**
|
|
49
|
-
* 将 X
|
|
49
|
+
* 将 X 轴端点时间格式化为当前显示单位下的自适应精度文本
|
|
50
50
|
* @param value 时间值(秒)
|
|
51
|
-
* @param _domain
|
|
51
|
+
* @param _domain 用于计算显示精度的可视域
|
|
52
52
|
* @param timeUnit 时间单位
|
|
53
53
|
* @returns 格式化的时间字符串
|
|
54
54
|
*/
|
|
55
55
|
export declare function formatEndpointTime(value: number, _domain: [number, number], timeUnit: TimeUnit): string;
|
|
56
56
|
/**
|
|
57
|
-
* 将 X
|
|
57
|
+
* 将 X 轴时间刻度格式化为当前显示单位下的自适应精度文本
|
|
58
58
|
* @param value 时间值(秒)
|
|
59
59
|
* @param timeUnit 时间单位
|
|
60
|
-
* @param _domain
|
|
60
|
+
* @param _domain 用于计算显示精度的可视域
|
|
61
61
|
* @returns 格式化的时间字符串
|
|
62
62
|
*/
|
|
63
63
|
export declare function formatAxisTime(value: number, timeUnit: TimeUnit, _domain?: [number, number]): string;
|
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
2
|
+
import { computed, inject, onBeforeUnmount, onMounted, reactive, ref, shallowRef } from 'vue'
|
|
3
3
|
import 'vue3-colorpicker/style.css'
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
type WaveformOverlayMode,
|
|
17
17
|
type WaveformPlotMargin,
|
|
18
18
|
type WaveformTitleOptions,
|
|
19
|
-
type WaveformZoomEndPayload,
|
|
20
19
|
type WaveformZeroLineOptions,
|
|
21
20
|
} from './components'
|
|
22
21
|
import { normalizeWaveformSeries } from './core'
|
|
@@ -25,6 +24,8 @@ import DemoChartHost from './demo/DemoChartHost.vue'
|
|
|
25
24
|
import DemoControlPanel from './demo/DemoControlPanel.vue'
|
|
26
25
|
import type { DemoChartModel, DemoControlPanelModel } from './demo/types'
|
|
27
26
|
import { useDemoXAxisLabelControls } from './demo/useDemoXAxisLabelControls'
|
|
27
|
+
import { remoteWindowLoaderKey } from './demo/remoteWaveformLoader'
|
|
28
|
+
import { useRemoteWaveformWindow } from './demo/useRemoteWaveformWindow'
|
|
28
29
|
|
|
29
30
|
const demoPreset = new URLSearchParams(window.location.search).get('demo')
|
|
30
31
|
const compactNineDemo = demoPreset === 'compact-nine'
|
|
@@ -164,20 +165,27 @@ const plotMargin = computed<WaveformPlotMargin>(() => ({
|
|
|
164
165
|
bottom: plotMarginBottom.value,
|
|
165
166
|
}))
|
|
166
167
|
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
168
|
+
const demoChartHostRef = ref<{ resetViewport: (trackIndex?: number) => void }>()
|
|
169
|
+
const sourceData = shallowRef<WaveformData>(fullChartData)
|
|
170
|
+
const remoteWindow = useRemoteWaveformWindow(sourceData, {
|
|
171
|
+
loadWindow: inject(remoteWindowLoaderKey, undefined),
|
|
172
|
+
resetViewport: (trackIndex) => demoChartHostRef.value?.resetViewport(trackIndex),
|
|
173
|
+
})
|
|
174
|
+
const {
|
|
175
|
+
chartData,
|
|
176
|
+
initialXDomain,
|
|
177
|
+
recordDomain,
|
|
178
|
+
minZoomSpan,
|
|
179
|
+
loading,
|
|
180
|
+
errors,
|
|
181
|
+
handleZoomIntent,
|
|
182
|
+
handleZoomChange,
|
|
183
|
+
handleZoomEnd,
|
|
184
|
+
handlePanEnd,
|
|
185
|
+
resetWaveformViewport,
|
|
186
|
+
retry,
|
|
187
|
+
} = remoteWindow
|
|
188
|
+
onMounted(() => void resetWaveformViewport())
|
|
181
189
|
const lineStyleOverrides = ref<Record<string, WaveformLineStyle>>({})
|
|
182
190
|
const selectedSeriesId = ref(
|
|
183
191
|
fullChartData.kind === 'series' ? (fullChartData.series[0]?.id ?? '') : '',
|
|
@@ -188,7 +196,7 @@ const lineStyleOptions: Array<{ label: string; value: WaveformLineStyle }> = [
|
|
|
188
196
|
{ label: '点划线', value: 'dash-dot' },
|
|
189
197
|
]
|
|
190
198
|
const seriesStyleOptions = computed(() =>
|
|
191
|
-
normalizeWaveformSeries(
|
|
199
|
+
normalizeWaveformSeries(sourceData.value).map((series) => ({
|
|
192
200
|
label: series.name || series.id,
|
|
193
201
|
value: series.id,
|
|
194
202
|
})),
|
|
@@ -214,7 +222,7 @@ const displayChartData = computed<WaveformData>(() => {
|
|
|
214
222
|
})
|
|
215
223
|
const gridTrackLines = computed<WaveformGridTrackLines>(() =>
|
|
216
224
|
Object.fromEntries(
|
|
217
|
-
normalizeWaveformSeries(
|
|
225
|
+
normalizeWaveformSeries(sourceData.value).map((series) => [
|
|
218
226
|
series.trackId ?? series.id,
|
|
219
227
|
{
|
|
220
228
|
horizontal: horizontalGridVisible.value,
|
|
@@ -225,88 +233,6 @@ const gridTrackLines = computed<WaveformGridTrackLines>(() =>
|
|
|
225
233
|
]),
|
|
226
234
|
),
|
|
227
235
|
)
|
|
228
|
-
const demoChartHostRef = ref<{ resetViewport: (trackIndex?: number) => void }>()
|
|
229
|
-
|
|
230
|
-
let zoomRequestSequence = 0
|
|
231
|
-
|
|
232
|
-
function filterWaveformData(data: WaveformData, start: number, end: number): WaveformData {
|
|
233
|
-
const lower = Math.min(start, end)
|
|
234
|
-
const upper = Math.max(start, end)
|
|
235
|
-
if (data.kind !== 'series') {
|
|
236
|
-
if (data.kind !== 'points') return data
|
|
237
|
-
return {
|
|
238
|
-
kind: 'points',
|
|
239
|
-
points: data.points.filter((point) => point.x >= lower && point.x <= upper),
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return {
|
|
243
|
-
kind: 'series',
|
|
244
|
-
series: data.series.map((series) => ({
|
|
245
|
-
...series,
|
|
246
|
-
data:
|
|
247
|
-
series.data.kind === 'points'
|
|
248
|
-
? {
|
|
249
|
-
kind: 'points',
|
|
250
|
-
points: series.data.points.filter((point) => point.x >= lower && point.x <= upper),
|
|
251
|
-
}
|
|
252
|
-
: series.data,
|
|
253
|
-
})),
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function mergeIndependentWindow(
|
|
258
|
-
currentData: WaveformData,
|
|
259
|
-
responseData: WaveformData,
|
|
260
|
-
seriesIds: string[],
|
|
261
|
-
): WaveformData {
|
|
262
|
-
if (currentData.kind !== 'series' || responseData.kind !== 'series') return responseData
|
|
263
|
-
const responseById = new Map(responseData.series.map((series) => [series.id, series]))
|
|
264
|
-
const changedIds = new Set(seriesIds)
|
|
265
|
-
return {
|
|
266
|
-
kind: 'series',
|
|
267
|
-
series: currentData.series.map((series) =>
|
|
268
|
-
series.id && changedIds.has(series.id) ? (responseById.get(series.id) ?? series) : series,
|
|
269
|
-
),
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let pendingCommandId: string | undefined
|
|
274
|
-
let pendingPayloads: WaveformZoomEndPayload[] = []
|
|
275
|
-
async function handleZoomEnd(payload: WaveformZoomEndPayload) {
|
|
276
|
-
if (payload.action === 'fit') return
|
|
277
|
-
if (payload.commandId && payload.commandId === pendingCommandId) {
|
|
278
|
-
pendingPayloads.push(payload)
|
|
279
|
-
return
|
|
280
|
-
}
|
|
281
|
-
pendingCommandId = payload.commandId
|
|
282
|
-
const batch = [payload]
|
|
283
|
-
pendingPayloads = batch
|
|
284
|
-
// Demo-only sequence number cancellation. Production code should use AbortController
|
|
285
|
-
// to cancel in-flight requests when a newer zoom gesture arrives.
|
|
286
|
-
const requestSequence = ++zoomRequestSequence
|
|
287
|
-
await new Promise((resolve) => window.setTimeout(resolve, 80))
|
|
288
|
-
if (requestSequence !== zoomRequestSequence) return
|
|
289
|
-
|
|
290
|
-
// Demo-only stand-in for the backend response. Production code should replace this
|
|
291
|
-
// with a request using payload.start/payload.end and the optional channel metadata.
|
|
292
|
-
let nextData = chartData.value
|
|
293
|
-
batch.forEach((item) => {
|
|
294
|
-
const responseData = filterWaveformData(fullChartData, item.start, item.end)
|
|
295
|
-
nextData =
|
|
296
|
-
item.trackIndex !== undefined && item.seriesIds?.length
|
|
297
|
-
? mergeIndependentWindow(nextData, responseData, item.seriesIds)
|
|
298
|
-
: responseData
|
|
299
|
-
})
|
|
300
|
-
chartData.value = nextData
|
|
301
|
-
pendingCommandId = undefined
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function resetWaveformViewport() {
|
|
305
|
-
zoomRequestSequence += 1
|
|
306
|
-
chartData.value = fullChartData
|
|
307
|
-
initialXDomain.value = initialXDomainValue
|
|
308
|
-
demoChartHostRef.value?.resetViewport()
|
|
309
|
-
}
|
|
310
236
|
const titleOptions = computed<WaveformTitleOptions>(() => ({
|
|
311
237
|
visible: titleVisible.value,
|
|
312
238
|
text: titleText.value,
|
|
@@ -406,6 +332,11 @@ const chartModel = reactive({
|
|
|
406
332
|
toolbarVisible,
|
|
407
333
|
data: displayChartData,
|
|
408
334
|
initialXDomain,
|
|
335
|
+
recordDomain,
|
|
336
|
+
minZoomSpan,
|
|
337
|
+
loading,
|
|
338
|
+
errors,
|
|
339
|
+
retry,
|
|
409
340
|
displayMode,
|
|
410
341
|
overlayMode,
|
|
411
342
|
rowCount,
|
|
@@ -428,7 +359,10 @@ const chartModel = reactive({
|
|
|
428
359
|
annotationsVisible,
|
|
429
360
|
interactionMode,
|
|
430
361
|
hiddenSeriesIds,
|
|
362
|
+
handleZoomIntent,
|
|
363
|
+
handleZoomChange,
|
|
431
364
|
handleZoomEnd,
|
|
365
|
+
handlePanEnd,
|
|
432
366
|
resetWaveformViewport,
|
|
433
367
|
}) satisfies DemoChartModel
|
|
434
368
|
</script>
|
|
@@ -46,11 +46,9 @@ const {
|
|
|
46
46
|
chartWidth,
|
|
47
47
|
drawingHeight,
|
|
48
48
|
hasWaveformData,
|
|
49
|
-
requestViewportReset,
|
|
50
49
|
trackLayouts,
|
|
51
50
|
presentationTracks,
|
|
52
51
|
presentationAnnotations,
|
|
53
|
-
paginationOpacity,
|
|
54
52
|
stabilizeAnimations,
|
|
55
53
|
clipPathId,
|
|
56
54
|
chartTopMargin,
|
|
@@ -189,7 +187,6 @@ function handleChartPointerLeave() {
|
|
|
189
187
|
:height="drawingHeight"
|
|
190
188
|
role="img"
|
|
191
189
|
:aria-label="hasWaveformData ? '波形折线图' : '暂无波形数据'"
|
|
192
|
-
@dblclick="requestViewportReset"
|
|
193
190
|
>
|
|
194
191
|
<defs>
|
|
195
192
|
<clipPath
|
|
@@ -244,7 +241,6 @@ function handleChartPointerLeave() {
|
|
|
244
241
|
v-for="track in trackLayouts"
|
|
245
242
|
:key="`${track.index}-${track.id}`"
|
|
246
243
|
:track="presentationTracks.find((item) => item.id === track.id) ?? track"
|
|
247
|
-
:pagination-opacity="paginationOpacity"
|
|
248
244
|
:frame-path="
|
|
249
245
|
displayMode === 'compact'
|
|
250
246
|
? compactFramePath(track, trackLayouts, compactFrameInset)
|
|
@@ -62,7 +62,7 @@ export function resolveAnimationProps(
|
|
|
62
62
|
animationEnabled: field(props.animationEnabled, true, boolean, 'animationEnabled'),
|
|
63
63
|
animationThreshold: field(
|
|
64
64
|
props.animationThreshold,
|
|
65
|
-
|
|
65
|
+
200000,
|
|
66
66
|
(v) => nonnegative(v) && Number.isInteger(v),
|
|
67
67
|
'animationThreshold',
|
|
68
68
|
),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ComputedRef, Ref, ShallowRef } from 'vue'
|
|
2
2
|
import type { TrackLayout } from '../core/types'
|
|
3
|
+
import { normalizeGridOptions } from '../core/grid'
|
|
3
4
|
import type { PreparedWaveformSeries } from '../core/useWaveformData'
|
|
4
5
|
import type { ResolvedWaveformChartProps } from '../core/waveformChartTypes'
|
|
5
6
|
|
|
@@ -18,6 +19,11 @@ export interface AnimationContext {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export function animationGridShape(context: AnimationContext) {
|
|
23
|
+
const grid = normalizeGridOptions(context.props.grid)
|
|
24
|
+
return `${grid.rowCount}:${grid.columnCount}`
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
export function animationLayoutSignature(context: AnimationContext) {
|
|
22
28
|
const { props, tracks } = context
|
|
23
29
|
return JSON.stringify({
|
|
@@ -27,8 +33,10 @@ export function animationLayoutSignature(context: AnimationContext) {
|
|
|
27
33
|
track.top,
|
|
28
34
|
track.width,
|
|
29
35
|
track.height,
|
|
36
|
+
track.xScale.domain(),
|
|
37
|
+
track.yScale.domain(),
|
|
30
38
|
track.seriesList.map((series) => series.id),
|
|
31
|
-
track.yAxes.map((axis) => axis.seriesList.map((series) => series.id)),
|
|
39
|
+
track.yAxes.map((axis) => [axis.seriesList.map((series) => series.id), axis.scale.domain()]),
|
|
32
40
|
]),
|
|
33
41
|
width: context.innerWidth.value,
|
|
34
42
|
height: context.innerHeight.value,
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type
|
|
1
|
+
import type { ComputedRef } from 'vue'
|
|
2
|
+
import { animationLayoutSignature, type AnimationContext } from './context'
|
|
3
3
|
import type { ResolvedAnimationProps } from './config'
|
|
4
4
|
import type { WaveformAnimationController } from '@/animation/WaveformAnimationController'
|
|
5
|
-
import { clampProgress } from '@/animation/presets/shared'
|
|
6
5
|
import { chartExportOptions } from '../controls/chartExportOptions'
|
|
7
6
|
|
|
8
|
-
/**
|
|
7
|
+
/** Coordinates entrance replay; drawing remains owned by the normal series pipeline. */
|
|
9
8
|
export function createPaginationPresentation(
|
|
10
9
|
context: AnimationContext,
|
|
11
10
|
controller: WaveformAnimationController,
|
|
12
11
|
config: ComputedRef<ResolvedAnimationProps>,
|
|
13
12
|
effective: ComputedRef<boolean>,
|
|
14
|
-
|
|
13
|
+
prepare: () => void,
|
|
15
14
|
) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
let request:
|
|
16
|
+
| {
|
|
17
|
+
page: number
|
|
18
|
+
trigger: 'page' | 'grid'
|
|
19
|
+
environment: string
|
|
20
|
+
started: boolean
|
|
21
|
+
geometry?: string
|
|
22
|
+
signature?: string
|
|
23
|
+
}
|
|
24
|
+
| undefined
|
|
20
25
|
const environment = () =>
|
|
21
26
|
JSON.stringify({
|
|
22
27
|
options: Object.fromEntries(
|
|
@@ -25,82 +30,58 @@ export function createPaginationPresentation(
|
|
|
25
30
|
),
|
|
26
31
|
),
|
|
27
32
|
size: context.containerSize.value,
|
|
33
|
+
amplitudeLimits: context.props.amplitudeLimits,
|
|
28
34
|
hidden: context.props.hiddenSeriesIds,
|
|
29
35
|
presentation: context.props.presentationMode,
|
|
30
36
|
})
|
|
31
37
|
function reset() {
|
|
32
38
|
request = undefined
|
|
33
|
-
opacity.value = 1
|
|
34
39
|
}
|
|
35
|
-
function
|
|
36
|
-
controller.cancel(key)
|
|
40
|
+
function requestPage(page: number, trigger: 'page' | 'grid' = 'page') {
|
|
37
41
|
reset()
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
cancel()
|
|
42
|
+
controller.cancelAll()
|
|
43
|
+
// Discard old-page presentation before recording the new request.
|
|
41
44
|
controller.finishAll()
|
|
42
|
-
if (!effective.value || !config.value.
|
|
43
|
-
request = { page,
|
|
44
|
-
if (config.value.animationUpdate.duration > 0) opacity.value = 0
|
|
45
|
+
if (!effective.value || !config.value.animation.enabled) return
|
|
46
|
+
request = { page, trigger, environment: environment(), started: false }
|
|
45
47
|
}
|
|
46
|
-
function reconcile(dataChanged: boolean) {
|
|
48
|
+
function reconcile(dataChanged: boolean): 'replay' | 'static' | undefined {
|
|
47
49
|
const current = request
|
|
48
|
-
if (!current) return
|
|
50
|
+
if (!current) return
|
|
51
|
+
if (!config.value.animation.enabled) {
|
|
52
|
+
// Category closure is handled by finishWhere; it must release, not finish annotations.
|
|
53
|
+
reset()
|
|
54
|
+
return
|
|
55
|
+
}
|
|
49
56
|
if (
|
|
50
57
|
dataChanged ||
|
|
51
58
|
current.page !== context.currentPage.value ||
|
|
52
59
|
current.environment !== environment() ||
|
|
53
60
|
!effective.value ||
|
|
54
|
-
!config.value.
|
|
61
|
+
(!current.started && config.value.animation.duration === 0) ||
|
|
62
|
+
['fallback', 'error'].includes(context.sampling.state.value) ||
|
|
63
|
+
(current.started &&
|
|
64
|
+
(current.geometry !== animationLayoutSignature(context) ||
|
|
65
|
+
current.signature !== context.sampling.requestSignature.value ||
|
|
66
|
+
context.sampling.state.value !== 'success'))
|
|
55
67
|
) {
|
|
56
68
|
controller.finishAll()
|
|
57
69
|
reset()
|
|
58
|
-
return
|
|
70
|
+
return 'static'
|
|
59
71
|
}
|
|
60
|
-
if (
|
|
61
|
-
commit()
|
|
62
|
-
if (context.sampling.state.value === 'pending' || !context.layoutReady.value) return true
|
|
63
|
-
if (context.sampling.state.value !== 'success') {
|
|
64
|
-
controller.finishAll()
|
|
72
|
+
if (current.started && !controller.getActiveAnimationCount()) {
|
|
65
73
|
reset()
|
|
66
|
-
return
|
|
74
|
+
return
|
|
67
75
|
}
|
|
68
|
-
if (current.started)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
if (!ids.length) {
|
|
77
|
-
reset()
|
|
78
|
-
return true
|
|
76
|
+
if (!current.started) {
|
|
77
|
+
prepare()
|
|
78
|
+
current.started = context.sampling.state.value === 'success' && context.layoutReady.value
|
|
79
|
+
if (current.started) {
|
|
80
|
+
current.geometry = animationLayoutSignature(context)
|
|
81
|
+
current.signature = context.sampling.requestSignature.value
|
|
82
|
+
}
|
|
79
83
|
}
|
|
80
|
-
|
|
81
|
-
const options = config.value.animationUpdate
|
|
82
|
-
const notify = (callback?: () => void) => ids.forEach(() => callback?.())
|
|
83
|
-
controller.animate(key, {
|
|
84
|
-
duration: options.duration,
|
|
85
|
-
easing: options.easing,
|
|
86
|
-
force: true,
|
|
87
|
-
initialize: () => {
|
|
88
|
-
opacity.value = options.duration > 0 ? 0 : 1
|
|
89
|
-
},
|
|
90
|
-
during: (progress, frame) => {
|
|
91
|
-
if (request?.version === current.version)
|
|
92
|
-
opacity.value = frame.terminal ? 1 : clampProgress(progress)
|
|
93
|
-
},
|
|
94
|
-
done: () => {
|
|
95
|
-
if (request?.version === current.version) reset()
|
|
96
|
-
notify(options.onComplete)
|
|
97
|
-
},
|
|
98
|
-
aborted: () => {
|
|
99
|
-
if (request?.version === current.version) reset()
|
|
100
|
-
notify(options.onAbort)
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
return true
|
|
84
|
+
return 'replay'
|
|
104
85
|
}
|
|
105
86
|
function wrapPagination(goToPage: (page: number) => void, count: ComputedRef<number>) {
|
|
106
87
|
return (page: number) => {
|
|
@@ -110,5 +91,15 @@ export function createPaginationPresentation(
|
|
|
110
91
|
goToPage(page)
|
|
111
92
|
}
|
|
112
93
|
}
|
|
113
|
-
return {
|
|
94
|
+
return {
|
|
95
|
+
reset,
|
|
96
|
+
reconcile,
|
|
97
|
+
wrapPagination,
|
|
98
|
+
requestLayout: () => requestPage(context.currentPage.value, 'grid'),
|
|
99
|
+
enterDuration: () =>
|
|
100
|
+
request?.trigger === 'page' && config.value.animation.duration > 0
|
|
101
|
+
? 500
|
|
102
|
+
: config.value.animation.duration,
|
|
103
|
+
active: () => !!request && (!request.started || controller.getActiveAnimationCount() > 0),
|
|
104
|
+
}
|
|
114
105
|
}
|