feat(chart): support configurable x-axis labels
All checks were successful
Package component / package (push) Successful in 4m35s
All checks were successful
Package component / package (push) Successful in 4m35s
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelFormatterContext } from '../types'
|
||||
import {
|
||||
formatAnnotationTime,
|
||||
formatAxisTime,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
formatScientificAxisLabel,
|
||||
formatTooltipNumber,
|
||||
formatTooltipTime,
|
||||
formatXAxisLabel,
|
||||
resolveScientificAxisExponent,
|
||||
shouldUseScientificAxisLabel,
|
||||
} from './formatters'
|
||||
@@ -55,20 +57,50 @@ describe('waveform number formatters', () => {
|
||||
expect(formatScientificAxisExponent(0, 0)).toBeNull()
|
||||
})
|
||||
|
||||
it('formats X-axis ticks and endpoints as plain integers in the selected display unit', () => {
|
||||
it('formats X-axis ticks and endpoints as complete plain values in the display unit', () => {
|
||||
const domain: [number, number] = [0, 1]
|
||||
expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500')
|
||||
expect(formatAxisTime(0.5004, 'ms', domain)).toBe('500.4')
|
||||
expect(formatEndpointTime(1, domain, 'ms')).toBe('1000')
|
||||
expect(formatAxisTime(0.5, 's', domain)).toBe('1')
|
||||
expect(formatAxisTime(0.5, 's', domain)).toBe('0.5')
|
||||
expect(formatEndpointTime(1, domain, 's')).toBe('1')
|
||||
|
||||
const tinyDomain: [number, number] = [0, 0.000001]
|
||||
expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0')
|
||||
expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('0')
|
||||
expect(formatEndpointTime(0.000001, tinyDomain, 's')).toBe('0.000001')
|
||||
expect(formatAxisTime(-0.000001, 's', tinyDomain)).toBe('-0.000001')
|
||||
expect(formatAxisTime(-0, 's')).toBe('0')
|
||||
expect(formatAxisTime(1e21, 's')).toBe('1000000000000000000000')
|
||||
expect(formatAxisTime(Number.POSITIVE_INFINITY, 's')).toBe('Infinity')
|
||||
})
|
||||
|
||||
it('passes display values and complete source context to X-axis label formatters', () => {
|
||||
const domain: [number, number] = [0.125, 1.875]
|
||||
const formatter: WaveformXAxisLabelFormatter = (value, context) =>
|
||||
`${context.kind}:${value / 10}`
|
||||
|
||||
expect(formatXAxisLabel(0.5, domain, 'ms', 'tick', formatter)).toBe('tick:50')
|
||||
expect(formatXAxisLabel(domain[0], domain, 'ms', 'start', formatter)).toBe('start:12.5')
|
||||
expect(formatXAxisLabel(domain[1], domain, 'ms', 'end', formatter)).toBe('end:187.5')
|
||||
|
||||
let receivedContext: WaveformXAxisLabelFormatterContext | undefined
|
||||
formatXAxisLabel(0.5, domain, 'ms', 'tick', (value, context) => {
|
||||
receivedContext = context
|
||||
return String(value)
|
||||
})
|
||||
expect(receivedContext).toEqual({
|
||||
kind: 'tick',
|
||||
rawValue: 0.5,
|
||||
timeUnit: 'ms',
|
||||
domain: [0.125, 1.875],
|
||||
displayDomain: [125, 1875],
|
||||
})
|
||||
|
||||
expect(() =>
|
||||
formatXAxisLabel(0.5, domain, 's', 'tick', () => {
|
||||
throw new Error('formatter failed')
|
||||
}),
|
||||
).toThrow('formatter failed')
|
||||
})
|
||||
|
||||
it('formats tooltip and raw values for their display contexts', () => {
|
||||
expect(formatTooltipNumber(12345.67891)).toBe('12,345.6789')
|
||||
expect(formatTooltipNumber(-0)).toBe('0')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelKind } from '../types'
|
||||
|
||||
/**
|
||||
* 时间单位类型
|
||||
*/
|
||||
@@ -22,10 +24,6 @@ const Y_AXIS_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
|
||||
const TOOLTIP_NUMBER_FORMATTER = new Intl.NumberFormat('zh-CN', {
|
||||
maximumFractionDigits: 4,
|
||||
})
|
||||
const X_AXIS_TIME_FORMATTER = new Intl.NumberFormat('zh-CN', {
|
||||
maximumFractionDigits: 0,
|
||||
useGrouping: false,
|
||||
})
|
||||
|
||||
function formatFixedNumber(value: number, precision: number): string {
|
||||
const formatted = value.toFixed(Math.max(0, precision))
|
||||
@@ -103,13 +101,6 @@ export function formatTooltipNumber(value: number): string {
|
||||
return TOOLTIP_NUMBER_FORMATTER.format(value)
|
||||
}
|
||||
|
||||
/** Format an X-axis time value as a plain integer without grouping separators. */
|
||||
function formatAxisTimeValue(value: number): string {
|
||||
if (!Number.isFinite(value)) return String(value)
|
||||
const formatted = X_AXIS_TIME_FORMATTER.format(value)
|
||||
return formatted === '-0' ? '0' : formatted
|
||||
}
|
||||
|
||||
/** Convert a number to complete plain decimal text without forcing exponential notation. */
|
||||
export function formatPlainNumber(value: number): string {
|
||||
if (!Number.isFinite(value)) return String(value)
|
||||
@@ -143,6 +134,26 @@ export function displayTime(value: number, timeUnit: TimeUnit): number {
|
||||
return timeUnit === 'ms' ? value * 1000 : value
|
||||
}
|
||||
|
||||
/** Format an X-axis value without changing its source coordinate. */
|
||||
export function formatXAxisLabel(
|
||||
rawValue: number,
|
||||
domain: [number, number],
|
||||
timeUnit: TimeUnit,
|
||||
kind: WaveformXAxisLabelKind,
|
||||
formatter?: WaveformXAxisLabelFormatter,
|
||||
): string {
|
||||
const value = displayTime(rawValue, timeUnit)
|
||||
if (!formatter) return formatPlainNumber(value)
|
||||
|
||||
return formatter(value, {
|
||||
kind,
|
||||
rawValue,
|
||||
timeUnit,
|
||||
domain: [domain[0], domain[1]],
|
||||
displayDomain: [displayTime(domain[0], timeUnit), displayTime(domain[1], timeUnit)],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算端点标签的小数位数
|
||||
* @param domain 数据域 [最小值, 最大值]
|
||||
@@ -158,7 +169,7 @@ export function endpointFractionDigits(domain: [number, number], timeUnit: TimeU
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 X 轴端点时间格式化为当前显示单位下的普通整数
|
||||
* 将 X 轴端点时间格式化为当前显示单位下的普通十进制文本
|
||||
* @param value 时间值(秒)
|
||||
* @param _domain 数据域(为保持现有调用签名而保留)
|
||||
* @param timeUnit 时间单位
|
||||
@@ -169,11 +180,11 @@ export function formatEndpointTime(
|
||||
_domain: [number, number],
|
||||
timeUnit: TimeUnit,
|
||||
): string {
|
||||
return formatAxisTimeValue(displayTime(value, timeUnit))
|
||||
return formatXAxisLabel(value, _domain, timeUnit, 'end')
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 X 轴时间刻度格式化为当前显示单位下的普通整数
|
||||
* 将 X 轴时间刻度格式化为当前显示单位下的普通十进制文本
|
||||
* @param value 时间值(秒)
|
||||
* @param timeUnit 时间单位
|
||||
* @param _domain 数据域(为保持现有调用签名而保留)
|
||||
@@ -184,8 +195,7 @@ export function formatAxisTime(
|
||||
timeUnit: TimeUnit,
|
||||
_domain?: [number, number],
|
||||
): string {
|
||||
void _domain
|
||||
return formatAxisTimeValue(displayTime(value, timeUnit))
|
||||
return formatXAxisLabel(value, _domain ?? [value, value], timeUnit, 'tick')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@ export {
|
||||
endpointFractionDigits,
|
||||
formatEndpointTime,
|
||||
formatAxisTime,
|
||||
formatXAxisLabel,
|
||||
formatTooltipTime,
|
||||
formatAnnotationTime,
|
||||
formatPlainNumber,
|
||||
|
||||
Reference in New Issue
Block a user