fix(chart): preserve clean view geometry
This commit is contained in:
@@ -297,63 +297,87 @@ describe('WaveformChart', () => {
|
|||||||
expect(zeroLines[0].attributes('y1')).not.toBe(zeroLines[1].attributes('y1'))
|
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 () => {
|
it('preserves a titled multi-axis plot and hides auxiliary layers in clean view', async () => {
|
||||||
const wrapper = await mountSizedChart(
|
const data: WaveformData = {
|
||||||
{
|
kind: 'series',
|
||||||
kind: 'series',
|
series: [
|
||||||
series: [
|
{
|
||||||
{
|
id: 'first',
|
||||||
id: 'first',
|
trackId: 'overlay',
|
||||||
trackId: 'overlay',
|
name: 'first',
|
||||||
name: 'first',
|
data: {
|
||||||
data: {
|
kind: 'points',
|
||||||
kind: 'points',
|
points: [
|
||||||
points: [
|
{ x: 0, y: -1 },
|
||||||
{ x: 0, y: -1 },
|
{ x: 1, y: 1 },
|
||||||
{ x: 1, y: 1 },
|
],
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
id: 'second',
|
{
|
||||||
trackId: 'overlay',
|
id: 'second',
|
||||||
name: 'second',
|
trackId: 'overlay',
|
||||||
data: {
|
name: 'second',
|
||||||
kind: 'points',
|
data: {
|
||||||
points: [
|
kind: 'points',
|
||||||
{ x: 0, y: 1 },
|
points: [
|
||||||
{ x: 1, y: 2 },
|
{ x: 0, y: 1 },
|
||||||
],
|
{ x: 1, y: 2 },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
id: 'third',
|
{
|
||||||
name: 'third',
|
id: 'third',
|
||||||
data: {
|
name: 'third',
|
||||||
kind: 'points',
|
data: {
|
||||||
points: [
|
kind: 'points',
|
||||||
{ x: 0, y: 2 },
|
points: [
|
||||||
{ x: 1, y: 3 },
|
{ x: 0, y: 2 },
|
||||||
],
|
{ x: 1, y: 3 },
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
],
|
},
|
||||||
},
|
],
|
||||||
{
|
}
|
||||||
cleanView: true,
|
const sharedProps = {
|
||||||
grid: { rowCount: 1, columnCount: 1, showPagination: true },
|
grid: { rowCount: 1, columnCount: 1, showPagination: true },
|
||||||
title: { text: 'hidden title' },
|
overlayMode: 'multi-axis' as const,
|
||||||
frameNumber: 1,
|
frameNumber: 1,
|
||||||
annotations: [{ id: 'note', seriesId: 'first', x: 0.5, y: 0, text: 'hidden note' }],
|
annotations: [{ id: 'note', seriesId: 'first', x: 0.5, y: 0, text: 'hidden note' }],
|
||||||
zeroLine: { visible: true },
|
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')
|
const regularTrack = regularWrapper.get('.waveform-chart__track')
|
||||||
expect(wrapper.get('.waveform-chart__track').attributes('data-track-height')).toBe('360')
|
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.findAll('.waveform-chart__series')).toHaveLength(2)
|
||||||
expect(wrapper.find('.waveform-chart__overlay--independent').exists()).toBe(true)
|
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__axis').exists()).toBe(false)
|
||||||
expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false)
|
expect(wrapper.find('.waveform-chart__grid').exists()).toBe(false)
|
||||||
expect(wrapper.find('.waveform-chart__plot-frame').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)
|
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 () => {
|
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 lineTypes = ['step-start', 'step-middle', 'step-end', 'step-after'] as const
|
||||||
const wrapper = await mountSizedChart({
|
const wrapper = await mountSizedChart({
|
||||||
|
|||||||
@@ -288,13 +288,11 @@ const legendOrientation = computed<Exclude<WaveformLegendOrientation, 'auto'>>((
|
|||||||
: 'vertical'
|
: 'vertical'
|
||||||
})
|
})
|
||||||
const resolvedTitleText = computed(() => props.title?.text.trim() ?? '')
|
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 titleFontSize = computed(() => {
|
||||||
const fontSize = props.title?.textStyle?.fontSize
|
const fontSize = props.title?.textStyle?.fontSize
|
||||||
return Number.isFinite(fontSize) && (fontSize ?? 0) > 0 ? (fontSize as number) : 14
|
return Number.isFinite(fontSize) && (fontSize ?? 0) > 0 ? (fontSize as number) : 14
|
||||||
@@ -343,12 +341,10 @@ const titleLayout = computed(() =>
|
|||||||
rotation: titleRotation.value,
|
rotation: titleRotation.value,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const titleAreaHeight = computed(() => (titleVisible.value ? titleLayout.value.areaHeight : 0))
|
const titleAreaHeight = computed(() => (titleAreaReserved.value ? titleLayout.value.areaHeight : 0))
|
||||||
const chartTopMargin = computed(() => (isCleanView.value ? 0 : margin.top))
|
const chartTopMargin = computed(() => margin.top)
|
||||||
const drawingHeight = computed(() => Math.max(0, chartHeight.value - titleAreaHeight.value))
|
const drawingHeight = computed(() => Math.max(0, chartHeight.value - titleAreaHeight.value))
|
||||||
const innerHeight = computed(() =>
|
const innerHeight = computed(() => Math.max(0, drawingHeight.value - margin.top - margin.bottom))
|
||||||
Math.max(0, drawingHeight.value - (isCleanView.value ? 0 : margin.top + margin.bottom)),
|
|
||||||
)
|
|
||||||
const titleAreaStyle = computed<CSSProperties>(() => ({
|
const titleAreaStyle = computed<CSSProperties>(() => ({
|
||||||
height: `${titleAreaHeight.value}px`,
|
height: `${titleAreaHeight.value}px`,
|
||||||
justifyContent:
|
justifyContent:
|
||||||
@@ -461,7 +457,7 @@ const hasVisibleWaveformData = computed(() =>
|
|||||||
)
|
)
|
||||||
const chartLeftMargin = computed(() =>
|
const chartLeftMargin = computed(() =>
|
||||||
Math.max(
|
Math.max(
|
||||||
isCleanView.value ? 0 : margin.left,
|
margin.left,
|
||||||
hasYAxisLabels.value
|
hasYAxisLabels.value
|
||||||
? yAxisMetrics.value.fullClearance
|
? yAxisMetrics.value.fullClearance
|
||||||
: hasVisibleWaveformData.value
|
: hasVisibleWaveformData.value
|
||||||
@@ -482,20 +478,14 @@ const multiAxisClearance = computed(() =>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
const resolvedChartLeftMargin = computed(() =>
|
const resolvedChartLeftMargin = computed(() =>
|
||||||
isCleanView.value
|
props.overlayMode === 'multi-axis'
|
||||||
? 0
|
? Math.max(chartLeftMargin.value, multiAxisClearance.value.left)
|
||||||
: props.overlayMode === 'multi-axis'
|
: chartLeftMargin.value,
|
||||||
? Math.max(chartLeftMargin.value, multiAxisClearance.value.left)
|
|
||||||
: chartLeftMargin.value,
|
|
||||||
)
|
)
|
||||||
const chartRightMargin = computed(() =>
|
const chartRightMargin = computed(() =>
|
||||||
props.overlayMode === 'multi-axis'
|
props.overlayMode === 'multi-axis'
|
||||||
? isCleanView.value
|
? Math.max(margin.right, multiAxisClearance.value.right)
|
||||||
? 0
|
: margin.right,
|
||||||
: Math.max(margin.right, multiAxisClearance.value.right)
|
|
||||||
: isCleanView.value
|
|
||||||
? 0
|
|
||||||
: margin.right,
|
|
||||||
)
|
)
|
||||||
const innerWidth = computed(() =>
|
const innerWidth = computed(() =>
|
||||||
Math.max(0, chartWidth.value - resolvedChartLeftMargin.value - chartRightMargin.value),
|
Math.max(0, chartWidth.value - resolvedChartLeftMargin.value - chartRightMargin.value),
|
||||||
@@ -595,7 +585,6 @@ const gridCells = computed(() => {
|
|||||||
props.displayMode,
|
props.displayMode,
|
||||||
pagedTracks.value.map(Boolean),
|
pagedTracks.value.map(Boolean),
|
||||||
yAxisLayout.value.horizontalGap,
|
yAxisLayout.value.horizontalGap,
|
||||||
!isCleanView.value,
|
|
||||||
)
|
)
|
||||||
return cells.map((cell, index) => ({ ...cell, series: pagedTracks.value[index] }))
|
return cells.map((cell, index) => ({ ...cell, series: pagedTracks.value[index] }))
|
||||||
})
|
})
|
||||||
@@ -1763,7 +1752,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function measureTitle() {
|
function measureTitle() {
|
||||||
if (!titleVisible.value || !titleMeasureElement.value) {
|
if (!titleAreaReserved.value || !titleMeasureElement.value) {
|
||||||
measuredTitleWidth.value = 0
|
measuredTitleWidth.value = 0
|
||||||
measuredTitleHeight.value = 0
|
measuredTitleHeight.value = 0
|
||||||
return
|
return
|
||||||
@@ -1774,7 +1763,7 @@ function measureTitle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[resolvedTitleText, titleVisible, titleMeasureStyle],
|
[resolvedTitleText, titleAreaReserved, titleMeasureStyle],
|
||||||
async () => {
|
async () => {
|
||||||
measuredTitleWidth.value = 0
|
measuredTitleWidth.value = 0
|
||||||
measuredTitleHeight.value = 0
|
measuredTitleHeight.value = 0
|
||||||
@@ -1813,7 +1802,10 @@ onBeforeUnmount(() => {
|
|||||||
:class="[
|
:class="[
|
||||||
`waveform-chart--${displayMode}`,
|
`waveform-chart--${displayMode}`,
|
||||||
`waveform-chart--interaction-${activeInteractionMode}`,
|
`waveform-chart--interaction-${activeInteractionMode}`,
|
||||||
{ 'waveform-chart--panning': selection?.mode === 'pan' },
|
{
|
||||||
|
'waveform-chart--clean': isCleanView,
|
||||||
|
'waveform-chart--panning': selection?.mode === 'pan',
|
||||||
|
},
|
||||||
]"
|
]"
|
||||||
:style="containerStyle"
|
:style="containerStyle"
|
||||||
:data-display-mode="displayMode"
|
:data-display-mode="displayMode"
|
||||||
@@ -1824,11 +1816,12 @@ onBeforeUnmount(() => {
|
|||||||
@contextmenu.capture="handleNativeContextMenu"
|
@contextmenu.capture="handleNativeContextMenu"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="titleVisible"
|
v-if="titleAreaReserved"
|
||||||
class="waveform-chart__title-area"
|
class="waveform-chart__title-area"
|
||||||
:style="titleAreaStyle"
|
:style="titleAreaStyle"
|
||||||
role="heading"
|
:role="titleVisible ? 'heading' : undefined"
|
||||||
aria-level="2"
|
:aria-level="titleVisible ? 2 : undefined"
|
||||||
|
:aria-hidden="isCleanView || undefined"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
ref="titleMeasureElement"
|
ref="titleMeasureElement"
|
||||||
@@ -1838,7 +1831,7 @@ onBeforeUnmount(() => {
|
|||||||
>
|
>
|
||||||
{{ resolvedTitleText }}
|
{{ resolvedTitleText }}
|
||||||
</span>
|
</span>
|
||||||
<span class="waveform-chart__title-visual" :style="titleVisualStyle">
|
<span v-if="titleVisible" class="waveform-chart__title-visual" :style="titleVisualStyle">
|
||||||
<span
|
<span
|
||||||
class="waveform-chart__title-text"
|
class="waveform-chart__title-text"
|
||||||
:style="titleTextStyle"
|
:style="titleTextStyle"
|
||||||
@@ -2069,6 +2062,10 @@ onBeforeUnmount(() => {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.waveform-chart--clean {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.waveform-chart__pagination {
|
.waveform-chart__pagination {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
|||||||
Reference in New Issue
Block a user