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.
- package/README.md +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +274 -318
- package/dist/types/components/core/layout.d.ts +2 -4
- package/dist/types/components/core/types.d.ts +0 -3
- package/dist/types/utils/formatters.d.ts +9 -10
- package/dist/types/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/core/layout.test.ts +7 -8
- package/src/components/core/layout.ts +10 -20
- package/src/components/core/trackLayoutBuilder.ts +3 -23
- package/src/components/core/types.ts +0 -3
- package/src/components/core/useWaveformLayout.ts +5 -27
- package/src/components/interaction/WaveformTooltip.test.ts +16 -0
- package/src/components/rendering/WaveformTrackAxes.vue +4 -26
- package/src/components/waveformChartCases/compactAndSharedHover.test.ts +7 -7
- package/src/components/waveformChartCases/dataAndHoverPerformance.test.ts +2 -2
- package/src/components/waveformChartCases/dataLifecycle.test.ts +4 -4
- package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +29 -22
- package/src/components/waveformChartCases/interactionAndAnnotationCreation.test.ts +1 -1
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +15 -15
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +8 -8
- package/src/components/waveformChartCases/presentationAndAxes.test.ts +6 -4
- package/src/components/waveformChartCases/viewportConstraintsAndReset.test.ts +8 -8
- package/src/components/waveformChartCases/wheelZoomOut.test.ts +3 -3
- package/src/utils/formatters.test.ts +40 -31
- package/src/utils/formatters.ts +45 -45
- 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(-
|
|
36
|
+
expect(labelX).toBe(-74)
|
|
37
37
|
expect(labelBackgroundX).toBe(labelX - 12)
|
|
38
|
-
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(
|
|
39
|
-
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(
|
|
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(
|
|
66
|
-
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(
|
|
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(
|
|
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('-
|
|
266
|
-
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('
|
|
267
|
-
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-
|
|
268
|
-
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('
|
|
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('
|
|
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
|
|
316
|
-
expect(yTickLabels()).toContain('97
|
|
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
|
|
321
|
-
expect(yTickLabels()).not.toContain('97
|
|
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:
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
58
|
-
expect(second.get('.waveform-chart__axis
|
|
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.
|
|
303
|
-
expect(
|
|
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
|
|
83
|
-
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('
|
|
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
|
|
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
|
|
343
|
-
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('
|
|
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
|
|
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
|
|
374
|
-
expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('
|
|
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: '-
|
|
52
|
+
expect(endpoints()).toEqual({ start: '-5000', end: '5000' })
|
|
53
53
|
await dispatchWheel(-4000)
|
|
54
|
-
expect(endpoints()).not.toEqual({ start: '-
|
|
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: '-
|
|
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.
|
|
19
|
-
expect(shouldUseScientificAxisLabel(0.
|
|
20
|
-
expect(shouldUseScientificAxisLabel(
|
|
21
|
-
expect(shouldUseScientificAxisLabel(
|
|
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('
|
|
25
|
-
const positiveAxis = { axisMin:
|
|
26
|
-
expect(formatScientificAxisLabel(
|
|
27
|
-
expect(formatScientificAxisLabel(
|
|
28
|
-
expect(
|
|
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.
|
|
32
|
-
expect(formatScientificAxisLabel(0.0001, tinyAxis)).toBe('1
|
|
33
|
-
expect(formatScientificAxisLabel(0.0002, tinyAxis)).toBe('2
|
|
34
|
-
expect(
|
|
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
|
-
|
|
37
|
-
expect(
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
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
|
|
43
|
-
expect(formatScientificAxisLabel(
|
|
44
|
-
expect(formatScientificAxisLabel(
|
|
45
|
-
expect(formatScientificAxisLabel(
|
|
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
|
|
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.
|
|
54
|
-
expect(formatEndpointTime(1, domain, 'ms')).toBe('
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
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('
|
|
62
|
-
expect(
|
|
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')
|
package/src/utils/formatters.ts
CHANGED
|
@@ -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
|
|
18
|
-
const
|
|
19
|
-
const
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
161
|
+
* 将 X 轴端点时间格式化为当前显示单位下的普通整数
|
|
138
162
|
* @param value 时间值(秒)
|
|
139
|
-
* @param
|
|
163
|
+
* @param _domain 数据域(为保持现有调用签名而保留)
|
|
140
164
|
* @param timeUnit 时间单位
|
|
141
165
|
* @returns 格式化的时间字符串
|
|
142
166
|
*/
|
|
143
167
|
export function formatEndpointTime(
|
|
144
168
|
value: number,
|
|
145
|
-
|
|
169
|
+
_domain: [number, number],
|
|
146
170
|
timeUnit: TimeUnit,
|
|
147
171
|
): string {
|
|
148
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
185
|
+
_domain?: [number, number],
|
|
168
186
|
): string {
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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. */
|