waveform-analysis 0.1.32 → 0.1.35
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 +22 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +418 -400
- package/dist/types/components/WaveformChart.vue.d.ts +1 -0
- package/dist/types/components/core/trackLayoutBuilder.d.ts +2 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +1 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +3 -2
- package/dist/types/components/core/xDomain.d.ts +3 -0
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/waveform.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/chart.d.ts +10 -0
- package/dist/types/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/WaveformChart.vue +1 -0
- package/src/components/core/trackLayoutBuilder.ts +19 -7
- package/src/components/core/useWaveformChartLifecycle.ts +1 -0
- package/src/components/core/useWaveformLayout.ts +19 -6
- package/src/components/core/waveformChartTypes.ts +3 -0
- package/src/components/core/xDomain.test.ts +28 -0
- package/src/components/core/xDomain.ts +25 -0
- package/src/components/data/types.ts +1 -0
- package/src/components/waveform.ts +1 -0
- package/src/components/waveformChartCases/viewportReset.test.ts +35 -0
- package/src/components/waveformChartCases/xDomainStrategy.test.ts +96 -0
- package/src/index.ts +1 -0
- package/src/types/chart.ts +11 -0
- package/src/types/index.ts +1 -0
|
@@ -44,6 +44,7 @@ declare const __VLS_export: import("vue").DefineComponent<WaveformChartProps, {
|
|
|
44
44
|
zoomable: boolean;
|
|
45
45
|
pannable: boolean;
|
|
46
46
|
minVisiblePoints: number;
|
|
47
|
+
xDomainStrategy: import("..").WaveformXDomainStrategy;
|
|
47
48
|
timeUnit: "s" | "ms";
|
|
48
49
|
annotationsVisible: boolean;
|
|
49
50
|
grid: import("..").WaveformGridOptions;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ZoomTransform } from 'd3';
|
|
2
2
|
import { type ResolvedWaveformRenderingOptions } from '../../core/rendering';
|
|
3
|
-
import type { WaveformDisplayMode, WaveformOverlayMode, WaveformXAxisLabelFormatter } from '../../types';
|
|
3
|
+
import type { WaveformDisplayMode, WaveformOverlayMode, WaveformXDomainStrategy, WaveformXAxisLabelFormatter } from '../../types';
|
|
4
4
|
import { type GridCellGeometry, type NormalizedWaveformGridOptions } from './grid';
|
|
5
5
|
import type { DisplayTrack, TrackLayout } from './types';
|
|
6
6
|
interface SeriesGridCell extends GridCellGeometry {
|
|
@@ -15,6 +15,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
15
15
|
sharedZoomDomain: [number, number];
|
|
16
16
|
initialXDomain?: [number, number];
|
|
17
17
|
initialXDomains?: Record<string, [number, number]>;
|
|
18
|
+
xDomainStrategy?: WaveformXDomainStrategy;
|
|
18
19
|
fixedYDomain?: [number, number];
|
|
19
20
|
fixedYDomains?: Record<string, [number, number]>;
|
|
20
21
|
yDomains?: Record<string, [number, number]>;
|
|
@@ -330,6 +330,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
|
|
|
330
330
|
minZoomSpan?: number | undefined;
|
|
331
331
|
minVisiblePoints: number;
|
|
332
332
|
initialXDomains?: Record<string, [number, number]> | undefined;
|
|
333
|
+
xDomainStrategy: import("../..").WaveformXDomainStrategy;
|
|
333
334
|
yDomain?: [number, number] | undefined;
|
|
334
335
|
yDomains?: Record<string, [number, number]> | undefined;
|
|
335
336
|
timeUnit: "s" | "ms";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPlotMargin, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload, WaveformZoomResetPayload } from '../data/types';
|
|
1
|
+
import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPlotMargin, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformXDomainStrategy, WaveformZeroLineOptions, WaveformZoomEndPayload, WaveformZoomResetPayload } from '../data/types';
|
|
2
2
|
import type { WaveformGridOptions } from './grid';
|
|
3
3
|
export interface WaveformChartProps {
|
|
4
4
|
data: WaveformData;
|
|
@@ -16,6 +16,7 @@ export interface WaveformChartProps {
|
|
|
16
16
|
minVisiblePoints?: number;
|
|
17
17
|
initialXDomain?: [number, number];
|
|
18
18
|
initialXDomains?: Record<string, [number, number]>;
|
|
19
|
+
xDomainStrategy?: WaveformXDomainStrategy;
|
|
19
20
|
yDomain?: [number, number];
|
|
20
21
|
yDomains?: Record<string, [number, number]>;
|
|
21
22
|
timeUnit?: 's' | 'ms';
|
|
@@ -36,7 +37,7 @@ export interface WaveformChartProps {
|
|
|
36
37
|
presentationMode?: boolean;
|
|
37
38
|
zeroLine?: WaveformZeroLineOptions;
|
|
38
39
|
}
|
|
39
|
-
type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
|
|
40
|
+
type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'xDomainStrategy' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
|
|
40
41
|
export type ResolvedWaveformChartProps = Readonly<WaveformChartProps & Required<Pick<WaveformChartProps, DefaultedProp>>>;
|
|
41
42
|
export interface WaveformChartEmit {
|
|
42
43
|
(event: 'point-hover', point: WaveformPoint | null): void;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WaveformXDomainStrategy } from '../../types';
|
|
2
|
+
/** Derives a viewport domain without changing any source coordinates. */
|
|
3
|
+
export declare function applyXDomainStrategy(domain: [number, number], strategy: WaveformXDomainStrategy, explicit?: boolean): [number, number];
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* 数据系统类型定义
|
|
3
3
|
* 重新导出 src/types 中的类型,方便组件内部使用
|
|
4
4
|
*/
|
|
5
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from '../../types';
|
|
5
|
+
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from '../../types';
|
|
6
6
|
export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from '../core/grid';
|
|
7
7
|
export { normalizeWaveformData, normalizeWaveformSeries } from '../../core';
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* 向后兼容的导出文件
|
|
3
3
|
* 保留原有导入路径,内部从新的模块结构导出
|
|
4
4
|
*/
|
|
5
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
|
|
5
|
+
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
|
|
6
6
|
export { normalizeWaveformData, normalizeWaveformSeries } from '../core';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @packageDocumentation
|
|
4
4
|
*/
|
|
5
5
|
export { default as WaveformChart } from './components/WaveformChart.vue';
|
|
6
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
|
|
6
|
+
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
|
|
7
7
|
export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './components/core/grid';
|
|
8
8
|
export { normalizeWaveformData, normalizeWaveformSeries } from './core';
|
|
9
9
|
export { paddedDomain, buildMinorTicks, displayTime, formatEndpointTime, formatAxisTime, formatTooltipTime, resolveTrackGeometry, clamp, type TimeUnit, type TrackGeometry, } from './utils';
|
|
@@ -22,6 +22,16 @@ export type WaveformDisplayMode = 'independent' | 'separated' | 'compact';
|
|
|
22
22
|
export type WaveformOverlayMode = 'single-axis' | 'multi-axis';
|
|
23
23
|
/** 标注工具模式 */
|
|
24
24
|
export type WaveformInteractionMode = 'zoom' | 'annotation';
|
|
25
|
+
/** Controls how the initial X viewport is derived when no explicit domain is configured. */
|
|
26
|
+
export interface WaveformXDomainStrategy {
|
|
27
|
+
type: 'data' | 'nice';
|
|
28
|
+
/** Selects which bounds are expanded when type is `nice`. Defaults to `both`. */
|
|
29
|
+
bounds?: 'both' | 'end';
|
|
30
|
+
/** Stable tick count used to calculate nice bounds. Defaults to 10. */
|
|
31
|
+
tickCount?: number;
|
|
32
|
+
/** Applies the strategy to explicit initial domains. Defaults to false. */
|
|
33
|
+
includeExplicit?: boolean;
|
|
34
|
+
}
|
|
25
35
|
/** Describes the X-axis viewport after a zoom gesture completes. */
|
|
26
36
|
export interface WaveformZoomEndPayload {
|
|
27
37
|
start: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 类型定义统一导出
|
|
3
3
|
*/
|
|
4
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
|
|
4
|
+
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
|
|
5
5
|
export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
WaveformDisplayMode,
|
|
17
17
|
WaveformOverlayMode,
|
|
18
18
|
WaveformPoint,
|
|
19
|
+
WaveformXDomainStrategy,
|
|
19
20
|
WaveformXAxisLabelFormatter,
|
|
20
21
|
} from '../../types'
|
|
21
22
|
import { buildMinorTicks, formatXAxisLabel } from '../../utils'
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
} from './grid'
|
|
27
28
|
import { axisTextMetrics, resolveYAxisSeriesGroups } from './layout'
|
|
28
29
|
import type { DisplaySeries, DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
|
30
|
+
import { applyXDomainStrategy } from './xDomain'
|
|
29
31
|
import {
|
|
30
32
|
Y_AXIS_LABEL_BAND_WIDTH,
|
|
31
33
|
Y_AXIS_LABEL_GAP,
|
|
@@ -46,6 +48,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
46
48
|
sharedZoomDomain: [number, number]
|
|
47
49
|
initialXDomain?: [number, number]
|
|
48
50
|
initialXDomains?: Record<string, [number, number]>
|
|
51
|
+
xDomainStrategy?: WaveformXDomainStrategy
|
|
49
52
|
fixedYDomain?: [number, number]
|
|
50
53
|
fixedYDomains?: Record<string, [number, number]>
|
|
51
54
|
yDomains?: Record<string, [number, number]>
|
|
@@ -57,6 +60,21 @@ export interface BuildTrackLayoutsOptions {
|
|
|
57
60
|
showCompactEmptyTracks: boolean
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
function resolveIndependentXDomain(
|
|
64
|
+
track: DisplayTrack,
|
|
65
|
+
seriesId: string,
|
|
66
|
+
options: BuildTrackLayoutsOptions,
|
|
67
|
+
): [number, number] {
|
|
68
|
+
const strategy = options.xDomainStrategy ?? { type: 'data' }
|
|
69
|
+
const explicitDomain =
|
|
70
|
+
options.initialXDomains?.[track.id] ??
|
|
71
|
+
options.initialXDomains?.[seriesId] ??
|
|
72
|
+
options.initialXDomain
|
|
73
|
+
return explicitDomain
|
|
74
|
+
? applyXDomainStrategy(explicitDomain, strategy, true)
|
|
75
|
+
: applyXDomainStrategy(track.xDomain, strategy)
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayout[] {
|
|
61
79
|
const visibleCells = options.cells.map((cell) => ({ ...cell, hasSeries: Boolean(cell.series) }))
|
|
62
80
|
const bottomCells = getBottomRowCellIndexes(visibleCells, options.grid.columnCount)
|
|
@@ -88,13 +106,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
88
106
|
const series = displayTrack.visibleSeries[0] ?? displayTrack.series[0] ?? emptySeries
|
|
89
107
|
const baseXScale =
|
|
90
108
|
options.displayMode === 'independent'
|
|
91
|
-
? scaleLinear(
|
|
92
|
-
options.initialXDomains?.[displayTrack.id] ??
|
|
93
|
-
options.initialXDomains?.[series.id] ??
|
|
94
|
-
options.initialXDomain ??
|
|
95
|
-
displayTrack.xDomain,
|
|
96
|
-
[0, cell.width],
|
|
97
|
-
)
|
|
109
|
+
? scaleLinear(resolveIndependentXDomain(displayTrack, series.id, options), [0, cell.width])
|
|
98
110
|
: scaleLinear(options.sharedZoomDomain, [0, cell.width])
|
|
99
111
|
const transform =
|
|
100
112
|
options.displayMode === 'independent'
|
|
@@ -204,6 +204,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
204
204
|
() => props.minVisiblePoints,
|
|
205
205
|
() => props.initialXDomain,
|
|
206
206
|
() => props.initialXDomains,
|
|
207
|
+
() => props.xDomainStrategy,
|
|
207
208
|
() => props.displayMode,
|
|
208
209
|
() => chartTracks.value.length,
|
|
209
210
|
currentPage,
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
35
35
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
36
36
|
import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
37
|
+
import { applyXDomainStrategy } from './xDomain'
|
|
37
38
|
import type { useWaveformAnnotationInteraction } from '../annotation'
|
|
38
39
|
|
|
39
40
|
interface LayoutContext {
|
|
@@ -224,9 +225,13 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
224
225
|
Number.isFinite(domain[1]) &&
|
|
225
226
|
domain[0] !== domain[1]
|
|
226
227
|
) {
|
|
227
|
-
return
|
|
228
|
+
return applyXDomainStrategy(
|
|
229
|
+
domain[0] < domain[1] ? domain : [domain[1], domain[0]],
|
|
230
|
+
props.xDomainStrategy,
|
|
231
|
+
true,
|
|
232
|
+
)
|
|
228
233
|
}
|
|
229
|
-
return sharedXDomain.value
|
|
234
|
+
return applyXDomainStrategy(sharedXDomain.value, props.xDomainStrategy)
|
|
230
235
|
})
|
|
231
236
|
const resolveInitialTrackDomain = (track: TrackLayout): [number, number] => {
|
|
232
237
|
const configuredDomain =
|
|
@@ -239,11 +244,18 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
239
244
|
Number.isFinite(configuredDomain[1]) &&
|
|
240
245
|
configuredDomain[0] !== configuredDomain[1]
|
|
241
246
|
) {
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
return applyXDomainStrategy(
|
|
248
|
+
configuredDomain[0] < configuredDomain[1]
|
|
249
|
+
? configuredDomain
|
|
250
|
+
: [configuredDomain[1], configuredDomain[0]],
|
|
251
|
+
props.xDomainStrategy,
|
|
252
|
+
true,
|
|
253
|
+
)
|
|
245
254
|
}
|
|
246
|
-
return
|
|
255
|
+
return applyXDomainStrategy(
|
|
256
|
+
paddedDomain(track.seriesList.flatMap((series) => series.xDomain)),
|
|
257
|
+
props.xDomainStrategy,
|
|
258
|
+
)
|
|
247
259
|
}
|
|
248
260
|
const sharedZoomDomain = computed(
|
|
249
261
|
() =>
|
|
@@ -272,6 +284,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
272
284
|
sharedZoomDomain: sharedZoomDomain.value,
|
|
273
285
|
initialXDomain: props.initialXDomain ? initialXDomain.value : undefined,
|
|
274
286
|
initialXDomains: props.initialXDomains,
|
|
287
|
+
xDomainStrategy: props.xDomainStrategy,
|
|
275
288
|
fixedYDomain: props.yDomain,
|
|
276
289
|
fixedYDomains: props.yDomains,
|
|
277
290
|
yDomains:
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
WaveformPoint,
|
|
12
12
|
WaveformRenderingOptions,
|
|
13
13
|
WaveformTitleOptions,
|
|
14
|
+
WaveformXDomainStrategy,
|
|
14
15
|
WaveformZeroLineOptions,
|
|
15
16
|
WaveformZoomEndPayload,
|
|
16
17
|
WaveformZoomResetPayload,
|
|
@@ -33,6 +34,7 @@ export interface WaveformChartProps {
|
|
|
33
34
|
minVisiblePoints?: number
|
|
34
35
|
initialXDomain?: [number, number]
|
|
35
36
|
initialXDomains?: Record<string, [number, number]>
|
|
37
|
+
xDomainStrategy?: WaveformXDomainStrategy
|
|
36
38
|
yDomain?: [number, number]
|
|
37
39
|
yDomains?: Record<string, [number, number]>
|
|
38
40
|
timeUnit?: 's' | 'ms'
|
|
@@ -63,6 +65,7 @@ type DefaultedProp =
|
|
|
63
65
|
| 'zoomable'
|
|
64
66
|
| 'pannable'
|
|
65
67
|
| 'minVisiblePoints'
|
|
68
|
+
| 'xDomainStrategy'
|
|
66
69
|
| 'timeUnit'
|
|
67
70
|
| 'annotations'
|
|
68
71
|
| 'annotationsVisible'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { applyXDomainStrategy } from './xDomain'
|
|
4
|
+
|
|
5
|
+
describe('applyXDomainStrategy', () => {
|
|
6
|
+
it('keeps the exact data domain by default', () => {
|
|
7
|
+
expect(applyXDomainStrategy([0, 4999.999], { type: 'data' })).toEqual([0, 4999.999])
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('expands both bounds to stable nice values', () => {
|
|
11
|
+
expect(applyXDomainStrategy([123, 456], { type: 'nice' })).toEqual([100, 500])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('can expand only the end bound', () => {
|
|
15
|
+
expect(applyXDomainStrategy([123, 456], { type: 'nice', bounds: 'end' })).toEqual([123, 500])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('keeps explicit domains exact unless they are included', () => {
|
|
19
|
+
expect(applyXDomainStrategy([123, 456], { type: 'nice' }, true)).toEqual([123, 456])
|
|
20
|
+
expect(applyXDomainStrategy([123, 456], { type: 'nice', includeExplicit: true }, true)).toEqual(
|
|
21
|
+
[100, 500],
|
|
22
|
+
)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('falls back to the default tick count for invalid values', () => {
|
|
26
|
+
expect(applyXDomainStrategy([0, 4999.999], { type: 'nice', tickCount: 0 })).toEqual([0, 5000])
|
|
27
|
+
})
|
|
28
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { scaleLinear } from 'd3'
|
|
2
|
+
|
|
3
|
+
import type { WaveformXDomainStrategy } from '../../types'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_NICE_TICK_COUNT = 10
|
|
6
|
+
|
|
7
|
+
function resolveTickCount(value: number | undefined): number {
|
|
8
|
+
if (!Number.isFinite(value) || (value ?? 0) < 1) return DEFAULT_NICE_TICK_COUNT
|
|
9
|
+
return Math.max(1, Math.trunc(value as number))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Derives a viewport domain without changing any source coordinates. */
|
|
13
|
+
export function applyXDomainStrategy(
|
|
14
|
+
domain: [number, number],
|
|
15
|
+
strategy: WaveformXDomainStrategy,
|
|
16
|
+
explicit = false,
|
|
17
|
+
): [number, number] {
|
|
18
|
+
if (strategy.type !== 'nice' || (explicit && !strategy.includeExplicit)) return [...domain]
|
|
19
|
+
|
|
20
|
+
const niceDomain = scaleLinear()
|
|
21
|
+
.domain(domain)
|
|
22
|
+
.nice(resolveTickCount(strategy.tickCount))
|
|
23
|
+
.domain() as [number, number]
|
|
24
|
+
return strategy.bounds === 'end' ? [domain[0], niceDomain[1]] : niceDomain
|
|
25
|
+
}
|
|
@@ -5,6 +5,13 @@ import { flushAnimationFrames } from '../../test/setup'
|
|
|
5
5
|
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
6
6
|
|
|
7
7
|
describe('WaveformChart viewport reset', () => {
|
|
8
|
+
const niceExplicitStrategy = {
|
|
9
|
+
type: 'nice' as const,
|
|
10
|
+
bounds: 'end' as const,
|
|
11
|
+
tickCount: 10,
|
|
12
|
+
includeExplicit: true,
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
it('resets a shared viewport on double-click and emits a global payload', async () => {
|
|
9
16
|
const wrapper = await mountSizedChart(
|
|
10
17
|
{
|
|
@@ -111,4 +118,32 @@ describe('WaveformChart viewport reset', () => {
|
|
|
111
118
|
expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0')
|
|
112
119
|
expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1000')
|
|
113
120
|
})
|
|
121
|
+
|
|
122
|
+
it('resets shared and independent explicit domains to their included nice bounds', async () => {
|
|
123
|
+
const shared = await mountSizedChart(
|
|
124
|
+
{
|
|
125
|
+
kind: 'points',
|
|
126
|
+
points: [
|
|
127
|
+
{ x: 0, y: 0 },
|
|
128
|
+
{ x: 4.999999, y: 1 },
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
{ initialXDomain: [0, 4.999999], xDomainStrategy: niceExplicitStrategy },
|
|
132
|
+
)
|
|
133
|
+
const independent = await mountSizedChart(gridSeries(2), {
|
|
134
|
+
displayMode: 'independent',
|
|
135
|
+
initialXDomains: { 'channel-0': [0, 4.999999] },
|
|
136
|
+
xDomainStrategy: niceExplicitStrategy,
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
;(shared.vm as unknown as { resetViewport: () => void }).resetViewport()
|
|
140
|
+
;(independent.vm as unknown as { resetViewport: () => void }).resetViewport()
|
|
141
|
+
await flushPromises()
|
|
142
|
+
|
|
143
|
+
expect(shared.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
144
|
+
expect(shared.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
|
|
145
|
+
const firstTrack = independent.findAll('.waveform-chart__track')[0]
|
|
146
|
+
expect(firstTrack.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
147
|
+
expect(firstTrack.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
|
|
148
|
+
})
|
|
114
149
|
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { gridSeries, mountSizedChart } from '../../test/waveformChart'
|
|
4
|
+
|
|
5
|
+
describe('WaveformChart x domain strategy', () => {
|
|
6
|
+
it('expands automatic x domains without changing source seconds', async () => {
|
|
7
|
+
const data = {
|
|
8
|
+
kind: 'points' as const,
|
|
9
|
+
points: [
|
|
10
|
+
{ x: 0, y: 0 },
|
|
11
|
+
{ x: 4.999999, y: 1 },
|
|
12
|
+
],
|
|
13
|
+
}
|
|
14
|
+
const wrapper = await mountSizedChart(data, {
|
|
15
|
+
timeUnit: 'ms',
|
|
16
|
+
xDomainStrategy: { type: 'nice', bounds: 'end' },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
20
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
|
|
21
|
+
expect(data.points[1]?.x).toBe(4.999999)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('keeps explicit initial x domains exact by default', async () => {
|
|
25
|
+
const wrapper = await mountSizedChart(
|
|
26
|
+
{
|
|
27
|
+
kind: 'points',
|
|
28
|
+
points: [
|
|
29
|
+
{ x: 123, y: 0 },
|
|
30
|
+
{ x: 456, y: 1 },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
timeUnit: 's',
|
|
35
|
+
initialXDomain: [120, 460],
|
|
36
|
+
xDomainStrategy: { type: 'nice' },
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('120')
|
|
41
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('460')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('keeps explicit per-track initial x domains exact by default', async () => {
|
|
45
|
+
const wrapper = await mountSizedChart(gridSeries(1), {
|
|
46
|
+
displayMode: 'independent',
|
|
47
|
+
initialXDomains: { 'channel-0': [0, 4.999999] },
|
|
48
|
+
xDomainStrategy: { type: 'nice', bounds: 'end' },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
52
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4999.999')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('includes an explicit shared initial x domain when requested', async () => {
|
|
56
|
+
const wrapper = await mountSizedChart(
|
|
57
|
+
{
|
|
58
|
+
kind: 'points',
|
|
59
|
+
points: [
|
|
60
|
+
{ x: 0, y: 0 },
|
|
61
|
+
{ x: 4.999999, y: 1 },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
initialXDomain: [0, 4.999999],
|
|
66
|
+
xDomainStrategy: {
|
|
67
|
+
type: 'nice',
|
|
68
|
+
bounds: 'end',
|
|
69
|
+
tickCount: 10,
|
|
70
|
+
includeExplicit: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
76
|
+
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('includes explicit per-track initial x domains in independent mode', async () => {
|
|
80
|
+
const wrapper = await mountSizedChart(gridSeries(2), {
|
|
81
|
+
displayMode: 'independent',
|
|
82
|
+
grid: { rowCount: 1, columnCount: 2 },
|
|
83
|
+
initialXDomains: {
|
|
84
|
+
'channel-0': [0, 4.999999],
|
|
85
|
+
'channel-1': [10, 14.999999],
|
|
86
|
+
},
|
|
87
|
+
xDomainStrategy: { type: 'nice', bounds: 'end', tickCount: 10, includeExplicit: true },
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const tracks = wrapper.findAll('.waveform-chart__track')
|
|
91
|
+
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
|
|
92
|
+
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
|
|
93
|
+
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('10000')
|
|
94
|
+
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('15000')
|
|
95
|
+
})
|
|
96
|
+
})
|
package/src/index.ts
CHANGED
package/src/types/chart.ts
CHANGED
|
@@ -26,6 +26,17 @@ export type WaveformOverlayMode = 'single-axis' | 'multi-axis'
|
|
|
26
26
|
/** 标注工具模式 */
|
|
27
27
|
export type WaveformInteractionMode = 'zoom' | 'annotation'
|
|
28
28
|
|
|
29
|
+
/** Controls how the initial X viewport is derived when no explicit domain is configured. */
|
|
30
|
+
export interface WaveformXDomainStrategy {
|
|
31
|
+
type: 'data' | 'nice'
|
|
32
|
+
/** Selects which bounds are expanded when type is `nice`. Defaults to `both`. */
|
|
33
|
+
bounds?: 'both' | 'end'
|
|
34
|
+
/** Stable tick count used to calculate nice bounds. Defaults to 10. */
|
|
35
|
+
tickCount?: number
|
|
36
|
+
/** Applies the strategy to explicit initial domains. Defaults to false. */
|
|
37
|
+
includeExplicit?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
29
40
|
/** Describes the X-axis viewport after a zoom gesture completes. */
|
|
30
41
|
export interface WaveformZoomEndPayload {
|
|
31
42
|
start: number
|