waveform-analysis 0.1.65 → 0.1.67

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 (81) hide show
  1. package/README.md +146 -5
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1326 -1118
  4. package/dist/style.css +1 -1
  5. package/dist/types/components/WaveformChart.vue.d.ts +7 -2
  6. package/dist/types/components/core/constants.d.ts +2 -0
  7. package/dist/types/components/core/grid.d.ts +1 -1
  8. package/dist/types/components/core/layout.d.ts +2 -2
  9. package/dist/types/components/core/pagination.d.ts +2 -0
  10. package/dist/types/components/core/title.d.ts +2 -1
  11. package/dist/types/components/core/trackLayoutBuilder.d.ts +5 -0
  12. package/dist/types/components/core/types.d.ts +2 -1
  13. package/dist/types/components/core/useWaveformChartController.d.ts +9 -0
  14. package/dist/types/components/core/useWaveformLayout.d.ts +1 -0
  15. package/dist/types/components/core/useWaveformPresentation.d.ts +3 -0
  16. package/dist/types/components/core/useWaveformYAxisLayout.d.ts +31 -0
  17. package/dist/types/components/core/waveformChartTypes.d.ts +11 -2
  18. package/dist/types/components/core/yAxisLayoutMetrics.d.ts +1 -1
  19. package/dist/types/components/core/yAxisTitleLayout.d.ts +4 -0
  20. package/dist/types/components/data/types.d.ts +1 -1
  21. package/dist/types/components/interaction/integerZoom.d.ts +12 -0
  22. package/dist/types/components/interaction/interactionTarget.d.ts +1 -0
  23. package/dist/types/components/interaction/remoteViewport.d.ts +8 -0
  24. package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -2
  25. package/dist/types/components/interaction/viewportDomain.d.ts +1 -1
  26. package/dist/types/components/interaction/viewportPan.d.ts +4 -0
  27. package/dist/types/components/interaction/zoomConstraints.d.ts +2 -1
  28. package/dist/types/components/rendering/WaveformLegend.vue.d.ts +2 -0
  29. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +6 -6
  30. package/dist/types/components/rendering/WaveformTrackAxes.vue.d.ts +5 -1
  31. package/dist/types/components/rendering/WaveformTrackBackdrop.vue.d.ts +0 -4
  32. package/dist/types/components/rendering/WaveformZeroLines.vue.d.ts +12 -0
  33. package/dist/types/data/recordedDampedWaveform.d.ts +1 -0
  34. package/dist/types/demo/types.d.ts +3 -0
  35. package/dist/types/index.d.ts +2 -1
  36. package/dist/types/types/chart.d.ts +17 -0
  37. package/dist/types/types/index.d.ts +1 -1
  38. package/package.json +1 -1
  39. package/src/App.vue +6 -0
  40. package/src/components/WaveformChart.css +5 -0
  41. package/src/components/WaveformChart.vue +3 -0
  42. package/src/components/WaveformChartView.vue +14 -3
  43. package/src/components/core/constants.ts +2 -0
  44. package/src/components/core/grid.ts +4 -1
  45. package/src/components/core/layout.ts +16 -2
  46. package/src/components/core/pagination.ts +17 -0
  47. package/src/components/core/title.ts +7 -3
  48. package/src/components/core/trackLayoutBuilder.ts +39 -59
  49. package/src/components/core/types.ts +2 -0
  50. package/src/components/core/useWaveformChartController.ts +17 -1
  51. package/src/components/core/useWaveformChartLifecycle.ts +11 -17
  52. package/src/components/core/useWaveformLayout.ts +67 -107
  53. package/src/components/core/useWaveformPresentation.ts +27 -3
  54. package/src/components/core/useWaveformYAxisLayout.ts +140 -0
  55. package/src/components/core/waveformChartTypes.ts +15 -0
  56. package/src/components/core/yAxisLayoutMetrics.ts +11 -2
  57. package/src/components/core/yAxisTitleLayout.ts +78 -0
  58. package/src/components/data/types.ts +3 -0
  59. package/src/components/interaction/integerZoom.ts +64 -0
  60. package/src/components/interaction/interactionTarget.ts +10 -0
  61. package/src/components/interaction/remoteViewport.ts +53 -0
  62. package/src/components/interaction/useWaveformViewport.ts +16 -58
  63. package/src/components/interaction/useWaveformZoom.ts +13 -11
  64. package/src/components/interaction/viewportDomain.ts +11 -3
  65. package/src/components/interaction/viewportPan.ts +82 -0
  66. package/src/components/interaction/zoomConstraints.ts +7 -5
  67. package/src/components/rendering/WaveformLegend.vue +10 -1
  68. package/src/components/rendering/WaveformTrack.vue +24 -6
  69. package/src/components/rendering/WaveformTrackAxes.vue +16 -5
  70. package/src/components/rendering/WaveformTrackBackdrop.vue +2 -34
  71. package/src/components/rendering/WaveformZeroLines.vue +53 -0
  72. package/src/data/recordedDampedWaveform.ts +3 -0
  73. package/src/data/simulatedWaveforms.ts +12 -10
  74. package/src/demo/DemoChartHost.vue +1 -0
  75. package/src/demo/DemoGridFrameControls.vue +12 -0
  76. package/src/demo/DemoViewControls.vue +4 -0
  77. package/src/demo/types.ts +3 -0
  78. package/src/index.ts +4 -0
  79. package/src/styles.css +3 -3
  80. package/src/types/chart.ts +20 -0
  81. package/src/types/index.ts +3 -0
@@ -1,12 +1,13 @@
1
1
  import { type ZoomTransform } from 'd3';
2
2
  import type { WaveformPoint } from '../../types';
3
3
  import { type WaveformPointSource } from '../../core/waveformPointSource';
4
+ import { type IntegerZoomOptions } from './integerZoom';
4
5
  interface PointSeriesSource {
5
6
  points: WaveformPoint[];
6
7
  source?: WaveformPointSource;
7
8
  }
8
9
  export type ZoomSeriesGroup = readonly PointSeriesSource[];
9
- interface ZoomConstraintOptions {
10
+ interface ZoomConstraintOptions extends IntegerZoomOptions {
10
11
  minZoomSpan?: number;
11
12
  minVisiblePoints?: number;
12
13
  maxZoomScale?: number | null;
@@ -8,6 +8,7 @@ interface Props {
8
8
  width: number;
9
9
  height: number;
10
10
  interactive?: boolean;
11
+ showUnits?: boolean;
11
12
  hiddenSeriesIds?: string[];
12
13
  }
13
14
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
@@ -17,6 +18,7 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
17
18
  }>, {
18
19
  interactive: boolean;
19
20
  hiddenSeriesIds: string[];
21
+ showUnits: boolean;
20
22
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
23
  declare const _default: typeof __VLS_export;
22
24
  export default _default;
@@ -14,10 +14,11 @@ interface Props {
14
14
  axes?: WaveformAxesOptions;
15
15
  timeUnit: 's' | 'ms';
16
16
  yLabel?: string;
17
+ showAxisUnits?: boolean;
18
+ containYAxisEndpoints?: boolean;
19
+ containFrameStroke?: boolean;
17
20
  cleanView?: boolean;
18
- zeroLine?: Required<Pick<WaveformZeroLineOptions, 'color' | 'width' | 'dash'>> & {
19
- visible: boolean;
20
- };
21
+ zeroLine?: Required<WaveformZeroLineOptions>;
21
22
  }
22
23
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
23
24
  contextmenu: (mouseEvent: MouseEvent) => any;
@@ -38,10 +39,9 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
38
39
  }>, {
39
40
  interactive: boolean;
40
41
  cleanView: boolean;
41
- zeroLine: Required<Pick<WaveformZeroLineOptions, "color" | "width" | "dash">> & {
42
- visible: boolean;
43
- };
42
+ zeroLine: Required<WaveformZeroLineOptions>;
44
43
  interactionMode: WaveformInteractionMode;
44
+ showAxisUnits: boolean;
45
45
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
46
46
  declare const _default: typeof __VLS_export;
47
47
  export default _default;
@@ -7,7 +7,11 @@ interface Props {
7
7
  axes?: WaveformAxesOptions;
8
8
  timeUnit: 's' | 'ms';
9
9
  yLabel?: string;
10
+ showUnits?: boolean;
11
+ containYAxisEndpoints?: boolean;
10
12
  }
11
- declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
13
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
14
+ showUnits: boolean;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
16
  declare const _default: typeof __VLS_export;
13
17
  export default _default;
@@ -1,4 +1,3 @@
1
- import type { WaveformZeroLineOptions } from '../../types';
2
1
  import type { TrackLayout } from '../core/types';
3
2
  interface Props {
4
3
  track: TrackLayout;
@@ -9,9 +8,6 @@ interface Props {
9
8
  frameStyle: {
10
9
  backgroundColor: string;
11
10
  };
12
- zeroLine: Required<Pick<WaveformZeroLineOptions, 'color' | 'width' | 'dash'>> & {
13
- visible: boolean;
14
- };
15
11
  }
16
12
  declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
13
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,12 @@
1
+ import type { WaveformZeroLineOptions } from '../../types';
2
+ import type { TrackLayout } from '../core/types';
3
+ type __VLS_Props = {
4
+ track: TrackLayout;
5
+ clipPathId: string;
6
+ innerWidth: number;
7
+ cleanView: boolean;
8
+ zeroLine: Required<WaveformZeroLineOptions>;
9
+ };
10
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
+ declare const _default: typeof __VLS_export;
12
+ export default _default;
@@ -0,0 +1 @@
1
+ export declare const recordedDampedPoints: number[][];
@@ -9,6 +9,7 @@ export interface DemoControlPanelModel {
9
9
  displayMode: WaveformDisplayMode;
10
10
  overlayMode: WaveformOverlayMode;
11
11
  showTooltip: boolean;
12
+ integerZoom: boolean;
12
13
  plotMarginTop: number;
13
14
  plotMarginBottom: number;
14
15
  cleanView: boolean;
@@ -20,6 +21,7 @@ export interface DemoControlPanelModel {
20
21
  zeroLineVisible: boolean;
21
22
  zeroLineColor: string;
22
23
  zeroLineWidth: number;
24
+ zeroLineOpacity: number;
23
25
  zeroLineDash: string;
24
26
  zeroLineDashOptions: Array<SelectOption<string>>;
25
27
  rowCount: number;
@@ -82,6 +84,7 @@ export interface DemoChartModel {
82
84
  cleanView: boolean;
83
85
  presentationMode: boolean;
84
86
  showTooltip: boolean;
87
+ integerZoom: boolean;
85
88
  plotMargin: WaveformPlotMargin;
86
89
  zeroLine: WaveformZeroLineOptions;
87
90
  frameWatermarkVisible: boolean;
@@ -3,7 +3,8 @@
3
3
  * @packageDocumentation
4
4
  */
5
5
  export { default as WaveformChart } from './components/WaveformChart.vue';
6
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
6
+ export type { WaveformChartProps } from './components/core/waveformChartTypes';
7
+ 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, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
7
8
  export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './components/core/grid';
8
9
  export { normalizeWaveformData, normalizeWaveformSeries } from './core';
9
10
  export { paddedDomain, buildMinorTicks, displayTime, formatEndpointTime, formatAxisTime, formatTooltipTime, resolveTrackGeometry, clamp, type TimeUnit, type TrackGeometry, } from './utils';
@@ -11,6 +11,10 @@ export interface WaveformPoint {
11
11
  /** Error above Y; overrides `error` for the upper side. */
12
12
  upperError?: number;
13
13
  }
14
+ /** Optional compact title and time-label placement at the card edges. */
15
+ export type WaveformLayoutPreset = 'default' | 'edge-compact';
16
+ /** Controls unit placement without changing series metadata. */
17
+ export type WaveformUnitDisplayMode = 'axis' | 'legend-single-series';
14
18
  /**
15
19
  * 显示模式
16
20
  * - independent: 每个波形独立 Y 轴和缩放
@@ -44,6 +48,13 @@ export interface WaveformZoomEndPayload {
44
48
  seriesIds?: string[];
45
49
  gesture?: 'wheel' | 'box';
46
50
  }
51
+ /** Final horizontal viewport after a pan; endpoints are always seconds. */
52
+ export interface WaveformPanEndPayload {
53
+ start: number;
54
+ end: number;
55
+ trackIndex?: number;
56
+ seriesIds?: string[];
57
+ }
47
58
  /** Describes the X-axis viewport targeted by a user zoom gesture before rendering settles. */
48
59
  export interface WaveformZoomIntentPayload {
49
60
  start: number;
@@ -221,6 +232,8 @@ export interface WaveformAxesOptions {
221
232
  splitNumber?: number;
222
233
  /** Expands Y-axis domains to equal, human-friendly intervals. Defaults to true. */
223
234
  nice?: boolean;
235
+ /** Adds this fraction of the automatic Y span above its maximum. Defaults to 0. */
236
+ upperPaddingRatio?: number;
224
237
  };
225
238
  }
226
239
  /** Styling and visibility options for the horizontal zero-value reference line. */
@@ -233,4 +246,8 @@ export interface WaveformZeroLineOptions {
233
246
  width?: number;
234
247
  /** SVG stroke-dasharray. Defaults to '6 4'; use '' for a solid line. */
235
248
  dash?: string;
249
+ /** Stroke opacity from 0 to 1. Defaults to 0.5; multiplies the color's alpha. */
250
+ opacity?: number;
251
+ /** Hide within this fraction of a Y-axis tick interval from either boundary. Defaults to 0.02. */
252
+ boundaryThreshold?: number;
236
253
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * 类型定义统一导出
3
3
  */
4
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
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, WaveformZeroLineOptions, } from './chart';
5
5
  export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
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
@@ -48,11 +48,13 @@ const annotationsVisible = ref(true)
48
48
  const cleanView = ref(false)
49
49
  const presentationMode = ref(false)
50
50
  const showTooltip = ref(true)
51
+ const integerZoom = ref(true)
51
52
  const plotMarginTop = ref(18)
52
53
  const plotMarginBottom = ref(52)
53
54
  const zeroLineVisible = ref(true)
54
55
  const zeroLineColor = ref('#ff0000')
55
56
  const zeroLineWidth = ref(1)
57
+ const zeroLineOpacity = ref(0.5)
56
58
  const zeroLineDash = ref('6 4')
57
59
  const interactionMode = ref<WaveformInteractionMode>('zoom')
58
60
  const legendPosition = ref<WaveformLegendPosition>('top-right')
@@ -127,6 +129,7 @@ const zeroLine = computed<WaveformZeroLineOptions>(() => ({
127
129
  visible: zeroLineVisible.value,
128
130
  color: zeroLineColor.value,
129
131
  width: zeroLineWidth.value,
132
+ opacity: zeroLineOpacity.value,
130
133
  dash: zeroLineDash.value,
131
134
  }))
132
135
  const plotMargin = computed<WaveformPlotMargin>(() => ({
@@ -298,6 +301,7 @@ const controlPanelModel = reactive({
298
301
  displayMode,
299
302
  overlayMode,
300
303
  showTooltip,
304
+ integerZoom,
301
305
  plotMarginTop,
302
306
  plotMarginBottom,
303
307
  cleanView,
@@ -309,6 +313,7 @@ const controlPanelModel = reactive({
309
313
  zeroLineVisible,
310
314
  zeroLineColor,
311
315
  zeroLineWidth,
316
+ zeroLineOpacity,
312
317
  zeroLineDash,
313
318
  zeroLineDashOptions,
314
319
  rowCount,
@@ -365,6 +370,7 @@ const chartModel = reactive({
365
370
  cleanView,
366
371
  presentationMode,
367
372
  showTooltip,
373
+ integerZoom,
368
374
  plotMargin,
369
375
  zeroLine,
370
376
  frameWatermarkVisible,
@@ -117,6 +117,11 @@
117
117
  font-size: 12px;
118
118
  }
119
119
 
120
+ .waveform-chart--edge-compact .waveform-chart__x-label {
121
+ font-size: 18px;
122
+ font-weight: 700;
123
+ }
124
+
120
125
  .waveform-chart__label {
121
126
  font-weight: 700;
122
127
  }
@@ -10,10 +10,13 @@ import WaveformChartView from './WaveformChartView.vue'
10
10
  const props = withDefaults(defineProps<WaveformChartProps>(), {
11
11
  displayMode: 'independent',
12
12
  overlayMode: 'single-axis',
13
+ unitDisplayMode: 'axis',
14
+ layoutPreset: 'default',
13
15
  yLabel: '幅值',
14
16
  lineColor: '#0960bd',
15
17
  showTooltip: true,
16
18
  zoomable: true,
19
+ integerZoom: false,
17
20
  pannable: false,
18
21
  minVisiblePoints: 0,
19
22
  xDomainStrategy: () => ({ type: 'integer-ms' }),
@@ -13,10 +13,14 @@ const {
13
13
  displayMode,
14
14
  activeInteractionMode,
15
15
  isCleanView,
16
+ isEdgeCompact,
17
+ paginationVisible,
16
18
  isPresentationMode,
17
19
  selection,
18
20
  containerStyle,
19
21
  overlayMode,
22
+ unitDisplayMode,
23
+ showLegendUnits,
20
24
  resolvedChartLeftMargin,
21
25
  titleAreaHeight,
22
26
  resolvedPlotMargin,
@@ -77,7 +81,6 @@ const {
77
81
  xAxisTitleY,
78
82
  hasChartArea,
79
83
  gridOptions,
80
- pageCount,
81
84
  currentPage,
82
85
  getPageSize,
83
86
  pageableTracks,
@@ -116,6 +119,7 @@ function handleChartPointerLeave() {
116
119
  `waveform-chart--interaction-${activeInteractionMode}`,
117
120
  {
118
121
  'waveform-chart--clean': isCleanView,
122
+ 'waveform-chart--edge-compact': isEdgeCompact,
119
123
  'waveform-chart--presentation': isPresentationMode,
120
124
  'waveform-chart--panning': selection?.kind === 'pan',
121
125
  },
@@ -233,6 +237,9 @@ function handleChartPointerLeave() {
233
237
  :frame-number="resolveFrameNumber(track.id)"
234
238
  :frame-style="frameStyle"
235
239
  :axes="axes"
240
+ :show-axis-units="unitDisplayMode === 'axis'"
241
+ :contain-y-axis-endpoints="isEdgeCompact"
242
+ :contain-frame-stroke="isEdgeCompact"
236
243
  :clean-view="isCleanView"
237
244
  :zero-line="resolvedZeroLine"
238
245
  :time-unit="timeUnit"
@@ -284,8 +291,12 @@ function handleChartPointerLeave() {
284
291
  :transform="`translate(${track.left}, ${track.top})`"
285
292
  >
286
293
  <WaveformLegend
287
- v-if="!track.isEmpty && track.legendSeries.length > 1"
294
+ v-if="
295
+ !track.isEmpty &&
296
+ (track.legendSeries.length > 1 || unitDisplayMode === 'legend-single-series')
297
+ "
288
298
  :series="track.legendSeries"
299
+ :show-units="showLegendUnits"
289
300
  :position="resolveLegendPosition(track.id)"
290
301
  :orientation="resolveLegendOrientation(resolveLegendPosition(track.id))"
291
302
  :background-color="legendBackgroundColor"
@@ -321,7 +332,7 @@ function handleChartPointerLeave() {
321
332
  </svg>
322
333
 
323
334
  <Pagination
324
- v-if="gridOptions.showPagination && pageCount > 1 && !isCleanView"
335
+ v-if="paginationVisible"
325
336
  class="waveform-chart__pagination"
326
337
  aria-label="波形分页"
327
338
  :current="currentPage"
@@ -182,6 +182,8 @@ export const ZERO_LINE_DEFAULTS = {
182
182
  WIDTH: 1,
183
183
  /** 虚线样式 */
184
184
  DASH: '6 4',
185
+ OPACITY: 0.5,
186
+ BOUNDARY_THRESHOLD: 0.02,
185
187
  }
186
188
 
187
189
  /**
@@ -134,6 +134,7 @@ export function resolveGridCellGeometry(
134
134
  slotHasSeries: boolean[] = [],
135
135
  horizontalGap?: number,
136
136
  showXAxis = true,
137
+ bottomAxisBandInMargin = false,
137
138
  ): GridCellGeometry[] {
138
139
  const pageSize = getPageSize(options)
139
140
  const seriesCount = slotHasSeries.filter(Boolean).length
@@ -164,7 +165,9 @@ export function resolveGridCellGeometry(
164
165
  }
165
166
  }
166
167
  const totalVerticalGap = Math.max(0, rowCount - 1) * defaultGap
167
- const totalAxisBand = axisRows.size * X_AXIS_BAND
168
+ // The final row's ticks may use the chart bottom margin; intermediate bands stay in-grid.
169
+ const bottomBandCredit = bottomAxisBandInMargin && axisRows.has(rowCount - 1) ? X_AXIS_BAND : 0
170
+ const totalAxisBand = axisRows.size * X_AXIS_BAND - bottomBandCredit
168
171
  const plotHeight = Math.max(1, (innerHeight - totalVerticalGap - totalAxisBand) / rowCount)
169
172
 
170
173
  return Array.from({ length: filledLastRow ? seriesCount : pageSize }, (_, slotIndex) => {
@@ -161,10 +161,21 @@ export function resolveRenderedYAxisSeriesGroups(
161
161
  yDomain?: WaveformYDomain,
162
162
  yDomains?: Record<string, WaveformYDomain>,
163
163
  viewportYDomains?: Record<string, [number, number]>,
164
+ upperPaddingRatio = 0,
164
165
  ): YAxisSeriesGroup[] {
165
166
  const viewportYDomain = viewportYDomains?.[track.id]
166
167
  return resolveYAxisSeriesGroups(track, overlayMode, yDomain, yDomains).map((group) =>
167
- !group.fixed && viewportYDomain ? { ...group, domain: viewportYDomain } : group,
168
+ !group.fixed && viewportYDomain
169
+ ? { ...group, domain: viewportYDomain }
170
+ : !group.fixed && Number.isFinite(upperPaddingRatio) && upperPaddingRatio > 0
171
+ ? {
172
+ ...group,
173
+ domain: [
174
+ group.domain[0],
175
+ group.domain[1] + (group.domain[1] - group.domain[0]) * upperPaddingRatio,
176
+ ] as WaveformYDomain,
177
+ }
178
+ : group,
168
179
  )
169
180
  }
170
181
 
@@ -255,6 +266,8 @@ export function buildYAxisSlots(
255
266
  nice = true,
256
267
  includeWithoutLastTick = false,
257
268
  viewportYDomains?: Record<string, [number, number]>,
269
+ upperPaddingRatio = 0,
270
+ showUnits = true,
258
271
  ): { slots: YAxisSlot[]; clearance: { left: number; right: number } } {
259
272
  const widths = new Map<string, number>()
260
273
  tracks.forEach((track) => {
@@ -265,6 +278,7 @@ export function buildYAxisSlots(
265
278
  yDomain,
266
279
  yDomains,
267
280
  viewportYDomains,
281
+ upperPaddingRatio,
268
282
  ).forEach((group) => {
269
283
  const sideIndex = sideIndexes[group.side]++
270
284
  const key = `${group.side}:${sideIndex}`
@@ -272,7 +286,7 @@ export function buildYAxisSlots(
272
286
  group.domain,
273
287
  nice,
274
288
  undefined,
275
- group.seriesList[0]?.unit,
289
+ showUnits ? group.seriesList[0]?.unit : undefined,
276
290
  tickCount,
277
291
  includeWithoutLastTick,
278
292
  ).tickTextWidth
@@ -0,0 +1,17 @@
1
+ /** Conservative Ant pagination sizing, including first/last pages and both ellipses. */
2
+ export function resolveCompactPaginationBand(
3
+ visible: boolean,
4
+ width: number,
5
+ pageCount: number,
6
+ timeLabelCenter: number,
7
+ timeLabel: string,
8
+ ): 0 | 16 | 40 {
9
+ if (!visible) return 0
10
+ if (width <= 520) return 40
11
+ const controls = Math.min(pageCount, 9) + 2
12
+ const paginationWidth = controls * 32 + controls * 8
13
+ // A full 18px em per character also accommodates wide glyphs and Chinese labels.
14
+ const labelRight = timeLabelCenter + (Array.from(timeLabel).length * 18) / 2
15
+ const availableWidth = width - 10 - labelRight - 8
16
+ return availableWidth >= paginationWidth ? 16 : 40
17
+ }
@@ -20,6 +20,7 @@ export interface RotatedTitleLayoutOptions {
20
20
  naturalHeight: number
21
21
  availableWidth: number
22
22
  rotation: number
23
+ edgeCompact?: boolean
23
24
  }
24
25
 
25
26
  function clampPositive(value: number): number {
@@ -31,7 +32,10 @@ export function calculateRotatedTitleLayout({
31
32
  naturalHeight,
32
33
  availableWidth,
33
34
  rotation,
35
+ edgeCompact = false,
34
36
  }: RotatedTitleLayoutOptions): RotatedTitleLayout {
37
+ const verticalPadding = edgeCompact ? 8 : TITLE_AREA_VERTICAL_PADDING
38
+ const minimumHeight = edgeCompact ? 0 : TITLE_AREA_MIN_HEIGHT
35
39
  const safeNaturalWidth = clampPositive(naturalWidth)
36
40
  const safeNaturalHeight = clampPositive(naturalHeight)
37
41
  const safeAvailableWidth = clampPositive(availableWidth)
@@ -53,13 +57,13 @@ export function calculateRotatedTitleLayout({
53
57
  textHeight,
54
58
  visualWidth: textWidth,
55
59
  visualHeight: textHeight,
56
- areaHeight: Math.max(TITLE_AREA_MIN_HEIGHT, textHeight + TITLE_AREA_VERTICAL_PADDING),
60
+ areaHeight: Math.max(minimumHeight, textHeight + verticalPadding),
57
61
  scale: 1,
58
62
  wrapped: lineCount > 1,
59
63
  }
60
64
  }
61
65
 
62
- const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - TITLE_AREA_VERTICAL_PADDING
66
+ const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - verticalPadding
63
67
  const naturalVisualWidth = safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
64
68
  const naturalVisualHeight = safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
65
69
  const scale = Math.min(
@@ -71,7 +75,7 @@ export function calculateRotatedTitleLayout({
71
75
  const visualHeight = naturalVisualHeight * scale
72
76
  const areaHeight = Math.min(
73
77
  TITLE_AREA_MAX_HEIGHT,
74
- Math.max(TITLE_AREA_MIN_HEIGHT, visualHeight + TITLE_AREA_VERTICAL_PADDING),
78
+ Math.max(minimumHeight, visualHeight + verticalPadding),
75
79
  )
76
80
 
77
81
  return {