waveform-analysis 0.1.28 → 0.1.30

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.
Files changed (48) hide show
  1. package/README.md +57 -33
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +590 -577
  4. package/dist/types/components/WaveformChart.vue.d.ts +1 -0
  5. package/dist/types/components/core/constants.d.ts +2 -0
  6. package/dist/types/components/core/trackLayoutBuilder.d.ts +2 -1
  7. package/dist/types/components/core/useWaveformChartController.d.ts +6 -1
  8. package/dist/types/components/core/useWaveformLayout.d.ts +0 -1
  9. package/dist/types/components/core/useWaveformPresentation.d.ts +5 -0
  10. package/dist/types/components/core/waveformChartTypes.d.ts +3 -2
  11. package/dist/types/components/data/types.d.ts +1 -1
  12. package/dist/types/components/index.d.ts +1 -1
  13. package/dist/types/components/waveform.d.ts +1 -1
  14. package/dist/types/demo/types.d.ts +13 -1
  15. package/dist/types/demo/useDemoXAxisLabelControls.d.ts +32 -0
  16. package/dist/types/index.d.ts +1 -1
  17. package/dist/types/types/chart.d.ts +22 -0
  18. package/dist/types/types/index.d.ts +1 -1
  19. package/dist/types/utils/formatters.d.ts +5 -2
  20. package/dist/types/utils/index.d.ts +1 -1
  21. package/package.json +1 -1
  22. package/src/App.vue +17 -1
  23. package/src/components/WaveformChart.vue +1 -0
  24. package/src/components/WaveformChartView.vue +13 -10
  25. package/src/components/core/constants.ts +3 -0
  26. package/src/components/core/trackLayoutBuilder.ts +22 -4
  27. package/src/components/core/useWaveformLayout.ts +1 -3
  28. package/src/components/core/useWaveformPresentation.ts +17 -2
  29. package/src/components/core/waveformChartTypes.ts +3 -0
  30. package/src/components/data/types.ts +1 -0
  31. package/src/components/index.ts +1 -0
  32. package/src/components/rendering/WaveformTrackAxes.vue +6 -3
  33. package/src/components/waveform.ts +1 -0
  34. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +70 -3
  35. package/src/components/waveformChartCases/sizingAndTitles.test.ts +59 -0
  36. package/src/demo/DemoChartHost.vue +1 -0
  37. package/src/demo/DemoGridFrameControls.vue +99 -0
  38. package/src/demo/plotMarginControls.test.ts +29 -0
  39. package/src/demo/types.ts +13 -0
  40. package/src/demo/useDemoXAxisLabelControls.ts +77 -0
  41. package/src/demo/xAxisLabelControls.test.ts +86 -0
  42. package/src/index.ts +4 -0
  43. package/src/styles.css +9 -0
  44. package/src/types/chart.ts +29 -0
  45. package/src/types/index.ts +4 -0
  46. package/src/utils/formatters.test.ts +37 -5
  47. package/src/utils/formatters.ts +26 -16
  48. package/src/utils/index.ts +1 -0
@@ -48,6 +48,7 @@ declare const __VLS_export: import("vue").DefineComponent<WaveformChartProps, {
48
48
  annotationsVisible: boolean;
49
49
  grid: import("..").WaveformGridOptions;
50
50
  rendering: import("..").WaveformRenderingOptions;
51
+ plotMargin: import("..").WaveformPlotMargin;
51
52
  defaultHiddenSeriesIds: string[];
52
53
  cleanView: boolean;
53
54
  presentationMode: boolean;
@@ -8,6 +8,8 @@ export declare const margin: {
8
8
  bottom: number;
9
9
  left: number;
10
10
  };
11
+ /** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
12
+ export declare const X_AXIS_TITLE_BOTTOM_OFFSET = 12;
11
13
  /**
12
14
  * 图表最小高度(像素)
13
15
  */
@@ -1,6 +1,6 @@
1
1
  import { type ZoomTransform } from 'd3';
2
2
  import { type ResolvedWaveformRenderingOptions } from '../../core/rendering';
3
- import type { WaveformDisplayMode, WaveformOverlayMode } from '../../types';
3
+ import type { WaveformDisplayMode, WaveformOverlayMode, 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 {
@@ -19,6 +19,7 @@ export interface BuildTrackLayoutsOptions {
19
19
  fixedYDomains?: Record<string, [number, number]>;
20
20
  yDomains?: Record<string, [number, number]>;
21
21
  timeUnit: 's' | 'ms';
22
+ xAxisLabelFormatter?: WaveformXAxisLabelFormatter;
22
23
  rendering: ResolvedWaveformRenderingOptions;
23
24
  hideSecondaryLabels: boolean;
24
25
  yAxisLabelX: number;
@@ -287,7 +287,6 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
287
287
  resolveSeriesYScale: (track: import("./types").TrackLayout, seriesId: string) => import("d3").ScaleLinear<number, number, never>;
288
288
  annotationTrackLayouts: import("../annotation").AnnotationTrackLayout[];
289
289
  renderedAnnotations: import("../annotation").RenderedAnnotation[];
290
- xAxisTitleY: number;
291
290
  editorSeries: import("../annotation").AnnotationSeriesInfo | undefined;
292
291
  resolveFrameNumber: (trackIndex: number) => string | number | undefined;
293
292
  fixedWidth: number | undefined;
@@ -318,8 +317,13 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
318
317
  titleMeasureStyle: import("vue").CSSProperties;
319
318
  titleLayout: import("./title").RotatedTitleLayout;
320
319
  titleAreaHeight: number;
320
+ resolvedPlotMargin: {
321
+ top: number;
322
+ bottom: number;
323
+ };
321
324
  chartTopMargin: number;
322
325
  drawingHeight: number;
326
+ xAxisTitleY: number;
323
327
  innerHeight: number;
324
328
  titleAreaStyle: import("vue").CSSProperties;
325
329
  titleVisualStyle: import("vue").CSSProperties;
@@ -349,6 +353,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
349
353
  interactionMode?: import("..").WaveformInteractionMode | undefined;
350
354
  grid: import("./grid").WaveformGridOptions;
351
355
  rendering: import("..").WaveformRenderingOptions;
356
+ plotMargin: import("..").WaveformPlotMargin;
352
357
  title?: import("..").WaveformTitleOptions | undefined;
353
358
  legend: import("..").WaveformLegendOptions;
354
359
  hiddenSeriesIds?: string[] | undefined;
@@ -54,7 +54,6 @@ export declare function useWaveformLayout(context: LayoutContext): {
54
54
  resolveSeriesYScale: (track: TrackLayout, seriesId: string) => import("d3").ScaleLinear<number, number, never>;
55
55
  annotationTrackLayouts: ComputedRef<AnnotationTrackLayout[]>;
56
56
  renderedAnnotations: ComputedRef<import("../annotation").RenderedAnnotation[]>;
57
- xAxisTitleY: ComputedRef<number>;
58
57
  editorSeries: ComputedRef<AnnotationSeriesInfo | undefined>;
59
58
  resolveFrameNumber: (trackIndex: number) => string | number | undefined;
60
59
  };
@@ -39,8 +39,13 @@ export declare function useWaveformPresentation(context: PresentationContext): {
39
39
  titleMeasureStyle: import("vue").ComputedRef<CSSProperties>;
40
40
  titleLayout: import("vue").ComputedRef<import("./title").RotatedTitleLayout>;
41
41
  titleAreaHeight: import("vue").ComputedRef<number>;
42
+ resolvedPlotMargin: import("vue").ComputedRef<{
43
+ top: number;
44
+ bottom: number;
45
+ }>;
42
46
  chartTopMargin: import("vue").ComputedRef<number>;
43
47
  drawingHeight: import("vue").ComputedRef<number>;
48
+ xAxisTitleY: import("vue").ComputedRef<number>;
44
49
  innerHeight: import("vue").ComputedRef<number>;
45
50
  titleAreaStyle: import("vue").ComputedRef<CSSProperties>;
46
51
  titleVisualStyle: import("vue").ComputedRef<CSSProperties>;
@@ -1,4 +1,4 @@
1
- import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../data/types';
1
+ import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPlotMargin, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../data/types';
2
2
  import type { WaveformGridOptions } from './grid';
3
3
  export interface WaveformChartProps {
4
4
  data: WaveformData;
@@ -27,6 +27,7 @@ export interface WaveformChartProps {
27
27
  interactionMode?: WaveformInteractionMode;
28
28
  grid?: WaveformGridOptions;
29
29
  rendering?: WaveformRenderingOptions;
30
+ plotMargin?: WaveformPlotMargin;
30
31
  title?: WaveformTitleOptions;
31
32
  legend?: WaveformLegendOptions;
32
33
  hiddenSeriesIds?: string[];
@@ -35,7 +36,7 @@ export interface WaveformChartProps {
35
36
  presentationMode?: boolean;
36
37
  zeroLine?: WaveformZeroLineOptions;
37
38
  }
38
- type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
39
+ type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
39
40
  export type ResolvedWaveformChartProps = Readonly<WaveformChartProps & Required<Pick<WaveformChartProps, DefaultedProp>>>;
40
41
  export interface WaveformChartEmit {
41
42
  (event: 'point-hover', point: WaveformPoint | null): void;
@@ -2,6 +2,6 @@
2
2
  * 数据系统类型定义
3
3
  * 重新导出 src/types 中的类型,方便组件内部使用
4
4
  */
5
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, 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, WaveformZoomEndPayload, 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';
@@ -1,5 +1,5 @@
1
1
  export { default as WaveformChart } from './WaveformChart.vue';
2
- export type { SingleWaveformData, WaveformData, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, WaveformPoint, WaveformSeries, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './data/types';
2
+ export type { SingleWaveformData, WaveformData, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, WaveformPoint, WaveformSeries, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './data/types';
3
3
  export type { WaveformGridOptions as WaveformGridConfig } from './core/grid';
4
4
  export { WaveformTooltip } from './interaction';
5
5
  export { WaveformTrack } from './rendering';
@@ -2,5 +2,5 @@
2
2
  * 向后兼容的导出文件
3
3
  * 保留原有导入路径,内部从新的模块结构导出
4
4
  */
5
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
5
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
6
6
  export { normalizeWaveformData, normalizeWaveformSeries } from '../core';
@@ -1,4 +1,5 @@
1
- import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
1
+ import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformPlotMargin, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
2
+ import type { DemoXAxisLabelFormat, DemoXAxisLabelTimeZone } from './useDemoXAxisLabelControls';
2
3
  interface SelectOption<T> {
3
4
  label: string;
4
5
  value: T;
@@ -8,6 +9,8 @@ export interface DemoControlPanelModel {
8
9
  displayMode: WaveformDisplayMode;
9
10
  overlayMode: WaveformOverlayMode;
10
11
  showTooltip: boolean;
12
+ plotMarginTop: number;
13
+ plotMarginBottom: number;
11
14
  cleanView: boolean;
12
15
  presentationMode: boolean;
13
16
  selectedSeriesId: string;
@@ -27,6 +30,14 @@ export interface DemoControlPanelModel {
27
30
  verticalGridColor: string;
28
31
  xAxisLineVisible: boolean;
29
32
  yAxisLineVisible: boolean;
33
+ xAxisLabelFormatterEnabled: boolean;
34
+ xAxisLabelFormat: DemoXAxisLabelFormat;
35
+ xAxisLabelFormatOptions: Array<SelectOption<DemoXAxisLabelFormat>>;
36
+ xAxisLabelMultiplier: number;
37
+ xAxisLabelFractionDigits: number;
38
+ xAxisLabelTimeZone: DemoXAxisLabelTimeZone;
39
+ xAxisLabelTimeZoneOptions: Array<SelectOption<DemoXAxisLabelTimeZone>>;
40
+ xAxisLabelShowMilliseconds: boolean;
30
41
  frameBorderColor: string;
31
42
  frameBackgroundColor: string;
32
43
  frameBorderWidth: number;
@@ -72,6 +83,7 @@ export interface DemoChartModel {
72
83
  cleanView: boolean;
73
84
  presentationMode: boolean;
74
85
  showTooltip: boolean;
86
+ plotMargin: WaveformPlotMargin;
75
87
  zeroLine: WaveformZeroLineOptions;
76
88
  frameWatermarkVisible: boolean;
77
89
  annotations: WaveformAnnotation[];
@@ -0,0 +1,32 @@
1
+ import type { WaveformXAxisLabelFormatter } from '../types';
2
+ export type DemoXAxisLabelFormat = 'number' | 'datetime';
3
+ export type DemoXAxisLabelTimeZone = 'local' | 'Asia/Shanghai' | 'UTC';
4
+ export declare function formatDemoTimestamp(value: number, timeZone: DemoXAxisLabelTimeZone, showMilliseconds: boolean): string;
5
+ export declare function useDemoXAxisLabelControls(): {
6
+ controlModel: {
7
+ xAxisLabelFormatterEnabled: import("vue").Ref<boolean, boolean>;
8
+ xAxisLabelFormat: import("vue").Ref<DemoXAxisLabelFormat, DemoXAxisLabelFormat>;
9
+ xAxisLabelFormatOptions: ({
10
+ label: string;
11
+ value: "number";
12
+ } | {
13
+ label: string;
14
+ value: "datetime";
15
+ })[];
16
+ xAxisLabelMultiplier: import("vue").Ref<number, number>;
17
+ xAxisLabelFractionDigits: import("vue").Ref<number, number>;
18
+ xAxisLabelTimeZone: import("vue").Ref<DemoXAxisLabelTimeZone, DemoXAxisLabelTimeZone>;
19
+ xAxisLabelTimeZoneOptions: ({
20
+ label: string;
21
+ value: "Asia/Shanghai";
22
+ } | {
23
+ label: string;
24
+ value: "local";
25
+ } | {
26
+ label: string;
27
+ value: "UTC";
28
+ })[];
29
+ xAxisLabelShowMilliseconds: import("vue").Ref<boolean, boolean>;
30
+ };
31
+ xAxisLabelFormatter: import("vue").ComputedRef<WaveformXAxisLabelFormatter | undefined>;
32
+ };
@@ -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, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
6
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, 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';
@@ -66,6 +66,13 @@ export interface WaveformRenderingOptions {
66
66
  /** Minimum horizontal CSS-pixel spacing between rendered error bars. Use 0 to disable. */
67
67
  errorBarMinSpacing?: number;
68
68
  }
69
+ /** Pixel margins reserved above and below the waveform plotting area. */
70
+ export interface WaveformPlotMargin {
71
+ /** Space between the top of the drawing SVG and the plotting area. Defaults to 18. */
72
+ top?: number;
73
+ /** Space between the plotting area and the bottom of the drawing SVG. Defaults to 52. */
74
+ bottom?: number;
75
+ }
69
76
  /** Text styling for the chart-level title. */
70
77
  export interface WaveformTitleTextStyle {
71
78
  color?: string;
@@ -106,10 +113,25 @@ export interface WaveformFrameStyle {
106
113
  borderStyle?: 'solid' | 'dashed' | 'dotted';
107
114
  backgroundColor?: string;
108
115
  }
116
+ export type WaveformXAxisLabelKind = 'tick' | 'start' | 'end';
117
+ /** Context passed to custom X-axis label formatters. */
118
+ export interface WaveformXAxisLabelFormatterContext {
119
+ kind: WaveformXAxisLabelKind;
120
+ /** X coordinate in the source data, before time-unit conversion. */
121
+ rawValue: number;
122
+ timeUnit: 's' | 'ms';
123
+ /** Current visible X domain in source coordinates. */
124
+ domain: [number, number];
125
+ /** Current visible X domain converted to the selected display unit. */
126
+ displayDomain: [number, number];
127
+ }
128
+ export type WaveformXAxisLabelFormatter = (value: number, context: WaveformXAxisLabelFormatterContext) => string;
109
129
  /** Controls axis baseline visibility while preserving tick marks and axis text. */
110
130
  export interface WaveformAxesOptions {
111
131
  x?: {
112
132
  lineVisible?: boolean;
133
+ /** Formats display-unit X values for ticks and visible-range endpoints. */
134
+ labelFormatter?: WaveformXAxisLabelFormatter;
113
135
  };
114
136
  y?: {
115
137
  lineVisible?: boolean;
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * 类型定义统一导出
3
3
  */
4
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
4
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, 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';
@@ -1,3 +1,4 @@
1
+ import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelKind } from '../types';
1
2
  /**
2
3
  * 时间单位类型
3
4
  */
@@ -33,6 +34,8 @@ export declare function formatPlainNumber(value: number): string;
33
34
  * @returns 转换后的显示值
34
35
  */
35
36
  export declare function displayTime(value: number, timeUnit: TimeUnit): number;
37
+ /** Format an X-axis value without changing its source coordinate. */
38
+ export declare function formatXAxisLabel(rawValue: number, domain: [number, number], timeUnit: TimeUnit, kind: WaveformXAxisLabelKind, formatter?: WaveformXAxisLabelFormatter): string;
36
39
  /**
37
40
  * 计算端点标签的小数位数
38
41
  * @param domain 数据域 [最小值, 最大值]
@@ -41,7 +44,7 @@ export declare function displayTime(value: number, timeUnit: TimeUnit): number;
41
44
  */
42
45
  export declare function endpointFractionDigits(domain: [number, number], timeUnit: TimeUnit): number;
43
46
  /**
44
- * 将 X 轴端点时间格式化为当前显示单位下的普通整数
47
+ * 将 X 轴端点时间格式化为当前显示单位下的普通十进制文本
45
48
  * @param value 时间值(秒)
46
49
  * @param _domain 数据域(为保持现有调用签名而保留)
47
50
  * @param timeUnit 时间单位
@@ -49,7 +52,7 @@ export declare function endpointFractionDigits(domain: [number, number], timeUni
49
52
  */
50
53
  export declare function formatEndpointTime(value: number, _domain: [number, number], timeUnit: TimeUnit): string;
51
54
  /**
52
- * 将 X 轴时间刻度格式化为当前显示单位下的普通整数
55
+ * 将 X 轴时间刻度格式化为当前显示单位下的普通十进制文本
53
56
  * @param value 时间值(秒)
54
57
  * @param timeUnit 时间单位
55
58
  * @param _domain 数据域(为保持现有调用签名而保留)
@@ -2,6 +2,6 @@
2
2
  * 工具函数模块统一导出
3
3
  */
4
4
  export { paddedDomain, buildMinorTicks } from './domain';
5
- export { displayTime, endpointFractionDigits, formatEndpointTime, formatAxisTime, formatTooltipTime, formatAnnotationTime, formatPlainNumber, formatScientificAxisLabel, formatScientificAxisExponent, formatScientificYAxisLabel, formatTooltipNumber, resolveScientificAxisExponent, shouldUseScientificAxisLabel, shouldUseScientificYAxisLabel, type ScientificAxisLabelOptions, type ScientificYAxisLabelOptions, type TimeUnit, } from './formatters';
5
+ export { displayTime, endpointFractionDigits, formatEndpointTime, formatAxisTime, formatXAxisLabel, formatTooltipTime, formatAnnotationTime, formatPlainNumber, formatScientificAxisLabel, formatScientificAxisExponent, formatScientificYAxisLabel, formatTooltipNumber, resolveScientificAxisExponent, shouldUseScientificAxisLabel, shouldUseScientificYAxisLabel, type ScientificAxisLabelOptions, type ScientificYAxisLabelOptions, type TimeUnit, } from './formatters';
6
6
  export { resolveTrackGeometry, clamp, type TrackGeometry } from './geometry';
7
7
  export { downsampleLTTB, downsampleMinMax, adaptiveSampling, calculateSamplingThreshold, } from './sampling';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.28",
3
+ "version": "0.1.30",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
package/src/App.vue CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  type WaveformLegendOrientation,
15
15
  type WaveformLegendPosition,
16
16
  type WaveformOverlayMode,
17
+ type WaveformPlotMargin,
17
18
  type WaveformTitleOptions,
18
19
  type WaveformZoomEndPayload,
19
20
  type WaveformZeroLineOptions,
@@ -23,6 +24,7 @@ import { createSimulatedWaveformData } from './data/simulatedWaveforms'
23
24
  import DemoChartHost from './demo/DemoChartHost.vue'
24
25
  import DemoControlPanel from './demo/DemoControlPanel.vue'
25
26
  import type { DemoChartModel, DemoControlPanelModel } from './demo/types'
27
+ import { useDemoXAxisLabelControls } from './demo/useDemoXAxisLabelControls'
26
28
 
27
29
  const fullChartData = createSimulatedWaveformData()
28
30
  const displayMode = ref<WaveformDisplayMode>('independent')
@@ -40,11 +42,14 @@ const verticalGridVisible = ref(true)
40
42
  const verticalGridColor = ref('#dfe5ef')
41
43
  const xAxisLineVisible = ref(false)
42
44
  const yAxisLineVisible = ref(false)
45
+ const { controlModel: xAxisLabelControlModel, xAxisLabelFormatter } = useDemoXAxisLabelControls()
43
46
  const annotations = ref<WaveformAnnotation[]>([])
44
47
  const annotationsVisible = ref(true)
45
48
  const cleanView = ref(false)
46
49
  const presentationMode = ref(false)
47
50
  const showTooltip = ref(true)
51
+ const plotMarginTop = ref(18)
52
+ const plotMarginBottom = ref(52)
48
53
  const zeroLineVisible = ref(false)
49
54
  const zeroLineColor = ref('#98a2b3')
50
55
  const zeroLineWidth = ref(1)
@@ -112,7 +117,10 @@ const frameStyle = computed<WaveformFrameStyle>(() => ({
112
117
  backgroundColor: frameBackgroundColor.value,
113
118
  }))
114
119
  const axes = computed<WaveformAxesOptions>(() => ({
115
- x: { lineVisible: xAxisLineVisible.value },
120
+ x: {
121
+ lineVisible: xAxisLineVisible.value,
122
+ ...(xAxisLabelFormatter.value ? { labelFormatter: xAxisLabelFormatter.value } : {}),
123
+ },
116
124
  y: { lineVisible: yAxisLineVisible.value },
117
125
  }))
118
126
  const zeroLine = computed<WaveformZeroLineOptions>(() => ({
@@ -121,6 +129,10 @@ const zeroLine = computed<WaveformZeroLineOptions>(() => ({
121
129
  width: zeroLineWidth.value,
122
130
  dash: zeroLineDash.value,
123
131
  }))
132
+ const plotMargin = computed<WaveformPlotMargin>(() => ({
133
+ top: plotMarginTop.value,
134
+ bottom: plotMarginBottom.value,
135
+ }))
124
136
 
125
137
  const initialXValues = normalizeWaveformSeries(fullChartData).flatMap((series) =>
126
138
  series.points.map((point) => point.x),
@@ -289,6 +301,8 @@ const controlPanelModel = reactive({
289
301
  displayMode,
290
302
  overlayMode,
291
303
  showTooltip,
304
+ plotMarginTop,
305
+ plotMarginBottom,
292
306
  cleanView,
293
307
  presentationMode,
294
308
  selectedSeriesId,
@@ -308,6 +322,7 @@ const controlPanelModel = reactive({
308
322
  verticalGridColor,
309
323
  xAxisLineVisible,
310
324
  yAxisLineVisible,
325
+ ...xAxisLabelControlModel,
311
326
  frameBorderColor,
312
327
  frameBackgroundColor,
313
328
  frameBorderWidth,
@@ -354,6 +369,7 @@ const chartModel = reactive({
354
369
  cleanView,
355
370
  presentationMode,
356
371
  showTooltip,
372
+ plotMargin,
357
373
  zeroLine,
358
374
  frameWatermarkVisible,
359
375
  annotations,
@@ -23,6 +23,7 @@ const props = withDefaults(defineProps<WaveformChartProps>(), {
23
23
  interactionMode: undefined,
24
24
  grid: () => ({ rowCount: 2, columnCount: 1, showPagination: true }),
25
25
  rendering: () => ({}),
26
+ plotMargin: () => ({}),
26
27
  legend: () => ({ position: 'top-right', orientation: 'auto' }),
27
28
  defaultHiddenSeriesIds: () => [],
28
29
  cleanView: false,
@@ -19,6 +19,7 @@ const {
19
19
  overlayMode,
20
20
  resolvedChartLeftMargin,
21
21
  titleAreaHeight,
22
+ resolvedPlotMargin,
22
23
  pointerInsideChart,
23
24
  handleNativeContextMenu,
24
25
  titleAreaReserved,
@@ -118,6 +119,8 @@ const {
118
119
  :data-presentation-mode="isPresentationMode"
119
120
  :data-overlay-mode="overlayMode"
120
121
  :data-chart-left-margin="resolvedChartLeftMargin"
122
+ :data-plot-margin-top="resolvedPlotMargin.top"
123
+ :data-plot-margin-bottom="resolvedPlotMargin.bottom"
121
124
  :data-title-area-height="titleAreaHeight"
122
125
  @pointerenter="pointerInsideChart = true"
123
126
  @pointerleave="pointerInsideChart = false"
@@ -287,18 +290,18 @@ const {
287
290
  />
288
291
  </g>
289
292
  </g>
290
-
291
- <text
292
- v-if="resolvedXLabel && !isCleanView"
293
- class="waveform-chart__label waveform-chart__x-label"
294
- :x="innerWidth / 2"
295
- :y="xAxisTitleY"
296
- text-anchor="middle"
297
- >
298
- {{ resolvedXLabel }}
299
- </text>
300
293
  </g>
301
294
 
295
+ <text
296
+ v-if="resolvedXLabel && !isCleanView"
297
+ class="waveform-chart__label waveform-chart__x-label"
298
+ :x="resolvedChartLeftMargin + innerWidth / 2"
299
+ :y="xAxisTitleY"
300
+ text-anchor="middle"
301
+ >
302
+ {{ resolvedXLabel }}
303
+ </text>
304
+
302
305
  <text
303
306
  v-if="hasChartArea && !hasWaveformData"
304
307
  class="waveform-chart__empty"
@@ -7,6 +7,9 @@
7
7
  /** 图表边距 */
8
8
  export const margin = { top: 18, right: 24, bottom: 52, left: 64 }
9
9
 
10
+ /** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
11
+ export const X_AXIS_TITLE_BOTTOM_OFFSET = 12
12
+
10
13
  /**
11
14
  * 图表最小高度(像素)
12
15
  */
@@ -12,8 +12,13 @@ import {
12
12
  selectSeriesRenderPoints,
13
13
  type ResolvedWaveformRenderingOptions,
14
14
  } from '../../core/rendering'
15
- import type { WaveformDisplayMode, WaveformOverlayMode, WaveformPoint } from '../../types'
16
- import { buildMinorTicks, formatEndpointTime } from '../../utils'
15
+ import type {
16
+ WaveformDisplayMode,
17
+ WaveformOverlayMode,
18
+ WaveformPoint,
19
+ WaveformXAxisLabelFormatter,
20
+ } from '../../types'
21
+ import { buildMinorTicks, formatXAxisLabel } from '../../utils'
17
22
  import {
18
23
  getBottomRowCellIndexes,
19
24
  type GridCellGeometry,
@@ -45,6 +50,7 @@ export interface BuildTrackLayoutsOptions {
45
50
  fixedYDomains?: Record<string, [number, number]>
46
51
  yDomains?: Record<string, [number, number]>
47
52
  timeUnit: 's' | 'ms'
53
+ xAxisLabelFormatter?: WaveformXAxisLabelFormatter
48
54
  rendering: ResolvedWaveformRenderingOptions
49
55
  hideSecondaryLabels: boolean
50
56
  yAxisLabelX: number
@@ -147,8 +153,20 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
147
153
  const yAxisTickValues = yAxes[0]?.tickValues ?? []
148
154
  const domain = xScale.domain() as [number, number]
149
155
  const endpointLabels = {
150
- start: formatEndpointTime(domain[0], domain, options.timeUnit),
151
- end: formatEndpointTime(domain[1], domain, options.timeUnit),
156
+ start: formatXAxisLabel(
157
+ domain[0],
158
+ domain,
159
+ options.timeUnit,
160
+ 'start',
161
+ options.xAxisLabelFormatter,
162
+ ),
163
+ end: formatXAxisLabel(
164
+ domain[1],
165
+ domain,
166
+ options.timeUnit,
167
+ 'end',
168
+ options.xAxisLabelFormatter,
169
+ ),
152
170
  }
153
171
  const leftClearance = endpointLabels.start.length * 7 + 10
154
172
  const rightClearance = endpointLabels.end.length * 7 + 10
@@ -24,7 +24,6 @@ import {
24
24
  normalizeGridOptions,
25
25
  paginateSeries,
26
26
  resolveGridCellGeometry,
27
- X_AXIS_BAND,
28
27
  } from './grid'
29
28
  import {
30
29
  buildTrackLayouts,
@@ -285,6 +284,7 @@ export function useWaveformLayout(context: LayoutContext) {
285
284
  )
286
285
  : sharedYDomains.value,
287
286
  timeUnit: props.timeUnit,
287
+ xAxisLabelFormatter: props.axes?.x?.labelFormatter,
288
288
  rendering: renderingOptions.value,
289
289
  hideSecondaryLabels: isCleanView.value || yAxisLayout.value.hideSecondaryLabels,
290
290
  yAxisLabelX: yAxisMetrics.value.labelCenterX,
@@ -315,7 +315,6 @@ export function useWaveformLayout(context: LayoutContext) {
315
315
  )
316
316
  : [],
317
317
  )
318
- const xAxisTitleY = computed(() => innerHeight.value + X_AXIS_BAND + 10)
319
318
  const editorSeries = computed<AnnotationSeriesInfo | undefined>(() => {
320
319
  const seriesId = annotationInteraction.editorDraft.value?.annotation.seriesId
321
320
  const series = chartSeries.value.find((item) => item.id === seriesId)
@@ -359,7 +358,6 @@ export function useWaveformLayout(context: LayoutContext) {
359
358
  resolveSeriesYScale,
360
359
  annotationTrackLayouts,
361
360
  renderedAnnotations,
362
- xAxisTitleY,
363
361
  editorSeries,
364
362
  resolveFrameNumber,
365
363
  }
@@ -7,6 +7,7 @@ import {
7
7
  TITLE_CHAR_WIDTH_RATIO,
8
8
  TITLE_DEFAULT_FONT_SIZE,
9
9
  TITLE_LINE_HEIGHT,
10
+ X_AXIS_TITLE_BOTTOM_OFFSET,
10
11
  ZERO_LINE_DEFAULTS,
11
12
  } from './constants'
12
13
  import { calculateRotatedTitleLayout, TITLE_AREA_HORIZONTAL_PADDING } from './title'
@@ -144,11 +145,23 @@ export function useWaveformPresentation(context: PresentationContext) {
144
145
  const titleAreaHeight = computed(() =>
145
146
  titleAreaReserved.value ? titleLayout.value.areaHeight : 0,
146
147
  )
147
- const chartTopMargin = computed(() => margin.top)
148
+ const resolvePlotMargin = (value: number | undefined, fallback: number) =>
149
+ typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback
150
+ const resolvedPlotMargin = computed(() => ({
151
+ top: resolvePlotMargin(props.plotMargin.top, margin.top),
152
+ bottom: resolvePlotMargin(props.plotMargin.bottom, margin.bottom),
153
+ }))
154
+ const chartTopMargin = computed(() => resolvedPlotMargin.value.top)
148
155
  const drawingHeight = computed(() =>
149
156
  Math.max(0, chartHeight.value - titleAreaHeight.value - paginationBandHeight.value),
150
157
  )
151
- const innerHeight = computed(() => Math.max(0, drawingHeight.value - margin.top - margin.bottom))
158
+ const xAxisTitleY = computed(() => Math.max(0, drawingHeight.value - X_AXIS_TITLE_BOTTOM_OFFSET))
159
+ const innerHeight = computed(() =>
160
+ Math.max(
161
+ 0,
162
+ drawingHeight.value - resolvedPlotMargin.value.top - resolvedPlotMargin.value.bottom,
163
+ ),
164
+ )
152
165
  const titleAreaStyle = computed<CSSProperties>(() => ({
153
166
  height: `${titleAreaHeight.value}px`,
154
167
  justifyContent:
@@ -193,8 +206,10 @@ export function useWaveformPresentation(context: PresentationContext) {
193
206
  titleMeasureStyle,
194
207
  titleLayout,
195
208
  titleAreaHeight,
209
+ resolvedPlotMargin,
196
210
  chartTopMargin,
197
211
  drawingHeight,
212
+ xAxisTitleY,
198
213
  innerHeight,
199
214
  titleAreaStyle,
200
215
  titleVisualStyle,
@@ -7,6 +7,7 @@ import type {
7
7
  WaveformInteractionMode,
8
8
  WaveformLegendOptions,
9
9
  WaveformOverlayMode,
10
+ WaveformPlotMargin,
10
11
  WaveformPoint,
11
12
  WaveformRenderingOptions,
12
13
  WaveformTitleOptions,
@@ -42,6 +43,7 @@ export interface WaveformChartProps {
42
43
  interactionMode?: WaveformInteractionMode
43
44
  grid?: WaveformGridOptions
44
45
  rendering?: WaveformRenderingOptions
46
+ plotMargin?: WaveformPlotMargin
45
47
  title?: WaveformTitleOptions
46
48
  legend?: WaveformLegendOptions
47
49
  hiddenSeriesIds?: string[]
@@ -65,6 +67,7 @@ type DefaultedProp =
65
67
  | 'annotationsVisible'
66
68
  | 'grid'
67
69
  | 'rendering'
70
+ | 'plotMargin'
68
71
  | 'legend'
69
72
  | 'defaultHiddenSeriesIds'
70
73
  | 'cleanView'
@@ -13,6 +13,7 @@ export type {
13
13
  WaveformAnnotationStyle,
14
14
  WaveformAnnotation,
15
15
  WaveformRenderingOptions,
16
+ WaveformPlotMargin,
16
17
  WaveformTitleTextStyle,
17
18
  WaveformTitleOptions,
18
19
  WaveformLegendPosition,
@@ -11,6 +11,7 @@ export type {
11
11
  WaveformAnnotationStyle,
12
12
  WaveformAnnotation,
13
13
  WaveformRenderingOptions,
14
+ WaveformPlotMargin,
14
15
  WaveformTitleTextStyle,
15
16
  WaveformTitleOptions,
16
17
  WaveformLegendPosition,