From 4643a2dbfec2627d38dc3a97e7694af44108b80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=90=AF=E6=BA=90?= <1284082502@qq.com> Date: Tue, 21 Jul 2026 22:26:34 +0800 Subject: [PATCH] fix(chart): preserve clean view geometry --- src/components/WaveformChart.test.ts | 150 ++++++++++++++++++--------- src/components/WaveformChart.vue | 57 +++++----- 2 files changed, 126 insertions(+), 81 deletions(-) diff --git a/src/components/WaveformChart.test.ts b/src/components/WaveformChart.test.ts index 6953de3..fda4efb 100644 --- a/src/components/WaveformChart.test.ts +++ b/src/components/WaveformChart.test.ts @@ -297,63 +297,87 @@ describe('WaveformChart', () => { expect(zeroLines[0].attributes('y1')).not.toBe(zeroLines[1].attributes('y1')) }) - it('uses the full drawing area and hides auxiliary layers in clean view', async () => { - const wrapper = await mountSizedChart( - { - kind: 'series', - series: [ - { - id: 'first', - trackId: 'overlay', - name: 'first', - data: { - kind: 'points', - points: [ - { x: 0, y: -1 }, - { x: 1, y: 1 }, - ], - }, + it('preserves a titled multi-axis plot and hides auxiliary layers in clean view', async () => { + const data: WaveformData = { + kind: 'series', + series: [ + { + id: 'first', + trackId: 'overlay', + name: 'first', + data: { + kind: 'points', + points: [ + { x: 0, y: -1 }, + { x: 1, y: 1 }, + ], }, - { - id: 'second', - trackId: 'overlay', - name: 'second', - data: { - kind: 'points', - points: [ - { x: 0, y: 1 }, - { x: 1, y: 2 }, - ], - }, + }, + { + id: 'second', + trackId: 'overlay', + name: 'second', + data: { + kind: 'points', + points: [ + { x: 0, y: 1 }, + { x: 1, y: 2 }, + ], }, - { - id: 'third', - name: 'third', - data: { - kind: 'points', - points: [ - { x: 0, y: 2 }, - { x: 1, y: 3 }, - ], - }, + }, + { + id: 'third', + name: 'third', + data: { + kind: 'points', + points: [ + { x: 0, y: 2 }, + { x: 1, y: 3 }, + ], }, - ], - }, - { - cleanView: true, - grid: { rowCount: 1, columnCount: 1, showPagination: true }, - title: { text: 'hidden title' }, - frameNumber: 1, - annotations: [{ id: 'note', seriesId: 'first', x: 0.5, y: 0, text: 'hidden note' }], - zeroLine: { visible: true }, - }, - ) + }, + ], + } + const sharedProps = { + grid: { rowCount: 1, columnCount: 1, showPagination: true }, + overlayMode: 'multi-axis' as const, + frameNumber: 1, + annotations: [{ id: 'note', seriesId: 'first', x: 0.5, y: 0, text: 'hidden note' }], + zeroLine: { visible: true }, + title: { text: 'hidden title' }, + } + const regularWrapper = await mountSizedChart(data, sharedProps) + const wrapper = await mountSizedChart(data, { + ...sharedProps, + cleanView: true, + }) - expect(wrapper.get('.waveform-chart').attributes('data-chart-left-margin')).toBe('0') - expect(wrapper.get('.waveform-chart__track').attributes('data-track-height')).toBe('360') + const regularTrack = regularWrapper.get('.waveform-chart__track') + const cleanTrack = wrapper.get('.waveform-chart__track') + const geometryAttributes = [ + 'data-track-left', + 'data-track-top', + 'data-track-width', + 'data-track-height', + ] + + expect(wrapper.get('.waveform-chart').attributes('data-chart-left-margin')).toBe( + regularWrapper.get('.waveform-chart').attributes('data-chart-left-margin'), + ) + expect(wrapper.get('.waveform-chart').attributes('data-title-area-height')).toBe( + regularWrapper.get('.waveform-chart').attributes('data-title-area-height'), + ) + geometryAttributes.forEach((attribute) => { + expect(cleanTrack.attributes(attribute)).toBe(regularTrack.attributes(attribute)) + }) + expect(wrapper.get('.waveform-chart').classes()).toContain('waveform-chart--clean') + expect(getComputedStyle(wrapper.get('.waveform-chart').element).borderColor).toBe( + 'rgba(0, 0, 0, 0)', + ) expect(wrapper.findAll('.waveform-chart__series')).toHaveLength(2) expect(wrapper.find('.waveform-chart__overlay--independent').exists()).toBe(true) - expect(wrapper.find('.waveform-chart__title-area').exists()).toBe(false) + expect(wrapper.get('.waveform-chart__title-area').attributes('aria-hidden')).toBe('true') + expect(wrapper.find('.waveform-chart__title-visual').exists()).toBe(false) expect(wrapper.find('.waveform-chart__axis').exists()).toBe(false) expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false) expect(wrapper.find('.waveform-chart__plot-frame').exists()).toBe(false) @@ -366,6 +390,30 @@ describe('WaveformChart', () => { expect(wrapper.find('.ant-pagination').exists()).toBe(false) }) + it('preserves every track geometry in a multi-column clean view', async () => { + const props = { + displayMode: 'independent' as const, + grid: { rowCount: 2, columnCount: 2 }, + } + const regularWrapper = await mountSizedChart(gridSeries(4), props) + const cleanWrapper = await mountSizedChart(gridSeries(4), { ...props, cleanView: true }) + const geometryAttributes = [ + 'data-track-left', + 'data-track-top', + 'data-track-width', + 'data-track-height', + ] + const regularTracks = regularWrapper.findAll('.waveform-chart__track') + const cleanTracks = cleanWrapper.findAll('.waveform-chart__track') + + expect(cleanTracks).toHaveLength(regularTracks.length) + cleanTracks.forEach((track, index) => { + geometryAttributes.forEach((attribute) => { + expect(track.attributes(attribute)).toBe(regularTracks[index].attributes(attribute)) + }) + }) + }) + it('places start, middle, and end step transitions at the expected X positions', async () => { const lineTypes = ['step-start', 'step-middle', 'step-end', 'step-after'] as const const wrapper = await mountSizedChart({ diff --git a/src/components/WaveformChart.vue b/src/components/WaveformChart.vue index 6ab0ec4..1b6fbbf 100644 --- a/src/components/WaveformChart.vue +++ b/src/components/WaveformChart.vue @@ -288,13 +288,11 @@ const legendOrientation = computed>(( : 'vertical' }) const resolvedTitleText = computed(() => props.title?.text.trim() ?? '') -const titleVisible = computed( +const titleAreaReserved = computed( () => - !isCleanView.value && - Boolean(props.title) && - props.title?.visible !== false && - resolvedTitleText.value.length > 0, + Boolean(props.title) && props.title?.visible !== false && resolvedTitleText.value.length > 0, ) +const titleVisible = computed(() => titleAreaReserved.value && !isCleanView.value) const titleFontSize = computed(() => { const fontSize = props.title?.textStyle?.fontSize return Number.isFinite(fontSize) && (fontSize ?? 0) > 0 ? (fontSize as number) : 14 @@ -343,12 +341,10 @@ const titleLayout = computed(() => rotation: titleRotation.value, }), ) -const titleAreaHeight = computed(() => (titleVisible.value ? titleLayout.value.areaHeight : 0)) -const chartTopMargin = computed(() => (isCleanView.value ? 0 : margin.top)) +const titleAreaHeight = computed(() => (titleAreaReserved.value ? titleLayout.value.areaHeight : 0)) +const chartTopMargin = computed(() => margin.top) const drawingHeight = computed(() => Math.max(0, chartHeight.value - titleAreaHeight.value)) -const innerHeight = computed(() => - Math.max(0, drawingHeight.value - (isCleanView.value ? 0 : margin.top + margin.bottom)), -) +const innerHeight = computed(() => Math.max(0, drawingHeight.value - margin.top - margin.bottom)) const titleAreaStyle = computed(() => ({ height: `${titleAreaHeight.value}px`, justifyContent: @@ -461,7 +457,7 @@ const hasVisibleWaveformData = computed(() => ) const chartLeftMargin = computed(() => Math.max( - isCleanView.value ? 0 : margin.left, + margin.left, hasYAxisLabels.value ? yAxisMetrics.value.fullClearance : hasVisibleWaveformData.value @@ -482,20 +478,14 @@ const multiAxisClearance = computed(() => ), ) const resolvedChartLeftMargin = computed(() => - isCleanView.value - ? 0 - : props.overlayMode === 'multi-axis' - ? Math.max(chartLeftMargin.value, multiAxisClearance.value.left) - : chartLeftMargin.value, + props.overlayMode === 'multi-axis' + ? Math.max(chartLeftMargin.value, multiAxisClearance.value.left) + : chartLeftMargin.value, ) const chartRightMargin = computed(() => props.overlayMode === 'multi-axis' - ? isCleanView.value - ? 0 - : Math.max(margin.right, multiAxisClearance.value.right) - : isCleanView.value - ? 0 - : margin.right, + ? Math.max(margin.right, multiAxisClearance.value.right) + : margin.right, ) const innerWidth = computed(() => Math.max(0, chartWidth.value - resolvedChartLeftMargin.value - chartRightMargin.value), @@ -595,7 +585,6 @@ const gridCells = computed(() => { props.displayMode, pagedTracks.value.map(Boolean), yAxisLayout.value.horizontalGap, - !isCleanView.value, ) return cells.map((cell, index) => ({ ...cell, series: pagedTracks.value[index] })) }) @@ -1763,7 +1752,7 @@ watch( ) function measureTitle() { - if (!titleVisible.value || !titleMeasureElement.value) { + if (!titleAreaReserved.value || !titleMeasureElement.value) { measuredTitleWidth.value = 0 measuredTitleHeight.value = 0 return @@ -1774,7 +1763,7 @@ function measureTitle() { } watch( - [resolvedTitleText, titleVisible, titleMeasureStyle], + [resolvedTitleText, titleAreaReserved, titleMeasureStyle], async () => { measuredTitleWidth.value = 0 measuredTitleHeight.value = 0 @@ -1813,7 +1802,10 @@ onBeforeUnmount(() => { :class="[ `waveform-chart--${displayMode}`, `waveform-chart--interaction-${activeInteractionMode}`, - { 'waveform-chart--panning': selection?.mode === 'pan' }, + { + 'waveform-chart--clean': isCleanView, + 'waveform-chart--panning': selection?.mode === 'pan', + }, ]" :style="containerStyle" :data-display-mode="displayMode" @@ -1824,11 +1816,12 @@ onBeforeUnmount(() => { @contextmenu.capture="handleNativeContextMenu" >
{ > {{ resolvedTitleText }} - + { border-radius: 6px; } +.waveform-chart--clean { + border-color: transparent; +} + .waveform-chart__pagination { position: absolute; right: 10px;