waveform-analysis 0.1.28 → 0.1.29

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/src/index.ts CHANGED
@@ -23,6 +23,9 @@ export type {
23
23
  WaveformLegendOrientation,
24
24
  WaveformLegendOptions,
25
25
  WaveformFrameStyle,
26
+ WaveformXAxisLabelKind,
27
+ WaveformXAxisLabelFormatterContext,
28
+ WaveformXAxisLabelFormatter,
26
29
  WaveformAxesOptions,
27
30
  WaveformZeroLineOptions,
28
31
  // 数据类型
package/src/styles.css CHANGED
@@ -133,6 +133,14 @@ body {
133
133
  grid-template-columns: minmax(0, 1fr) auto;
134
134
  }
135
135
 
136
+ .x-axis-label-controls {
137
+ display: grid;
138
+ gap: 10px;
139
+ margin-top: 12px;
140
+ padding-top: 12px;
141
+ border-top: 1px solid #eaecf0;
142
+ }
143
+
136
144
  .grid-line-color-picker,
137
145
  .grid-line-color-picker .vc-color-wrap {
138
146
  width: 48px;
@@ -121,10 +121,31 @@ export interface WaveformFrameStyle {
121
121
  backgroundColor?: string
122
122
  }
123
123
 
124
+ export type WaveformXAxisLabelKind = 'tick' | 'start' | 'end'
125
+
126
+ /** Context passed to custom X-axis label formatters. */
127
+ export interface WaveformXAxisLabelFormatterContext {
128
+ kind: WaveformXAxisLabelKind
129
+ /** X coordinate in the source data, before time-unit conversion. */
130
+ rawValue: number
131
+ timeUnit: 's' | 'ms'
132
+ /** Current visible X domain in source coordinates. */
133
+ domain: [number, number]
134
+ /** Current visible X domain converted to the selected display unit. */
135
+ displayDomain: [number, number]
136
+ }
137
+
138
+ export type WaveformXAxisLabelFormatter = (
139
+ value: number,
140
+ context: WaveformXAxisLabelFormatterContext,
141
+ ) => string
142
+
124
143
  /** Controls axis baseline visibility while preserving tick marks and axis text. */
125
144
  export interface WaveformAxesOptions {
126
145
  x?: {
127
146
  lineVisible?: boolean
147
+ /** Formats display-unit X values for ticks and visible-range endpoints. */
148
+ labelFormatter?: WaveformXAxisLabelFormatter
128
149
  }
129
150
  y?: {
130
151
  lineVisible?: boolean
@@ -18,6 +18,9 @@ export type {
18
18
  WaveformLegendOrientation,
19
19
  WaveformLegendOptions,
20
20
  WaveformFrameStyle,
21
+ WaveformXAxisLabelKind,
22
+ WaveformXAxisLabelFormatterContext,
23
+ WaveformXAxisLabelFormatter,
21
24
  WaveformAxesOptions,
22
25
  WaveformZeroLineOptions,
23
26
  } from './chart'
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
 
3
+ import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelFormatterContext } from '../types'
3
4
  import {
4
5
  formatAnnotationTime,
5
6
  formatAxisTime,
@@ -9,6 +10,7 @@ import {
9
10
  formatScientificAxisLabel,
10
11
  formatTooltipNumber,
11
12
  formatTooltipTime,
13
+ formatXAxisLabel,
12
14
  resolveScientificAxisExponent,
13
15
  shouldUseScientificAxisLabel,
14
16
  } from './formatters'
@@ -55,20 +57,50 @@ describe('waveform number formatters', () => {
55
57
  expect(formatScientificAxisExponent(0, 0)).toBeNull()
56
58
  })
57
59
 
58
- it('formats X-axis ticks and endpoints as plain integers in the selected display unit', () => {
60
+ it('formats X-axis ticks and endpoints as complete plain values in the display unit', () => {
59
61
  const domain: [number, number] = [0, 1]
60
- expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500')
62
+ expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500.4')
61
63
  expect(formatEndpointTime(1, domain, 'ms')).toBe('1000')
62
- expect(formatAxisTime(0.5, 's', domain)).toBe('1')
64
+ expect(formatAxisTime(0.5, 's', domain)).toBe('0.5')
63
65
  expect(formatEndpointTime(1, domain, 's')).toBe('1')
64
66
 
65
67
  const tinyDomain: [number, number] = [0, 0.000001]
66
- expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0')
67
- expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('0')
68
+ expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0.000001')
69
+ expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('-0.000001')
70
+ expect(formatAxisTime(-0, 's')).toBe('0')
68
71
  expect(formatAxisTime(1e21, 's')).toBe('1000000000000000000000')
69
72
  expect(formatAxisTime(Number.POSITIVE_INFINITY, 's')).toBe('Infinity')
70
73
  })
71
74
 
75
+ it('passes display values and complete source context to X-axis label formatters', () => {
76
+ const domain: [number, number] = [0.125, 1.875]
77
+ const formatter: WaveformXAxisLabelFormatter = (value, context) =>
78
+ `${context.kind}:${value / 10}`
79
+
80
+ expect(formatXAxisLabel(0.5, domain, 'ms', 'tick', formatter)).toBe('tick:50')
81
+ expect(formatXAxisLabel(domain[0], domain, 'ms', 'start', formatter)).toBe('start:12.5')
82
+ expect(formatXAxisLabel(domain[1], domain, 'ms', 'end', formatter)).toBe('end:187.5')
83
+
84
+ let receivedContext: WaveformXAxisLabelFormatterContext | undefined
85
+ formatXAxisLabel(0.5, domain, 'ms', 'tick', (value, context) => {
86
+ receivedContext = context
87
+ return String(value)
88
+ })
89
+ expect(receivedContext).toEqual({
90
+ kind: 'tick',
91
+ rawValue: 0.5,
92
+ timeUnit: 'ms',
93
+ domain: [0.125, 1.875],
94
+ displayDomain: [125, 1875],
95
+ })
96
+
97
+ expect(() =>
98
+ formatXAxisLabel(0.5, domain, 's', 'tick', () => {
99
+ throw new Error('formatter failed')
100
+ }),
101
+ ).toThrow('formatter failed')
102
+ })
103
+
72
104
  it('formats tooltip and raw values for their display contexts', () => {
73
105
  expect(formatTooltipNumber(12345.67891)).toBe('12,345.6789')
74
106
  expect(formatTooltipNumber(-0)).toBe('0')
@@ -1,3 +1,5 @@
1
+ import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelKind } from '../types'
2
+
1
3
  /**
2
4
  * 时间单位类型
3
5
  */
@@ -22,10 +24,6 @@ const Y_AXIS_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
22
24
  const TOOLTIP_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
23
25
  maximumFractionDigits: 4,
24
26
  })
25
- const X_AXIS_TIME_FORMATTER = new Intl.NumberFormat('zh-CN', {
26
- maximumFractionDigits: 0,
27
- useGrouping: false,
28
- })
29
27
 
30
28
  function formatFixedNumber(value: number, precision: number): string {
31
29
  const formatted = value.toFixed(Math.max(0, precision))
@@ -103,13 +101,6 @@ export function formatTooltipNumber(value: number): string {
103
101
  return TOOLTIP_NUMBER_FORMATTER.format(value)
104
102
  }
105
103
 
106
- /** Format an X-axis time value as a plain integer without grouping separators. */
107
- function formatAxisTimeValue(value: number): string {
108
- if (!Number.isFinite(value)) return String(value)
109
- const formatted = X_AXIS_TIME_FORMATTER.format(value)
110
- return formatted === '-0' ? '0' : formatted
111
- }
112
-
113
104
  /** Convert a number to complete plain decimal text without forcing exponential notation. */
114
105
  export function formatPlainNumber(value: number): string {
115
106
  if (!Number.isFinite(value)) return String(value)
@@ -143,6 +134,26 @@ export function displayTime(value: number, timeUnit: TimeUnit): number {
143
134
  return timeUnit === 'ms' ? value * 1000 : value
144
135
  }
145
136
 
137
+ /** Format an X-axis value without changing its source coordinate. */
138
+ export function formatXAxisLabel(
139
+ rawValue: number,
140
+ domain: [number, number],
141
+ timeUnit: TimeUnit,
142
+ kind: WaveformXAxisLabelKind,
143
+ formatter?: WaveformXAxisLabelFormatter,
144
+ ): string {
145
+ const value = displayTime(rawValue, timeUnit)
146
+ if (!formatter) return formatPlainNumber(value)
147
+
148
+ return formatter(value, {
149
+ kind,
150
+ rawValue,
151
+ timeUnit,
152
+ domain: [domain[0], domain[1]],
153
+ displayDomain: [displayTime(domain[0], timeUnit), displayTime(domain[1], timeUnit)],
154
+ })
155
+ }
156
+
146
157
  /**
147
158
  * 计算端点标签的小数位数
148
159
  * @param domain 数据域 [最小值, 最大值]
@@ -158,7 +169,7 @@ export function endpointFractionDigits(domain: [number, number], timeUnit: TimeU
158
169
  }
159
170
 
160
171
  /**
161
- * 将 X 轴端点时间格式化为当前显示单位下的普通整数
172
+ * 将 X 轴端点时间格式化为当前显示单位下的普通十进制文本
162
173
  * @param value 时间值(秒)
163
174
  * @param _domain 数据域(为保持现有调用签名而保留)
164
175
  * @param timeUnit 时间单位
@@ -169,11 +180,11 @@ export function formatEndpointTime(
169
180
  _domain: [number, number],
170
181
  timeUnit: TimeUnit,
171
182
  ): string {
172
- return formatAxisTimeValue(displayTime(value, timeUnit))
183
+ return formatXAxisLabel(value, _domain, timeUnit, 'end')
173
184
  }
174
185
 
175
186
  /**
176
- * 将 X 轴时间刻度格式化为当前显示单位下的普通整数
187
+ * 将 X 轴时间刻度格式化为当前显示单位下的普通十进制文本
177
188
  * @param value 时间值(秒)
178
189
  * @param timeUnit 时间单位
179
190
  * @param _domain 数据域(为保持现有调用签名而保留)
@@ -184,8 +195,7 @@ export function formatAxisTime(
184
195
  timeUnit: TimeUnit,
185
196
  _domain?: [number, number],
186
197
  ): string {
187
- void _domain
188
- return formatAxisTimeValue(displayTime(value, timeUnit))
198
+ return formatXAxisLabel(value, _domain ?? [value, value], timeUnit, 'tick')
189
199
  }
190
200
 
191
201
  /**
@@ -11,6 +11,7 @@ export {
11
11
  endpointFractionDigits,
12
12
  formatEndpointTime,
13
13
  formatAxisTime,
14
+ formatXAxisLabel,
14
15
  formatTooltipTime,
15
16
  formatAnnotationTime,
16
17
  formatPlainNumber,