fix(chart): refine axis number formatting
All checks were successful
Package component / package (push) Successful in 4m2s
All checks were successful
Package component / package (push) Successful in 4m2s
This commit is contained in:
@@ -192,7 +192,7 @@ describe('multi-value Y-axis grouping', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('places left and right scientific exponents eight pixels outside their tick labels', () => {
|
||||
it('reserves label clearance for exponent-prefixed ticks on both axis sides', () => {
|
||||
const layout = buildTrackLayouts({
|
||||
cells: [
|
||||
{
|
||||
@@ -206,7 +206,7 @@ describe('multi-value Y-axis grouping', () => {
|
||||
plotHeight: 300,
|
||||
cellHeight: 330,
|
||||
xAxisBand: 30,
|
||||
series: track([series('left', 0, 254), series('right', 0, 254)]),
|
||||
series: track([series('left', 1000, 3000), series('right', 1000, 3000)]),
|
||||
},
|
||||
],
|
||||
grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
|
||||
@@ -222,14 +222,13 @@ describe('multi-value Y-axis grouping', () => {
|
||||
})[0]
|
||||
|
||||
expect(
|
||||
layout?.yAxes.map(({ side, x, exponentX, exponentLabel }) => ({
|
||||
layout?.yAxes.map(({ side, x, labelX }) => ({
|
||||
side,
|
||||
offset: Math.abs(exponentX - x),
|
||||
exponentLabel,
|
||||
labelOffset: Math.abs(labelX - x),
|
||||
})),
|
||||
).toEqual([
|
||||
{ side: 'left', offset: 43, exponentLabel: 'E+02' },
|
||||
{ side: 'right', offset: 43, exponentLabel: 'E+02' },
|
||||
{ side: 'left', labelOffset: 67 },
|
||||
{ side: 'right', labelOffset: 67 },
|
||||
])
|
||||
})
|
||||
|
||||
@@ -237,7 +236,7 @@ describe('multi-value Y-axis grouping', () => {
|
||||
const [group] = buildYAxisSeriesGroups(track([series('long', -1e120, 1e120)]), 'multi-axis')
|
||||
|
||||
expect(group).toBeDefined()
|
||||
expect(measureYAxisGroupClearance(group!)).toBe(119)
|
||||
expect(measureYAxisGroupClearance(group!)).toBe(90)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { scaleLinear } from 'd3'
|
||||
|
||||
import type { WaveformOverlayMode } from '../../types'
|
||||
import { formatScientificAxisExponent, formatScientificAxisLabel, paddedDomain } from '../../utils'
|
||||
import { formatScientificAxisLabel, paddedDomain } from '../../utils'
|
||||
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
||||
import { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
||||
import { MAX_MULTI_Y_AXIS_COUNT } from './constants'
|
||||
import {
|
||||
mergeYDomains,
|
||||
resolveSeriesFixedYDomain,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from './yDomain'
|
||||
|
||||
// 导出常量供外部使用
|
||||
export { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
||||
export { MAX_MULTI_Y_AXIS_COUNT } from './constants'
|
||||
|
||||
const Y_AXIS_CHARACTER_WIDTH = 7
|
||||
const Y_AXIS_TICK_PADDING = 7
|
||||
@@ -102,36 +102,27 @@ export function resolveYAxisSeriesGroups(
|
||||
export function axisTextMetrics(
|
||||
domain: [number, number],
|
||||
nice = true,
|
||||
): {
|
||||
exponentLabel: string | null
|
||||
exponentWidth: number
|
||||
tickTextWidth: number
|
||||
} {
|
||||
tickValues?: number[],
|
||||
): { tickTextWidth: number } {
|
||||
const scale = scaleLinear(domain, [1, 0])
|
||||
if (nice) scale.nice()
|
||||
const [axisMin, axisMax] = scale.domain()
|
||||
const values = scale.ticks(10)
|
||||
const values = tickValues ?? Array.from(new Set([axisMin, ...scale.ticks(10), axisMax]))
|
||||
const topTickValue = Math.max(...values)
|
||||
const maximumTickCharacters = Math.max(
|
||||
1,
|
||||
...values.map((value) => formatScientificAxisLabel(value, { axisMin, axisMax }).length),
|
||||
...values.map(
|
||||
(value) => formatScientificAxisLabel(value, { axisMin, axisMax, topTickValue }).length,
|
||||
),
|
||||
)
|
||||
const exponentLabel = formatScientificAxisExponent(axisMin, axisMax)
|
||||
return {
|
||||
exponentLabel,
|
||||
exponentWidth: exponentLabel ? exponentLabel.length * Y_AXIS_CHARACTER_WIDTH : 0,
|
||||
tickTextWidth: maximumTickCharacters * Y_AXIS_CHARACTER_WIDTH,
|
||||
}
|
||||
}
|
||||
|
||||
function axisExponentClearance(domain: [number, number], nice: boolean): number {
|
||||
const { exponentLabel, exponentWidth } = axisTextMetrics(domain, nice)
|
||||
return exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
||||
}
|
||||
|
||||
export function measureYAxisGroupClearance(group: YAxisSeriesGroup): number {
|
||||
return (
|
||||
axisTextMetrics(group.domain, !group.fixed).tickTextWidth +
|
||||
axisExponentClearance(group.domain, !group.fixed) +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH +
|
||||
@@ -142,7 +133,6 @@ export function measureYAxisGroupClearance(group: YAxisSeriesGroup): number {
|
||||
function measureYAxisGroupTickClearance(group: YAxisSeriesGroup): number {
|
||||
return (
|
||||
axisTextMetrics(group.domain, !group.fixed).tickTextWidth +
|
||||
axisExponentClearance(group.domain, !group.fixed) +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type ResolvedWaveformRenderingOptions,
|
||||
} from '../../core/rendering'
|
||||
import type { WaveformDisplayMode, WaveformOverlayMode, WaveformPoint } from '../../types'
|
||||
import { buildMinorTicks, formatAxisTimeExponent, formatEndpointTime } from '../../utils'
|
||||
import { buildMinorTicks, formatEndpointTime } from '../../utils'
|
||||
import {
|
||||
getBottomRowCellIndexes,
|
||||
type GridCellGeometry,
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
} from './grid'
|
||||
import { axisTextMetrics, resolveYAxisSeriesGroups } from './layout'
|
||||
import type { DisplaySeries, DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
||||
import { Y_AXIS_EXPONENT_GAP } from './constants'
|
||||
import {
|
||||
Y_AXIS_LABEL_BAND_WIDTH,
|
||||
Y_AXIS_LABEL_GAP,
|
||||
@@ -118,31 +117,16 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
const tickValues = Array.from(
|
||||
new Set([axisStart, ...visibleMajorTicks, ...(showAxisEnd ? [axisEnd] : [])]),
|
||||
)
|
||||
const { exponentLabel, exponentWidth, tickTextWidth } = axisTextMetrics(
|
||||
group.domain,
|
||||
!group.fixed,
|
||||
)
|
||||
const exponentClearance = exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
||||
const { tickTextWidth } = axisTextMetrics(group.domain, !group.fixed, tickValues)
|
||||
const clearance =
|
||||
tickTextWidth +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
exponentClearance +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
const x = group.side === 'left' ? -sideOffsets.left : cell.width + sideOffsets.right
|
||||
const exponentX =
|
||||
x +
|
||||
(group.side === 'left'
|
||||
? -(Y_AXIS_TICK_PADDING + tickTextWidth + Y_AXIS_EXPONENT_GAP)
|
||||
: Y_AXIS_TICK_PADDING + tickTextWidth + Y_AXIS_EXPONENT_GAP)
|
||||
const labelDistance =
|
||||
tickTextWidth +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
exponentClearance +
|
||||
exponentWidth +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH / 2
|
||||
tickTextWidth + Y_AXIS_TICK_PADDING + Y_AXIS_LABEL_GAP + Y_AXIS_LABEL_BAND_WIDTH / 2
|
||||
const labelX = x + (group.side === 'left' ? -labelDistance : labelDistance)
|
||||
sideOffsets[group.side] += clearance
|
||||
return {
|
||||
@@ -150,8 +134,6 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
side: group.side,
|
||||
x,
|
||||
labelX,
|
||||
exponentX,
|
||||
exponentLabel,
|
||||
scale,
|
||||
majorTicks,
|
||||
minorTicks: buildMinorTicks(majorTicks),
|
||||
@@ -168,7 +150,6 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
start: formatEndpointTime(domain[0], domain, options.timeUnit),
|
||||
end: formatEndpointTime(domain[1], domain, options.timeUnit),
|
||||
}
|
||||
const xAxisExponent = formatAxisTimeExponent(domain, options.timeUnit)
|
||||
const leftClearance = endpointLabels.start.length * 7 + 10
|
||||
const rightClearance = endpointLabels.end.length * 7 + 10
|
||||
const xAxisTickValues = xMajorTicks.filter((tick) => {
|
||||
@@ -235,7 +216,6 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
yAxisTickValues,
|
||||
xAxisTickValues,
|
||||
endpointLabels,
|
||||
xAxisExponent,
|
||||
path: seriesPaths[0]?.path ?? null,
|
||||
seriesPaths,
|
||||
gridLines: options.grid.trackLines[displayTrack.id] ?? {
|
||||
|
||||
@@ -51,8 +51,6 @@ export interface WaveformYAxisLayout {
|
||||
side: 'left' | 'right'
|
||||
x: number
|
||||
labelX: number
|
||||
exponentX: number
|
||||
exponentLabel: string | null
|
||||
scale: ScaleLinear<number, number>
|
||||
majorTicks: number[]
|
||||
minorTicks: number[]
|
||||
@@ -109,7 +107,6 @@ export interface TrackLayout {
|
||||
yAxisTickValues: number[]
|
||||
xAxisTickValues: number[]
|
||||
endpointLabels: { start: string; end: string }
|
||||
xAxisExponent: string | null
|
||||
path: string | null
|
||||
seriesPaths: TrackSeriesPath[]
|
||||
showXAxis: boolean
|
||||
|
||||
@@ -2,7 +2,7 @@ import { scaleLinear, type ZoomTransform } from 'd3'
|
||||
import { computed, type ComputedRef, type Ref, type ShallowRef } from 'vue'
|
||||
|
||||
import { resolveWaveformRenderingOptions } from '../../core'
|
||||
import { formatScientificAxisExponent, formatScientificAxisLabel, paddedDomain } from '../../utils'
|
||||
import { paddedDomain } from '../../utils'
|
||||
import {
|
||||
layoutAnnotations,
|
||||
type AnnotationSeriesInfo,
|
||||
@@ -28,9 +28,9 @@ import {
|
||||
} from './grid'
|
||||
import {
|
||||
buildTrackLayouts,
|
||||
axisTextMetrics,
|
||||
measureTrackYAxisClearance,
|
||||
resolveYAxisSeriesGroups,
|
||||
Y_AXIS_EXPONENT_GAP,
|
||||
} from './layout'
|
||||
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
||||
import type { PreparedWaveformSeries } from './useWaveformData'
|
||||
@@ -112,40 +112,18 @@ export function useWaveformLayout(context: LayoutContext) {
|
||||
.flatMap((track) =>
|
||||
resolveYAxisSeriesGroups(track, props.overlayMode, props.yDomain, props.yDomains),
|
||||
)
|
||||
.map((group) => {
|
||||
const scale = scaleLinear(group.domain, [1, 0])
|
||||
if (!group.fixed) scale.nice()
|
||||
const [axisMin, axisMax] = scale.domain()
|
||||
return {
|
||||
exponentLabel: formatScientificAxisExponent(axisMin, axisMax),
|
||||
tickLabels: scale
|
||||
.ticks(10)
|
||||
.map((value) => formatScientificAxisLabel(value, { axisMin, axisMax })),
|
||||
}
|
||||
})
|
||||
const maximumCharacters = Math.max(
|
||||
1,
|
||||
...axisText.flatMap(({ tickLabels }) => tickLabels).map((label) => label.length),
|
||||
)
|
||||
const tickTextWidth = maximumCharacters * Y_AXIS_CHARACTER_WIDTH
|
||||
const maximumExponentWidth = Math.max(
|
||||
0,
|
||||
...axisText.map(({ exponentLabel }) => (exponentLabel?.length ?? 0) * Y_AXIS_CHARACTER_WIDTH),
|
||||
)
|
||||
const exponentClearance = maximumExponentWidth ? maximumExponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
||||
const tickClearance =
|
||||
tickTextWidth + Y_AXIS_TICK_PADDING + exponentClearance + Y_AXIS_OUTER_PADDING
|
||||
.map((group) => axisTextMetrics(group.domain, !group.fixed).tickTextWidth)
|
||||
const tickTextWidth = Math.max(Y_AXIS_CHARACTER_WIDTH, ...axisText)
|
||||
const tickClearance = tickTextWidth + Y_AXIS_TICK_PADDING + Y_AXIS_OUTER_PADDING
|
||||
const labelCenterX = -(
|
||||
Y_AXIS_TICK_PADDING +
|
||||
tickTextWidth +
|
||||
exponentClearance +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH / 2
|
||||
)
|
||||
const fullClearance =
|
||||
tickTextWidth +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
exponentClearance +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
|
||||
@@ -91,6 +91,22 @@ describe('WaveformTooltip', () => {
|
||||
expect(wrapper.get('.waveform-tooltip__value').text()).toBe('-1,405.4932 A')
|
||||
})
|
||||
|
||||
it('formats tooltip time with at most four decimal places', () => {
|
||||
const wrapper = mount(WaveformTooltip, {
|
||||
props: {
|
||||
visible: true,
|
||||
position: { x: 10, y: 10 },
|
||||
timeUnit: 's',
|
||||
hoveredPoint: { x: 1.234567, y: 12 },
|
||||
seriesPoints: [],
|
||||
containerWidth: 400,
|
||||
containerHeight: 300,
|
||||
},
|
||||
})
|
||||
|
||||
expect(wrapper.get('.waveform-tooltip__time').text()).toBe('s: 1.2346')
|
||||
})
|
||||
|
||||
it('omits the error label when both resolved errors are zero', () => {
|
||||
const point = { x: 1, y: 12 }
|
||||
const wrapper = mount(WaveformTooltip, {
|
||||
|
||||
@@ -43,8 +43,11 @@ function renderAxes() {
|
||||
const element = yAxisElements.value[index]
|
||||
if (!element) return
|
||||
const [axisMin, axisMax] = axis.scale.domain()
|
||||
const topTickValue = Math.max(...axis.tickValues)
|
||||
const yAxis = (axis.side === 'left' ? axisLeft(axis.scale) : axisRight(axis.scale))
|
||||
.tickFormat((value) => formatScientificAxisLabel(Number(value), { axisMin, axisMax }))
|
||||
.tickFormat((value) =>
|
||||
formatScientificAxisLabel(Number(value), { axisMin, axisMax, topTickValue }),
|
||||
)
|
||||
.tickSize(-4)
|
||||
.tickPadding(7)
|
||||
.tickSizeOuter(0)
|
||||
@@ -136,17 +139,6 @@ watch(
|
||||
{{ track.endpointLabels.end }}
|
||||
</text>
|
||||
</g>
|
||||
<text
|
||||
v-if="track.showXAxis && track.xAxisExponent"
|
||||
class="waveform-track__axis-exponent waveform-track__axis-exponent--x waveform-chart__axis-exponent waveform-chart__axis-exponent--x"
|
||||
:x="track.width ?? innerWidth"
|
||||
:y="track.height + 27"
|
||||
text-anchor="end"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ track.xAxisExponent }}
|
||||
</text>
|
||||
|
||||
<g
|
||||
v-for="axis in track.isEmpty ? [] : track.yAxes"
|
||||
:key="`y-axis-${track.index}-${axis.index}`"
|
||||
@@ -160,20 +152,6 @@ watch(
|
||||
:data-y-axis-side="axis.side"
|
||||
:transform="`translate(${axis.x}, 0)`"
|
||||
/>
|
||||
<text
|
||||
v-for="axis in track.isEmpty ? [] : track.yAxes.filter((item) => item.exponentLabel)"
|
||||
:key="`y-axis-exponent-${track.index}-${axis.index}`"
|
||||
class="waveform-track__axis-exponent waveform-track__axis-exponent--y waveform-chart__axis-exponent waveform-chart__axis-exponent--y"
|
||||
:data-y-axis-index="axis.index"
|
||||
:x="axis.exponentX"
|
||||
y="0"
|
||||
dy="0.32em"
|
||||
:text-anchor="axis.side === 'left' ? 'end' : 'start'"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{{ axis.exponentLabel }}
|
||||
</text>
|
||||
|
||||
<g
|
||||
v-if="
|
||||
!cleanView &&
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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+$/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user