fix(chart): refine axis number formatting
All checks were successful
Package component / package (push) Successful in 4m2s

This commit is contained in:
李启源
2026-08-04 11:28:42 +08:00
parent f98c264562
commit c5aa3c662a
22 changed files with 217 additions and 262 deletions

View File

@@ -72,7 +72,7 @@ describe('WaveformChart', () => {
expect(endTicks[3]).toHaveLength(0)
})
it('keeps one separate shared exponent for every compact Y axis', async () => {
it('prefixes one shared exponent to the largest visible tick on every compact Y axis', async () => {
const wrapper = await mountSizedChart(
{
kind: 'series',
@@ -83,7 +83,7 @@ describe('WaveformChart', () => {
kind: 'points',
points: [
{ x: 0, y: 0 },
{ x: 1, y: 254 },
{ x: 1, y: 2540 },
],
},
},
@@ -103,14 +103,14 @@ describe('WaveformChart', () => {
)
const axes = wrapper.findAll('.waveform-chart__axis--y')
const exponents = wrapper.findAll('.waveform-chart__axis-exponent--y')
expect(axes).toHaveLength(2)
expect(exponents.map((label) => label.text())).toEqual(['E+02', 'E-04'])
axes.forEach((axis) => {
axes.forEach((axis, index) => {
const labels = axis.findAll('.tick text').map((tick) => tick.text())
expect(labels.every((label) => !label.startsWith('E'))).toBe(true)
expect(labels.every((label) => /^-?\d+\.\d{2}$/.test(label))).toBe(true)
const exponentLabels = labels.filter((label) => label.startsWith('E'))
expect(exponentLabels).toHaveLength(1)
expect(exponentLabels[0]).toMatch(index === 0 ? /^E\+03 / : /^E-04 /)
})
expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
})
it('prefers a trimmed series name and falls back to yLabel for unnamed data', async () => {

View File

@@ -178,7 +178,7 @@ describe('WaveformChart', () => {
await flushPromises()
expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([{ x: 1, y: 5 }])
expect(wrapper.find('.waveform-chart__tooltip').exists()).toBe(true)
expect(wrapper.get('.waveform-chart__tooltip').text()).toContain('ms: 1,000.0000')
expect(wrapper.get('.waveform-chart__tooltip-time').text()).toBe('ms: 1,000')
const crosshairLines = wrapper.findAll('.waveform-chart__crosshair line')
expect(crosshairLines).toHaveLength(1)
expect(crosshairLines[0].attributes('x1')).toBe(crosshairLines[0].attributes('x2'))
@@ -303,7 +303,7 @@ describe('WaveformChart', () => {
flushAnimationFrames()
await flushPromises()
expect(wrapper.get('.waveform-chart__tooltip').text()).toContain('ms: 1,000.0000')
expect(wrapper.get('.waveform-chart__tooltip-time').text()).toBe('ms: 1,000')
expect(wrapper.findAll('.waveform-chart__crosshair line')).toHaveLength(1)
expect(wrapper.get('.waveform-chart__line').element).toBe(pathBeforeHover)
expect(chartUpdate).not.toHaveBeenCalled()

View File

@@ -62,15 +62,15 @@ describe('WaveformChart', () => {
],
}
const wrapper = await mountSizedChart(firstData)
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
firstData.points.push({ x: 2, y: 2 })
await flushPromises()
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
await wrapper.setProps({ data: { ...firstData, points: [...firstData.points] } })
await flushPromises()
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
})
it('keeps controlled annotations when replacing the loaded data window', async () => {
@@ -170,7 +170,7 @@ describe('WaveformChart', () => {
expect(wrapper.findAll('.waveform-chart__track-label')).toHaveLength(0)
expect(
wrapper.findAll('.waveform-chart__axis-endpoint--end').map((item) => item.text()),
).toEqual(['1.00', '2.00'])
).toEqual(['1000', '2000'])
})
it('keeps the zero Y-axis label on upper compact tracks', async () => {

View File

@@ -162,35 +162,42 @@ describe('WaveformChart', () => {
expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
String(trackWidth),
)
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4.99')
expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4990')
expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
})
it('uses one shared scientific exponent only for large and tiny Y-axis domains', async () => {
const cases = [
{ values: [0, 50], exponent: null },
{ values: [0, 254], exponent: 'E+02' },
{ values: [0, 0.0002], exponent: 'E-04' },
{ values: [0, 50], yDomain: [0, 50] as [number, number], exponent: null },
{ values: [1000, 3000], yDomain: [1000, 3000] as [number, number], exponent: 'E+03' },
{
values: [0.0001, 0.0003],
yDomain: [0.0001, 0.0003] as [number, number],
exponent: 'E-04',
},
]
for (const { values, exponent } of cases) {
const wrapper = await mountSizedChart({
kind: 'points',
for (const { values, yDomain, exponent } of cases) {
const data = {
kind: 'points' as const,
points: values.map((y, x) => ({ x, y })),
})
}
const originalValues = data.points.map((point) => point.y)
const wrapper = await mountSizedChart(data, { yDomain })
const labels = wrapper
.get('.waveform-chart__axis--y')
.findAll('.tick text')
.map((tick) => tick.text())
const exponentLabel = wrapper.find('.waveform-chart__axis-exponent--y')
const exponentLabels = labels.filter((label) => label.startsWith('E'))
if (exponent === null) {
expect(exponentLabel.exists()).toBe(false)
expect(exponentLabels).toHaveLength(0)
} else {
expect(exponentLabel.text()).toBe(exponent)
expect(labels.every((label) => !label.startsWith('E'))).toBe(true)
expect(labels.every((label) => /^-?\d+\.\d{2}$/.test(label))).toBe(true)
expect(exponentLabels).toHaveLength(1)
expect(exponentLabels[0]).toMatch(new RegExp(`^${exponent.replace('+', '\\+')} `))
}
expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
expect(data.points.map((point) => point.y)).toEqual(originalValues)
wrapper.unmount()
}
@@ -212,13 +219,13 @@ describe('WaveformChart', () => {
expect(millisecondsChart.text()).toContain('时间ms')
expect(millisecondTicks.length).toBeGreaterThan(0)
expect(millisecondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
expect(millisecondsChart.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
expect(millisecondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
expect(millisecondsChart.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
expect(millisecondsChart.find('.waveform-chart__watermark').exists()).toBe(false)
const secondsChart = await mountSizedChart(data, { timeUnit: 's', xLabel: 'Elapsed time' })
expect(secondsChart.text()).toContain('Elapsed time')
expect(secondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
expect(secondsChart.get('.waveform-chart__axis-endpoint--end').text()).toBe('1')
expect(secondsChart.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
})
@@ -237,15 +244,15 @@ describe('WaveformChart', () => {
expect(start.attributes('x')).toBe('0')
expect(start.attributes('text-anchor')).toBe('start')
expect(start.text()).toBe('0.00')
expect(start.text()).toBe('0')
expect(end.attributes('x')).toBe(
wrapper.get('.waveform-chart__track').attributes('data-track-width'),
)
expect(end.attributes('text-anchor')).toBe('end')
expect(end.text()).toBe('2.00')
expect(end.text()).toBe('1999')
expect(middleTickLabels.length).toBeGreaterThan(0)
expect(middleTickLabels).not.toContain('0.00')
expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).toBe('E+03')
expect(middleTickLabels).not.toContain('0')
expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(
middleTickLabels.length,
)
@@ -300,6 +307,6 @@ describe('WaveformChart', () => {
)
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe(initialStart)
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).not.toBe(initialEnd)
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toContain('.')
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toMatch(/^-?\d+$/)
})
})

View File

@@ -59,7 +59,7 @@ describe('WaveformChart', () => {
await flushPromises()
expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(1)
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
})
it('updates rendering props and disables zoom interaction', async () => {

View File

@@ -33,10 +33,10 @@ describe('WaveformChart', () => {
tracks[0].get('.waveform-chart__y-axis-label-bg').attributes('x'),
)
expect(labelX).toBe(-103)
expect(labelX).toBe(-74)
expect(labelBackgroundX).toBe(labelX - 12)
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(119)
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(119)
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(90)
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(90)
})
it('keeps a tick-only gutter when channel labels are empty', async () => {
@@ -62,8 +62,8 @@ describe('WaveformChart', () => {
const secondLeft = Number(tracks[1].attributes('data-track-left'))
expect(wrapper.findAll('.waveform-chart__y-axis-label')).toHaveLength(0)
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(89)
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(89)
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(64)
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(60)
})
it('keeps the Y-axis label gutter stable while paging between value ranges', async () => {
@@ -122,7 +122,7 @@ describe('WaveformChart', () => {
const tracks = wrapper.findAll('.waveform-chart__track')
const firstWidth = Number(tracks[0].attributes('data-track-width'))
const secondLeft = Number(tracks[1].attributes('data-track-left'))
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(39)
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(32)
})
it('uses one shared overlay and bottom-row x axes for separated and compact grids', async () => {
@@ -262,10 +262,10 @@ describe('WaveformChart', () => {
)
const tracks = wrapper.findAll('.waveform-chart__track')
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8.00')
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5.00')
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8.00')
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5.00')
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8000')
expect(tracks[0]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--start').text()).toBe('-8000')
expect(tracks[1]?.get('.waveform-chart__axis-endpoint--end').text()).toBe('5000')
})
it('uses an explicit initial x domain override for an independent track', async () => {
@@ -293,7 +293,7 @@ describe('WaveformChart', () => {
},
)
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('1000')
})
it('reacts to exact fixed Y-domain props and returns to automatic bounds', async () => {
@@ -312,13 +312,13 @@ describe('WaveformChart', () => {
await wrapper.setProps({ yDomain: [3, 97] })
await flushPromises()
expect(yTickLabels()).toContain('3.00')
expect(yTickLabels()).toContain('97.00')
expect(yTickLabels()).toContain('3')
expect(yTickLabels()).toContain('97')
await wrapper.setProps({ yDomain: undefined })
await flushPromises()
expect(yTickLabels()).not.toContain('3.00')
expect(yTickLabels()).not.toContain('97.00')
expect(yTickLabels()).not.toContain('3')
expect(yTickLabels()).not.toContain('97')
})
it('keeps annotations bound to their channel while paging', async () => {

View File

@@ -30,7 +30,7 @@ describe('WaveformChart', () => {
kind: 'points',
points: [
{ x: 0, y: 0 },
{ x: 1, y: 254 },
{ x: 1, y: 2540 },
],
},
},
@@ -51,13 +51,13 @@ describe('WaveformChart', () => {
)
expect(
wrapper
.findAll('.waveform-chart__axis-exponent--y')
.map((label) => [label.attributes('data-y-axis-index'), label.text()]),
).toEqual([
['1', 'E+02'],
['2', 'E-04'],
])
wrapper.findAll('.waveform-chart__axis--y').map((axis) =>
axis
.findAll('.tick text')
.map((label) => label.text())
.filter((label) => label.startsWith('E')),
),
).toEqual([[], [expect.stringMatching(/^E\+03 /)], [expect.stringMatching(/^E-04 /)]])
})
it('reprojects annotations with the Y axis assigned to their series', async () => {

View File

@@ -54,8 +54,9 @@ describe('WaveformChart', () => {
],
})
expect(first.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
expect(second.get('.waveform-chart__axis-exponent--y').text()).toBe('E+04')
expect(first.get('.waveform-chart__axis--y').text()).not.toContain('E')
expect(second.get('.waveform-chart__axis--y').text()).toContain('E+04 ')
expect(second.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
})
it('renders a configurable zero line only when the Y domain contains zero', async () => {
@@ -299,8 +300,9 @@ describe('WaveformChart', () => {
})
expect(xAxis.get('path.domain').attributes('display')).toBe('none')
expect(wrapper.findAll('.waveform-chart__axis-endpoint')).toHaveLength(2)
expect(wrapper.get('.waveform-chart__axis-exponent--x').text()).not.toBe('')
expect(wrapper.get('.waveform-chart__axis-exponent--y').text()).not.toBe('')
expect(wrapper.find('.waveform-chart__axis-exponent--x').exists()).toBe(false)
expect(yAxes.some((axis) => axis.text().includes('E+04 '))).toBe(true)
expect(wrapper.find('.waveform-chart__axis-exponent--y').exists()).toBe(false)
expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false)
expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
stroke: '#dc2626',

View File

@@ -79,8 +79,8 @@ describe('WaveformChart', () => {
expect(wrapper.emitted('zoom-change')).toBeUndefined()
expect(wrapper.emitted('zoom-end')).toBeUndefined()
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
})
it('ignores independent viewport dragging', async () => {
@@ -333,14 +333,14 @@ describe('WaveformChart', () => {
)
flushAnimationFrames()
await flushPromises()
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe('0.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).not.toBe('0')
overlay.element.dispatchEvent(new MouseEvent('dblclick', { bubbles: true, cancelable: true }))
await flushPromises()
expect(wrapper.emitted('zoom-reset')).toHaveLength(1)
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2.00')
expect(wrapper.get('.waveform-chart__axis-endpoint--start').text()).toBe('0')
expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('2000')
})
it('exposes resetViewport for independent tracks', async () => {
@@ -364,13 +364,13 @@ describe('WaveformChart', () => {
)
flushAnimationFrames()
await flushPromises()
expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).not.toBe('0.00')
expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).not.toBe('0')
const chart = wrapper.vm as unknown as { resetViewport: () => void }
chart.resetViewport()
await flushPromises()
expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0.00')
expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1.00')
expect(wrapper.findAll('.waveform-chart__axis-endpoint--start')[0].text()).toBe('0')
expect(wrapper.findAll('.waveform-chart__axis-endpoint--end')[0].text()).toBe('1000')
})
})

View File

@@ -49,15 +49,15 @@ describe('WaveformChart wheel zoom out', () => {
end: wrapper.get('.waveform-chart__axis-endpoint--end').text(),
})
expect(endpoints()).toEqual({ start: '-5.00', end: '5.00' })
expect(endpoints()).toEqual({ start: '-5000', end: '5000' })
await dispatchWheel(-4000)
expect(endpoints()).not.toEqual({ start: '-5.00', end: '5.00' })
expect(endpoints()).not.toEqual({ start: '-5000', end: '5000' })
await wrapper.setProps({ data: createData(-0.125, 0.125) })
await flushPromises()
await dispatchWheel(4000)
expect(endpoints()).toEqual({ start: '-5.00', end: '5.00' })
expect(endpoints()).toEqual({ start: '-5000', end: '5000' })
})
it('emits one zoom-end payload after zooming a shared viewport out', async () => {