waveform-analysis 0.1.27 → 0.1.28

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 (28) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +274 -318
  4. package/dist/types/components/core/layout.d.ts +2 -4
  5. package/dist/types/components/core/types.d.ts +0 -3
  6. package/dist/types/utils/formatters.d.ts +9 -10
  7. package/dist/types/utils/index.d.ts +1 -1
  8. package/package.json +1 -1
  9. package/src/components/core/layout.test.ts +7 -8
  10. package/src/components/core/layout.ts +10 -20
  11. package/src/components/core/trackLayoutBuilder.ts +3 -23
  12. package/src/components/core/types.ts +0 -3
  13. package/src/components/core/useWaveformLayout.ts +5 -27
  14. package/src/components/interaction/WaveformTooltip.test.ts +16 -0
  15. package/src/components/rendering/WaveformTrackAxes.vue +4 -26
  16. package/src/components/waveformChartCases/compactAndSharedHover.test.ts +7 -7
  17. package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +2 -2
  18. package/src/components/waveformChartCases/dataLifecycle.test.ts +4 -4
  19. package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +29 -22
  20. package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +1 -1
  21. package/src/components/waveformChartCases/layoutAndDomains.test.ts +15 -15
  22. package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +8 -8
  23. package/src/components/waveformChartCases/presentationAndAxes.test.ts +6 -4
  24. package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +8 -8
  25. package/src/components/waveformChartCases/wheelZoomOut.test.ts +3 -3
  26. package/src/utils/formatters.test.ts +40 -31
  27. package/src/utils/formatters.ts +45 -45
  28. package/src/utils/index.ts +0 -1
@@ -33,10 +33,10 @@ describe('WaveformChart', () => {
33
33
  tracks[0].get('.waveform-chart__y-axis-label-bg').attributes('x'),
34
34
  )
35
35
 
36
- expect(labelX).toBe(-103)
36
+ expect(labelX).toBe(-74)
37
37
  expect(labelBackgroundX).toBe(labelX - 12)
38
- expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(119)
39
- expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(119)
38
+ expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(90)
39
+ expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(90)
40
40
  })
41
41
 
42
42
  it('keeps a tick-only gutter when channel labels are empty', async () => {
@@ -62,8 +62,8 @@ describe('WaveformChart', () => {
62
62
  const secondLeft = Number(tracks[1].attributes('data-track-left'))
63
63
 
64
64
  expect(wrapper.findAll('.waveform-chart__y-axis-label')).toHaveLength(0)
65
- expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(89)
66
- expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(89)
65
+ expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(64)
66
+ expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(60)
67
67
  })
68
68
 
69
69
  it('keeps the Y-axis label gutter stable while paging between value ranges', async () => {
@@ -122,7 +122,7 @@ describe('WaveformChart', () => {
122
122
  const tracks = wrapper.findAll('.waveform-chart__track')
123
123
  const firstWidth = Number(tracks[0].attributes('data-track-width'))
124
124
  const secondLeft = Number(tracks[1].attributes('data-track-left'))
125
- expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(39)
125
+ expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(32)
126
126
  })
127
127
 
128
128
  it('uses one shared overlay and bottom-row x axes for separated and compact grids', async () => {
@@ -262,10 +262,10 @@ describe('WaveformChart', () => {
262
262
  )
263
263
 
264
264
  const tracks = wrapper.findAll('.waveform-chart__track')
265
- expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8.00')
266
- expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5.00')
267
- expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8.00')
268
- expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5.00')
265
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8000')
266
+ expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
267
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8000')
268
+ expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
269
269
  })
270
270
 
271
271
  it('uses an explicit initial x domain override for an independent track', async () => {
@@ -293,7 +293,7 @@ describe('WaveformChart', () => {
293
293
  },
294
294
  )
295
295
 
296
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
296
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
297
297
  })
298
298
 
299
299
  it('reacts to exact fixed Y-domain props and returns to automatic bounds', async () => {
@@ -312,13 +312,13 @@ describe('WaveformChart', () => {
312
312
 
313
313
  await wrapper.setProps({ yDomain: [3, 97] })
314
314
  await flushPromises()
315
- expect(yTickLabels()).toContain('3.00')
316
- expect(yTickLabels()).toContain('97.00')
315
+ expect(yTickLabels()).toContain('3')
316
+ expect(yTickLabels()).toContain('97')
317
317
 
318
318
  await wrapper.setProps({ yDomain: undefined })
319
319
  await flushPromises()
320
- expect(yTickLabels()).not.toContain('3.00')
321
- expect(yTickLabels()).not.toContain('97.00')
320
+ expect(yTickLabels()).not.toContain('3')
321
+ expect(yTickLabels()).not.toContain('97')
322
322
  })
323
323
 
324
324
  it('keeps annotations bound to their channel while paging', async () => {
@@ -30,7 +30,7 @@ describe('WaveformChart', () => {
30
30
  kind: 'points',
31
31
  points: [
32
32
  { x: 0, y: 0 },
33
- { x: 1, y: 254 },
33
+ { x: 1, y: 2540 },
34
34
  ],
35
35
  },
36
36
  },
@@ -51,13 +51,13 @@ describe('WaveformChart', () => {
51
51
  )
52
52
 
53
53
  expect(
54
- wrapper
55
- .findAll('.waveform-chart__axis-exponent--y')
56
- .map((label) => [label.attributes('data-y-axis-index'), label.text()]),
57
- ).toEqual([
58
- ['1', 'E+02'],
59
- ['2', 'E-04'],
60
- ])
54
+ wrapper.findAll('.waveform-chart__axis--y').map((axis) =>
55
+ axis
56
+ .findAll('.tick text')
57
+ .map((label) => label.text())
58
+ .filter((label) => label.startsWith('E')),
59
+ ),
60
+ ).toEqual([[], [expect.stringMatching(/^E\+03 /)], [expect.stringMatching(/^E-04 /)]])
61
61
  })
62
62
 
63
63
  it('reprojects annotations with the Y axis assigned to their series', async () => {
@@ -54,8 +54,9 @@ describe('WaveformChart', () => {
54
54
  ],
55
55
  })
56
56
 
57
- expect(first.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
58
- expect(second.get('.waveform-chart__axis-exponent--y').text()).toBe('E+04')
57
+ expect(first.get('.waveform-chart__axis--y').text()).not.toContain('E')
58
+ expect(second.get('.waveform-chart__axis--y').text()).toContain('E+04 ')
59
+ expect(second.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
59
60
  })
60
61
 
61
62
  it('renders a configurable zero line only when the Y domain contains zero', async () => {
@@ -299,8 +300,9 @@ describe('WaveformChart', () => {
299
300
  })
300
301
  expect(xAxis.get('path.domain').attributes('display')).toBe('none')
301
302
  expect(wrapper.findAll('.waveform-chart__axis-endpoint')).toHaveLength(2)
302
- expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).not.toBe('')
303
- expect(wrapper.get('.waveform-chart__axis-exponent--y').text()).not.toBe('')
303
+ expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
304
+ expect(yAxes.some((axis) => axis.text().includes('E+04 '))).toBe(true)
305
+ expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
304
306
  expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false)
305
307
  expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
306
308
  stroke: '#dc2626',
@@ -79,8 +79,8 @@ describe('WaveformChart', () => {
79
79
 
80
80
  expect(wrapper.emitted('zoom-change')).toBeUndefined()
81
81
  expect(wrapper.emitted('zoom-end')).toBeUndefined()
82
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0.00')
83
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
82
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
83
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
84
84
  })
85
85
 
86
86
  it('ignores independent viewport dragging', async () => {
@@ -333,14 +333,14 @@ describe('WaveformChart', () => {
333
333
  )
334
334
  flushAnimationFrames()
335
335
  await flushPromises()
336
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe('0.00')
336
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe('0')
337
337
 
338
338
  overlay.element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }))
339
339
  await flushPromises()
340
340
 
341
341
  expect(wrapper.emitted('zoom-reset')).toHaveLength(1)
342
- expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0.00')
343
- expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
342
+ expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
343
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
344
344
  })
345
345
 
346
346
  it('exposes resetViewport for independent tracks', async () => {
@@ -364,13 +364,13 @@ describe('WaveformChart', () => {
364
364
  )
365
365
  flushAnimationFrames()
366
366
  await flushPromises()
367
- expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).not.toBe('0.00')
367
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).not.toBe('0')
368
368
 
369
369
  const chart = wrapper.vm as unknown as { resetViewport: () => void }
370
370
  chart.resetViewport()
371
371
  await flushPromises()
372
372
 
373
- expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0.00')
374
- expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1.00')
373
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0')
374
+ expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1000')
375
375
  })
376
376
  })
@@ -49,15 +49,15 @@ describe('WaveformChart wheel zoom out', () => {
49
49
  end: wrapper.get('.waveform-chart__axis-endpoint--end').text(),
50
50
  })
51
51
 
52
- expect(endpoints()).toEqual({ start: '-5.00', end: '5.00' })
52
+ expect(endpoints()).toEqual({ start: '-5000', end: '5000' })
53
53
  await dispatchWheel(-4000)
54
- expect(endpoints()).not.toEqual({ start: '-5.00', end: '5.00' })
54
+ expect(endpoints()).not.toEqual({ start: '-5000', end: '5000' })
55
55
 
56
56
  await wrapper.setProps({ data: createData(-0.125, 0.125) })
57
57
  await flushPromises()
58
58
  await dispatchWheel(4000)
59
59
 
60
- expect(endpoints()).toEqual({ start: '-5.00', end: '5.00' })
60
+ expect(endpoints()).toEqual({ start: '-5000', end: '5000' })
61
61
  })
62
62
 
63
63
  it('emits one zoom-end payload after zooming a shared viewport out', async () => {
@@ -3,69 +3,78 @@ import { describe, expect, it } from 'vitest'
3
3
  import {
4
4
  formatAnnotationTime,
5
5
  formatAxisTime,
6
- formatAxisTimeExponent,
7
6
  formatEndpointTime,
8
7
  formatPlainNumber,
9
8
  formatScientificAxisExponent,
10
9
  formatScientificAxisLabel,
11
10
  formatTooltipNumber,
11
+ formatTooltipTime,
12
+ resolveScientificAxisExponent,
12
13
  shouldUseScientificAxisLabel,
13
14
  } from './formatters'
14
15
 
15
16
  describe('waveform number formatters', () => {
16
17
  it('uses the reference Y-axis scientific notation boundaries', () => {
17
18
  expect(shouldUseScientificAxisLabel(0)).toBe(false)
18
- expect(shouldUseScientificAxisLabel(0.009)).toBe(true)
19
- expect(shouldUseScientificAxisLabel(0.01)).toBe(false)
20
- expect(shouldUseScientificAxisLabel(99.99)).toBe(false)
21
- expect(shouldUseScientificAxisLabel(100)).toBe(true)
19
+ expect(shouldUseScientificAxisLabel(0.000999)).toBe(true)
20
+ expect(shouldUseScientificAxisLabel(0.001)).toBe(false)
21
+ expect(shouldUseScientificAxisLabel(999.999)).toBe(false)
22
+ expect(shouldUseScientificAxisLabel(1000)).toBe(true)
22
23
  })
23
24
 
24
- it('shares one separate exponent across an axis', () => {
25
- const positiveAxis = { axisMin: 0, axisMax: 254 }
26
- expect(formatScientificAxisLabel(127, positiveAxis)).toBe('1.27')
27
- expect(formatScientificAxisLabel(254, positiveAxis)).toBe('2.54')
28
- expect(formatScientificAxisExponent(0, 254)).toBe('E+02')
25
+ it('prefixes one shared exponent to the largest visible tick', () => {
26
+ const positiveAxis = { axisMin: 1000, axisMax: 3000 }
27
+ expect(formatScientificAxisLabel(1000, positiveAxis)).toBe('1')
28
+ expect(formatScientificAxisLabel(2000, positiveAxis)).toBe('2')
29
+ expect(formatScientificAxisLabel(3000, positiveAxis)).toBe('E+03 3')
30
+ expect(formatScientificAxisLabel(2000, { ...positiveAxis, topTickValue: 2000 })).toBe('E+03 2')
31
+ expect(formatScientificAxisExponent(1000, 3000)).toBe('E+03')
29
32
  expect(formatScientificAxisExponent(0, 1e120)).toBe('E+120')
30
33
 
31
- const tinyAxis = { axisMin: 0, axisMax: 0.0002 }
32
- expect(formatScientificAxisLabel(0.0001, tinyAxis)).toBe('1.00')
33
- expect(formatScientificAxisLabel(0.0002, tinyAxis)).toBe('2.00')
34
- expect(formatScientificAxisExponent(0, 0.0002)).toBe('E-04')
34
+ const tinyAxis = { axisMin: 0.0001, axisMax: 0.0003 }
35
+ expect(formatScientificAxisLabel(0.0001, tinyAxis)).toBe('1')
36
+ expect(formatScientificAxisLabel(0.0002, tinyAxis)).toBe('2')
37
+ expect(formatScientificAxisLabel(0.0003, tinyAxis)).toBe('E-04 3')
38
+ expect(formatScientificAxisExponent(0.0001, 0.0003)).toBe('E-04')
39
+ })
35
40
 
36
- const negativeAxis = { axisMin: -254, axisMax: 0 }
37
- expect(formatScientificAxisLabel(-254, negativeAxis)).toBe('-2.54')
38
- expect(formatScientificAxisLabel(0, negativeAxis)).toBe('0.00')
39
- expect(formatScientificAxisExponent(-254, 0)).toBe('E+02')
41
+ it('derives the exponent from Math.max(axisMin, axisMax)', () => {
42
+ expect(resolveScientificAxisExponent(-9000, -1000)).toBe(3)
43
+ expect(resolveScientificAxisExponent(-10_000, 3000)).toBe(3)
44
+ expect(resolveScientificAxisExponent(-1000, 0)).toBeNull()
45
+ expect(resolveScientificAxisExponent(0, 0)).toBeNull()
40
46
  })
41
47
 
42
- it('keeps plain axes at two decimals and removes negative zero', () => {
43
- expect(formatScientificAxisLabel(99.99, { axisMin: 0, axisMax: 99.99 })).toBe('99.99')
44
- expect(formatScientificAxisLabel(0.01, { axisMin: 0, axisMax: 0.01 })).toBe('0.01')
45
- expect(formatScientificAxisLabel(-0.001, { axisMin: -1, axisMax: 1 })).toBe('0.00')
48
+ it('keeps five significant digits, removes trailing zeroes, and limits decimals to four', () => {
49
+ expect(formatScientificAxisLabel(123.456, { axisMin: 0, axisMax: 999 })).toBe('123.46')
50
+ expect(formatScientificAxisLabel(1234.56, { axisMin: 0, axisMax: 9999 })).toBe('1.2346')
51
+ expect(formatScientificAxisLabel(3000, { axisMin: 1000, axisMax: 3000 })).toBe('E+03 3')
52
+ expect(formatScientificAxisLabel(-0, { axisMin: -1, axisMax: 1 })).toBe('0')
46
53
  expect(formatScientificAxisLabel(Number.NaN)).toBe('NaN')
47
54
  expect(formatScientificAxisLabel(Number.POSITIVE_INFINITY)).toBe('Infinity')
48
55
  expect(formatScientificAxisExponent(0, 0)).toBeNull()
49
56
  })
50
57
 
51
- it('formats X-axis ticks and endpoints from the selected display unit', () => {
58
+ it('formats X-axis ticks and endpoints as plain integers in the selected display unit', () => {
52
59
  const domain: [number, number] = [0, 1]
53
- expect(formatAxisTime(0.5, 'ms', domain)).toBe('0.50')
54
- expect(formatEndpointTime(1, domain, 'ms')).toBe('1.00')
55
- expect(formatAxisTimeExponent(domain, 'ms')).toBe('E+03')
56
- expect(formatAxisTime(0.5, 's', domain)).toBe('0.50')
57
- expect(formatEndpointTime(1, domain, 's')).toBe('1.00')
58
- expect(formatAxisTimeExponent(domain, 's')).toBeNull()
60
+ expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500')
61
+ expect(formatEndpointTime(1, domain, 'ms')).toBe('1000')
62
+ expect(formatAxisTime(0.5, 's', domain)).toBe('1')
63
+ expect(formatEndpointTime(1, domain, 's')).toBe('1')
59
64
 
60
65
  const tinyDomain: [number, number] = [0, 0.000001]
61
- expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('1.00')
62
- expect(formatAxisTimeExponent(tinyDomain, 's')).toBe('E-06')
66
+ expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0')
67
+ expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('0')
68
+ expect(formatAxisTime(1e21, 's')).toBe('1000000000000000000000')
69
+ expect(formatAxisTime(Number.POSITIVE_INFINITY, 's')).toBe('Infinity')
63
70
  })
64
71
 
65
72
  it('formats tooltip and raw values for their display contexts', () => {
66
73
  expect(formatTooltipNumber(12345.67891)).toBe('12,345.6789')
67
74
  expect(formatTooltipNumber(-0)).toBe('0')
68
75
  expect(formatTooltipNumber(Number.POSITIVE_INFINITY)).toBe('Infinity')
76
+ expect(formatTooltipTime(1.234567, 's')).toBe('1.2346')
77
+ expect(formatTooltipTime(1, 'ms')).toBe('1,000')
69
78
  expect(formatPlainNumber(0.0000001)).toBe('0.0000001')
70
79
  expect(formatPlainNumber(1e21)).toBe('1000000000000000000000')
71
80
  expect(formatPlainNumber(-0)).toBe('0')
@@ -4,22 +4,28 @@
4
4
  export type TimeUnit = 'ms' | 's'
5
5
 
6
6
  export interface ScientificAxisLabelOptions {
7
+ /** @deprecated Y-axis labels always use five significant digits before localization. */
7
8
  precision?: number
8
9
  axisMin?: number
9
10
  axisMax?: number
11
+ topTickValue?: number
10
12
  }
11
13
 
12
14
  /** @deprecated Use ScientificAxisLabelOptions. */
13
- export type ScientificYAxisLabelOptions = ScientificAxisLabelOptions & {
14
- topTickValue?: number
15
- }
15
+ export type ScientificYAxisLabelOptions = ScientificAxisLabelOptions
16
16
 
17
- const DEFAULT_Y_AXIS_PRECISION = 2
18
- const SCIENTIFIC_MIN_ABSOLUTE_VALUE = 0.01
19
- const SCIENTIFIC_MAX_PLAIN_ABSOLUTE_VALUE = 100
17
+ const SCIENTIFIC_MIN_ABSOLUTE_VALUE = 0.001
18
+ const SCIENTIFIC_MAX_PLAIN_ABSOLUTE_VALUE = 1000
19
+ const Y_AXIS_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
20
+ maximumFractionDigits: 4,
21
+ })
20
22
  const TOOLTIP_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
21
23
  maximumFractionDigits: 4,
22
24
  })
25
+ const X_AXIS_TIME_FORMATTER = new Intl.NumberFormat('zh-CN', {
26
+ maximumFractionDigits: 0,
27
+ useGrouping: false,
28
+ })
23
29
 
24
30
  function formatFixedNumber(value: number, precision: number): string {
25
31
  const formatted = value.toFixed(Math.max(0, precision))
@@ -38,9 +44,10 @@ export function shouldUseScientificAxisLabel(maxAbsoluteValue: number): boolean
38
44
  export function resolveScientificAxisExponent(axisMin?: number, axisMax?: number): number | null {
39
45
  if (typeof axisMin !== 'number' || typeof axisMax !== 'number') return null
40
46
 
41
- const maxAbsoluteValue = Math.max(Math.abs(axisMin), Math.abs(axisMax))
42
- return shouldUseScientificAxisLabel(maxAbsoluteValue)
43
- ? Math.floor(Math.log10(maxAbsoluteValue))
47
+ const maxValue = Math.max(axisMin, axisMax)
48
+ const absoluteMaxValue = Math.abs(maxValue)
49
+ return shouldUseScientificAxisLabel(absoluteMaxValue)
50
+ ? Math.floor(Math.log10(absoluteMaxValue))
44
51
  : null
45
52
  }
46
53
 
@@ -49,6 +56,13 @@ function formatExponent(exponent: number): string {
49
56
  return `E${sign}${Math.abs(exponent).toString().padStart(2, '0')}`
50
57
  }
51
58
 
59
+ function formatYAxisNumber(value: number): string {
60
+ if (Object.is(value, -0)) return '0'
61
+ const roundedValue = value === 0 ? 0 : Number(value.toPrecision(5))
62
+ const formatted = Y_AXIS_NUMBER_FORMATTER.format(roundedValue)
63
+ return formatted === '-0' ? '0' : formatted
64
+ }
65
+
52
66
  /** Format a Y-axis tick, sharing one exponent derived from the complete axis domain. */
53
67
  export function formatScientificAxisLabel(
54
68
  value: number,
@@ -56,10 +70,13 @@ export function formatScientificAxisLabel(
56
70
  ): string {
57
71
  if (!Number.isFinite(value)) return String(value)
58
72
 
59
- const precision = options.precision ?? DEFAULT_Y_AXIS_PRECISION
60
73
  const exponent = resolveScientificAxisExponent(options.axisMin, options.axisMax)
61
74
  const scaledValue = exponent === null ? value : value / 10 ** exponent
62
- return formatFixedNumber(scaledValue, precision)
75
+ const formattedValue = formatYAxisNumber(scaledValue)
76
+ const topTickValue = options.topTickValue ?? options.axisMax
77
+ return exponent !== null && value === topTickValue
78
+ ? `${formatExponent(exponent)} ${formattedValue}`
79
+ : formattedValue
63
80
  }
64
81
 
65
82
  /** Return the shared E-style multiplier for an axis, or null for a plain axis. */
@@ -86,6 +103,13 @@ export function formatTooltipNumber(value: number): string {
86
103
  return TOOLTIP_NUMBER_FORMATTER.format(value)
87
104
  }
88
105
 
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
+
89
113
  /** Convert a number to complete plain decimal text without forcing exponential notation. */
90
114
  export function formatPlainNumber(value: number): string {
91
115
  if (!Number.isFinite(value)) return String(value)
@@ -134,54 +158,34 @@ export function endpointFractionDigits(domain: [number, number], timeUnit: TimeU
134
158
  }
135
159
 
136
160
  /**
137
- * 按完整 X 轴显示域格式化端点时间
161
+ * 将 X 轴端点时间格式化为当前显示单位下的普通整数
138
162
  * @param value 时间值(秒)
139
- * @param domain 数据域
163
+ * @param _domain 数据域(为保持现有调用签名而保留)
140
164
  * @param timeUnit 时间单位
141
165
  * @returns 格式化的时间字符串
142
166
  */
143
167
  export function formatEndpointTime(
144
168
  value: number,
145
- domain: [number, number],
169
+ _domain: [number, number],
146
170
  timeUnit: TimeUnit,
147
171
  ): string {
148
- const [axisMin, axisMax] = domain.map((domainValue) => displayTime(domainValue, timeUnit)) as [
149
- number,
150
- number,
151
- ]
152
- return formatScientificAxisLabel(displayTime(value, timeUnit), {
153
- axisMin,
154
- axisMax,
155
- })
172
+ return formatAxisTimeValue(displayTime(value, timeUnit))
156
173
  }
157
174
 
158
175
  /**
159
- * 按完整 X 轴显示域格式化时间刻度
176
+ * 将 X 轴时间刻度格式化为当前显示单位下的普通整数
160
177
  * @param value 时间值(秒)
161
178
  * @param timeUnit 时间单位
179
+ * @param _domain 数据域(为保持现有调用签名而保留)
162
180
  * @returns 格式化的时间字符串
163
181
  */
164
182
  export function formatAxisTime(
165
183
  value: number,
166
184
  timeUnit: TimeUnit,
167
- domain?: [number, number],
185
+ _domain?: [number, number],
168
186
  ): string {
169
- const displayValue = displayTime(value, timeUnit)
170
- const displayDomain = domain?.map((domainValue) => displayTime(domainValue, timeUnit)) as
171
- [number, number] | undefined
172
- return formatScientificAxisLabel(displayValue, {
173
- axisMin: displayDomain?.[0],
174
- axisMax: displayDomain?.[1],
175
- })
176
- }
177
-
178
- /** Return the shared multiplier for an X-axis domain in its selected display unit. */
179
- export function formatAxisTimeExponent(
180
- domain: [number, number],
181
- timeUnit: TimeUnit,
182
- ): string | null {
183
- const [axisMin, axisMax] = domain.map((value) => displayTime(value, timeUnit)) as [number, number]
184
- return formatScientificAxisExponent(axisMin, axisMax)
187
+ void _domain
188
+ return formatAxisTimeValue(displayTime(value, timeUnit))
185
189
  }
186
190
 
187
191
  /**
@@ -191,11 +195,7 @@ export function formatAxisTimeExponent(
191
195
  * @returns 格式化的时间字符串
192
196
  */
193
197
  export function formatTooltipTime(value: number, timeUnit: TimeUnit): string {
194
- const displayValue = displayTime(value, timeUnit)
195
- return displayValue.toLocaleString('zh-CN', {
196
- minimumFractionDigits: 4,
197
- maximumFractionDigits: 4,
198
- })
198
+ return formatTooltipNumber(displayTime(value, timeUnit))
199
199
  }
200
200
 
201
201
  /** Format an annotation X coordinate in the selected display time unit. */
@@ -11,7 +11,6 @@ export {
11
11
  endpointFractionDigits,
12
12
  formatEndpointTime,
13
13
  formatAxisTime,
14
- formatAxisTimeExponent,
15
14
  formatTooltipTime,
16
15
  formatAnnotationTime,
17
16
  formatPlainNumber,