feat(chart): support configurable plot margins
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "waveform-analysis",
|
"name": "waveform-analysis",
|
||||||
"version": "0.1.29",
|
"version": "0.1.30",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
"types": "./dist/types/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
|
|||||||
10
src/App.vue
10
src/App.vue
@@ -14,6 +14,7 @@ import {
|
|||||||
type WaveformLegendOrientation,
|
type WaveformLegendOrientation,
|
||||||
type WaveformLegendPosition,
|
type WaveformLegendPosition,
|
||||||
type WaveformOverlayMode,
|
type WaveformOverlayMode,
|
||||||
|
type WaveformPlotMargin,
|
||||||
type WaveformTitleOptions,
|
type WaveformTitleOptions,
|
||||||
type WaveformZoomEndPayload,
|
type WaveformZoomEndPayload,
|
||||||
type WaveformZeroLineOptions,
|
type WaveformZeroLineOptions,
|
||||||
@@ -47,6 +48,8 @@ const annotationsVisible = ref(true)
|
|||||||
const cleanView = ref(false)
|
const cleanView = ref(false)
|
||||||
const presentationMode = ref(false)
|
const presentationMode = ref(false)
|
||||||
const showTooltip = ref(true)
|
const showTooltip = ref(true)
|
||||||
|
const plotMarginTop = ref(18)
|
||||||
|
const plotMarginBottom = ref(52)
|
||||||
const zeroLineVisible = ref(false)
|
const zeroLineVisible = ref(false)
|
||||||
const zeroLineColor = ref('#98a2b3')
|
const zeroLineColor = ref('#98a2b3')
|
||||||
const zeroLineWidth = ref(1)
|
const zeroLineWidth = ref(1)
|
||||||
@@ -126,6 +129,10 @@ const zeroLine = computed<WaveformZeroLineOptions>(() => ({
|
|||||||
width: zeroLineWidth.value,
|
width: zeroLineWidth.value,
|
||||||
dash: zeroLineDash.value,
|
dash: zeroLineDash.value,
|
||||||
}))
|
}))
|
||||||
|
const plotMargin = computed<WaveformPlotMargin>(() => ({
|
||||||
|
top: plotMarginTop.value,
|
||||||
|
bottom: plotMarginBottom.value,
|
||||||
|
}))
|
||||||
|
|
||||||
const initialXValues = normalizeWaveformSeries(fullChartData).flatMap((series) =>
|
const initialXValues = normalizeWaveformSeries(fullChartData).flatMap((series) =>
|
||||||
series.points.map((point) => point.x),
|
series.points.map((point) => point.x),
|
||||||
@@ -294,6 +301,8 @@ const controlPanelModel = reactive({
|
|||||||
displayMode,
|
displayMode,
|
||||||
overlayMode,
|
overlayMode,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
|
plotMarginTop,
|
||||||
|
plotMarginBottom,
|
||||||
cleanView,
|
cleanView,
|
||||||
presentationMode,
|
presentationMode,
|
||||||
selectedSeriesId,
|
selectedSeriesId,
|
||||||
@@ -360,6 +369,7 @@ const chartModel = reactive({
|
|||||||
cleanView,
|
cleanView,
|
||||||
presentationMode,
|
presentationMode,
|
||||||
showTooltip,
|
showTooltip,
|
||||||
|
plotMargin,
|
||||||
zeroLine,
|
zeroLine,
|
||||||
frameWatermarkVisible,
|
frameWatermarkVisible,
|
||||||
annotations,
|
annotations,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const props = withDefaults(defineProps<WaveformChartProps>(), {
|
|||||||
interactionMode: undefined,
|
interactionMode: undefined,
|
||||||
grid: () => ({ rowCount: 2, columnCount: 1, showPagination: true }),
|
grid: () => ({ rowCount: 2, columnCount: 1, showPagination: true }),
|
||||||
rendering: () => ({}),
|
rendering: () => ({}),
|
||||||
|
plotMargin: () => ({}),
|
||||||
legend: () => ({ position: 'top-right', orientation: 'auto' }),
|
legend: () => ({ position: 'top-right', orientation: 'auto' }),
|
||||||
defaultHiddenSeriesIds: () => [],
|
defaultHiddenSeriesIds: () => [],
|
||||||
cleanView: false,
|
cleanView: false,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const {
|
|||||||
overlayMode,
|
overlayMode,
|
||||||
resolvedChartLeftMargin,
|
resolvedChartLeftMargin,
|
||||||
titleAreaHeight,
|
titleAreaHeight,
|
||||||
|
resolvedPlotMargin,
|
||||||
pointerInsideChart,
|
pointerInsideChart,
|
||||||
handleNativeContextMenu,
|
handleNativeContextMenu,
|
||||||
titleAreaReserved,
|
titleAreaReserved,
|
||||||
@@ -118,6 +119,8 @@ const {
|
|||||||
:data-presentation-mode="isPresentationMode"
|
:data-presentation-mode="isPresentationMode"
|
||||||
:data-overlay-mode="overlayMode"
|
:data-overlay-mode="overlayMode"
|
||||||
:data-chart-left-margin="resolvedChartLeftMargin"
|
:data-chart-left-margin="resolvedChartLeftMargin"
|
||||||
|
:data-plot-margin-top="resolvedPlotMargin.top"
|
||||||
|
:data-plot-margin-bottom="resolvedPlotMargin.bottom"
|
||||||
:data-title-area-height="titleAreaHeight"
|
:data-title-area-height="titleAreaHeight"
|
||||||
@pointerenter="pointerInsideChart = true"
|
@pointerenter="pointerInsideChart = true"
|
||||||
@pointerleave="pointerInsideChart = false"
|
@pointerleave="pointerInsideChart = false"
|
||||||
@@ -287,18 +290,18 @@ const {
|
|||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<text
|
|
||||||
v-if="resolvedXLabel && !isCleanView"
|
|
||||||
class="waveform-chart__label waveform-chart__x-label"
|
|
||||||
:x="innerWidth / 2"
|
|
||||||
:y="xAxisTitleY"
|
|
||||||
text-anchor="middle"
|
|
||||||
>
|
|
||||||
{{ resolvedXLabel }}
|
|
||||||
</text>
|
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
<text
|
||||||
|
v-if="resolvedXLabel && !isCleanView"
|
||||||
|
class="waveform-chart__label waveform-chart__x-label"
|
||||||
|
:x="resolvedChartLeftMargin + innerWidth / 2"
|
||||||
|
:y="xAxisTitleY"
|
||||||
|
text-anchor="middle"
|
||||||
|
>
|
||||||
|
{{ resolvedXLabel }}
|
||||||
|
</text>
|
||||||
|
|
||||||
<text
|
<text
|
||||||
v-if="hasChartArea && !hasWaveformData"
|
v-if="hasChartArea && !hasWaveformData"
|
||||||
class="waveform-chart__empty"
|
class="waveform-chart__empty"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
/** 图表边距 */
|
/** 图表边距 */
|
||||||
export const margin = { top: 18, right: 24, bottom: 52, left: 64 }
|
export const margin = { top: 18, right: 24, bottom: 52, left: 64 }
|
||||||
|
|
||||||
|
/** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
|
||||||
|
export const X_AXIS_TITLE_BOTTOM_OFFSET = 12
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 图表最小高度(像素)
|
* 图表最小高度(像素)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
normalizeGridOptions,
|
normalizeGridOptions,
|
||||||
paginateSeries,
|
paginateSeries,
|
||||||
resolveGridCellGeometry,
|
resolveGridCellGeometry,
|
||||||
X_AXIS_BAND,
|
|
||||||
} from './grid'
|
} from './grid'
|
||||||
import {
|
import {
|
||||||
buildTrackLayouts,
|
buildTrackLayouts,
|
||||||
@@ -316,7 +315,6 @@ export function useWaveformLayout(context: LayoutContext) {
|
|||||||
)
|
)
|
||||||
: [],
|
: [],
|
||||||
)
|
)
|
||||||
const xAxisTitleY = computed(() => innerHeight.value + X_AXIS_BAND + 10)
|
|
||||||
const editorSeries = computed<AnnotationSeriesInfo | undefined>(() => {
|
const editorSeries = computed<AnnotationSeriesInfo | undefined>(() => {
|
||||||
const seriesId = annotationInteraction.editorDraft.value?.annotation.seriesId
|
const seriesId = annotationInteraction.editorDraft.value?.annotation.seriesId
|
||||||
const series = chartSeries.value.find((item) => item.id === seriesId)
|
const series = chartSeries.value.find((item) => item.id === seriesId)
|
||||||
@@ -360,7 +358,6 @@ export function useWaveformLayout(context: LayoutContext) {
|
|||||||
resolveSeriesYScale,
|
resolveSeriesYScale,
|
||||||
annotationTrackLayouts,
|
annotationTrackLayouts,
|
||||||
renderedAnnotations,
|
renderedAnnotations,
|
||||||
xAxisTitleY,
|
|
||||||
editorSeries,
|
editorSeries,
|
||||||
resolveFrameNumber,
|
resolveFrameNumber,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
TITLE_CHAR_WIDTH_RATIO,
|
TITLE_CHAR_WIDTH_RATIO,
|
||||||
TITLE_DEFAULT_FONT_SIZE,
|
TITLE_DEFAULT_FONT_SIZE,
|
||||||
TITLE_LINE_HEIGHT,
|
TITLE_LINE_HEIGHT,
|
||||||
|
X_AXIS_TITLE_BOTTOM_OFFSET,
|
||||||
ZERO_LINE_DEFAULTS,
|
ZERO_LINE_DEFAULTS,
|
||||||
} from './constants'
|
} from './constants'
|
||||||
import { calculateRotatedTitleLayout, TITLE_AREA_HORIZONTAL_PADDING } from './title'
|
import { calculateRotatedTitleLayout, TITLE_AREA_HORIZONTAL_PADDING } from './title'
|
||||||
@@ -144,11 +145,23 @@ export function useWaveformPresentation(context: PresentationContext) {
|
|||||||
const titleAreaHeight = computed(() =>
|
const titleAreaHeight = computed(() =>
|
||||||
titleAreaReserved.value ? titleLayout.value.areaHeight : 0,
|
titleAreaReserved.value ? titleLayout.value.areaHeight : 0,
|
||||||
)
|
)
|
||||||
const chartTopMargin = computed(() => margin.top)
|
const resolvePlotMargin = (value: number | undefined, fallback: number) =>
|
||||||
|
typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback
|
||||||
|
const resolvedPlotMargin = computed(() => ({
|
||||||
|
top: resolvePlotMargin(props.plotMargin.top, margin.top),
|
||||||
|
bottom: resolvePlotMargin(props.plotMargin.bottom, margin.bottom),
|
||||||
|
}))
|
||||||
|
const chartTopMargin = computed(() => resolvedPlotMargin.value.top)
|
||||||
const drawingHeight = computed(() =>
|
const drawingHeight = computed(() =>
|
||||||
Math.max(0, chartHeight.value - titleAreaHeight.value - paginationBandHeight.value),
|
Math.max(0, chartHeight.value - titleAreaHeight.value - paginationBandHeight.value),
|
||||||
)
|
)
|
||||||
const innerHeight = computed(() => Math.max(0, drawingHeight.value - margin.top - margin.bottom))
|
const xAxisTitleY = computed(() => Math.max(0, drawingHeight.value - X_AXIS_TITLE_BOTTOM_OFFSET))
|
||||||
|
const innerHeight = computed(() =>
|
||||||
|
Math.max(
|
||||||
|
0,
|
||||||
|
drawingHeight.value - resolvedPlotMargin.value.top - resolvedPlotMargin.value.bottom,
|
||||||
|
),
|
||||||
|
)
|
||||||
const titleAreaStyle = computed<CSSProperties>(() => ({
|
const titleAreaStyle = computed<CSSProperties>(() => ({
|
||||||
height: `${titleAreaHeight.value}px`,
|
height: `${titleAreaHeight.value}px`,
|
||||||
justifyContent:
|
justifyContent:
|
||||||
@@ -193,8 +206,10 @@ export function useWaveformPresentation(context: PresentationContext) {
|
|||||||
titleMeasureStyle,
|
titleMeasureStyle,
|
||||||
titleLayout,
|
titleLayout,
|
||||||
titleAreaHeight,
|
titleAreaHeight,
|
||||||
|
resolvedPlotMargin,
|
||||||
chartTopMargin,
|
chartTopMargin,
|
||||||
drawingHeight,
|
drawingHeight,
|
||||||
|
xAxisTitleY,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
titleAreaStyle,
|
titleAreaStyle,
|
||||||
titleVisualStyle,
|
titleVisualStyle,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
WaveformInteractionMode,
|
WaveformInteractionMode,
|
||||||
WaveformLegendOptions,
|
WaveformLegendOptions,
|
||||||
WaveformOverlayMode,
|
WaveformOverlayMode,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformPoint,
|
WaveformPoint,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
@@ -42,6 +43,7 @@ export interface WaveformChartProps {
|
|||||||
interactionMode?: WaveformInteractionMode
|
interactionMode?: WaveformInteractionMode
|
||||||
grid?: WaveformGridOptions
|
grid?: WaveformGridOptions
|
||||||
rendering?: WaveformRenderingOptions
|
rendering?: WaveformRenderingOptions
|
||||||
|
plotMargin?: WaveformPlotMargin
|
||||||
title?: WaveformTitleOptions
|
title?: WaveformTitleOptions
|
||||||
legend?: WaveformLegendOptions
|
legend?: WaveformLegendOptions
|
||||||
hiddenSeriesIds?: string[]
|
hiddenSeriesIds?: string[]
|
||||||
@@ -65,6 +67,7 @@ type DefaultedProp =
|
|||||||
| 'annotationsVisible'
|
| 'annotationsVisible'
|
||||||
| 'grid'
|
| 'grid'
|
||||||
| 'rendering'
|
| 'rendering'
|
||||||
|
| 'plotMargin'
|
||||||
| 'legend'
|
| 'legend'
|
||||||
| 'defaultHiddenSeriesIds'
|
| 'defaultHiddenSeriesIds'
|
||||||
| 'cleanView'
|
| 'cleanView'
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export type {
|
|||||||
WaveformAnnotationStyle,
|
WaveformAnnotationStyle,
|
||||||
WaveformAnnotation,
|
WaveformAnnotation,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleTextStyle,
|
WaveformTitleTextStyle,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformLegendPosition,
|
WaveformLegendPosition,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type {
|
|||||||
WaveformAnnotationStyle,
|
WaveformAnnotationStyle,
|
||||||
WaveformAnnotation,
|
WaveformAnnotation,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleTextStyle,
|
WaveformTitleTextStyle,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformLegendPosition,
|
WaveformLegendPosition,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type {
|
|||||||
WaveformAnnotationStyle,
|
WaveformAnnotationStyle,
|
||||||
WaveformAnnotation,
|
WaveformAnnotation,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleTextStyle,
|
WaveformTitleTextStyle,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformFrameStyle,
|
WaveformFrameStyle,
|
||||||
|
|||||||
@@ -105,6 +105,65 @@ describe('WaveformChart', () => {
|
|||||||
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('300')
|
expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('300')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('configures plot top and bottom margins and reacts to updates', async () => {
|
||||||
|
const wrapper = await mountSizedChart(
|
||||||
|
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
|
||||||
|
{
|
||||||
|
displayMode: 'compact',
|
||||||
|
grid: { rowCount: 1, columnCount: 1 },
|
||||||
|
plotMargin: { top: 30, bottom: 70 },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(wrapper.attributes('data-plot-margin-top')).toBe('30')
|
||||||
|
expect(wrapper.attributes('data-plot-margin-bottom')).toBe('70')
|
||||||
|
expect(wrapper.get('.waveform-chart__overlay').attributes('height')).toBe('260')
|
||||||
|
expect(wrapper.get('.waveform-chart__svg > g').attributes('transform')).toContain(', 30)')
|
||||||
|
|
||||||
|
await wrapper.setProps({ plotMargin: { top: 12 } })
|
||||||
|
|
||||||
|
expect(wrapper.attributes('data-plot-margin-top')).toBe('12')
|
||||||
|
expect(wrapper.attributes('data-plot-margin-bottom')).toBe('52')
|
||||||
|
expect(wrapper.get('.waveform-chart__overlay').attributes('height')).toBe('296')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to default plot margins for invalid values', async () => {
|
||||||
|
const wrapper = await mountSizedChart(
|
||||||
|
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
|
||||||
|
{
|
||||||
|
displayMode: 'compact',
|
||||||
|
grid: { rowCount: 1, columnCount: 1 },
|
||||||
|
plotMargin: { top: -1, bottom: Number.NaN },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(wrapper.attributes('data-plot-margin-top')).toBe('18')
|
||||||
|
expect(wrapper.attributes('data-plot-margin-bottom')).toBe('52')
|
||||||
|
expect(wrapper.get('.waveform-chart__overlay').attributes('height')).toBe('290')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the chart title and X-axis title fixed when plot margins change', async () => {
|
||||||
|
const wrapper = await mountSizedChart(
|
||||||
|
{ kind: 'samples', values: [0, 1], sampleRate: 1 },
|
||||||
|
{
|
||||||
|
displayMode: 'compact',
|
||||||
|
grid: { rowCount: 1, columnCount: 1 },
|
||||||
|
title: { text: '固定标题' },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const titleAreaStyle = wrapper.get('.waveform-chart__title-area').attributes('style')
|
||||||
|
const xAxisTitle = wrapper.get('.waveform-chart__x-label')
|
||||||
|
const initialX = xAxisTitle.attributes('x')
|
||||||
|
const initialY = xAxisTitle.attributes('y')
|
||||||
|
|
||||||
|
await wrapper.setProps({ plotMargin: { top: 60, bottom: 90 } })
|
||||||
|
|
||||||
|
expect(wrapper.get('.waveform-chart__title-area').attributes('style')).toBe(titleAreaStyle)
|
||||||
|
expect(wrapper.get('.waveform-chart__x-label').attributes('x')).toBe(initialX)
|
||||||
|
expect(wrapper.get('.waveform-chart__x-label').attributes('y')).toBe(initialY)
|
||||||
|
expect(wrapper.get('.waveform-chart__svg > g').attributes('transform')).toContain(', 60)')
|
||||||
|
})
|
||||||
|
|
||||||
it('does not render or reserve space for missing, hidden, or blank titles', async () => {
|
it('does not render or reserve space for missing, hidden, or blank titles', async () => {
|
||||||
for (const title of [undefined, { visible: false, text: '隐藏标题' }, { text: ' ' }]) {
|
for (const title of [undefined, { visible: false, text: '隐藏标题' }, { text: ' ' }]) {
|
||||||
const wrapper = await mountSizedChart(
|
const wrapper = await mountSizedChart(
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ defineExpose({ resetViewport })
|
|||||||
:clean-view="model.cleanView"
|
:clean-view="model.cleanView"
|
||||||
:presentation-mode="model.presentationMode"
|
:presentation-mode="model.presentationMode"
|
||||||
:show-tooltip="model.showTooltip"
|
:show-tooltip="model.showTooltip"
|
||||||
|
:plot-margin="model.plotMargin"
|
||||||
:zero-line="model.zeroLine"
|
:zero-line="model.zeroLine"
|
||||||
:frame-number="model.frameWatermarkVisible ? 1 : undefined"
|
:frame-number="model.frameWatermarkVisible ? 1 : undefined"
|
||||||
:annotations-visible="model.annotationsVisible"
|
:annotations-visible="model.annotationsVisible"
|
||||||
|
|||||||
@@ -8,6 +8,35 @@ const model = defineModel<DemoControlPanelModel>('model', { required: true })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<section class="control-section">
|
||||||
|
<h2>绘图区边距</h2>
|
||||||
|
<div class="plot-margin-controls">
|
||||||
|
<label class="frame-style-control">
|
||||||
|
<span>上边距</span>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="model.plotMarginTop"
|
||||||
|
:min="0"
|
||||||
|
:max="300"
|
||||||
|
:step="1"
|
||||||
|
addon-after="px"
|
||||||
|
size="small"
|
||||||
|
aria-label="绘图区上边距"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label class="frame-style-control">
|
||||||
|
<span>下边距</span>
|
||||||
|
<InputNumber
|
||||||
|
v-model:value="model.plotMarginBottom"
|
||||||
|
:min="0"
|
||||||
|
:max="300"
|
||||||
|
:step="1"
|
||||||
|
addon-after="px"
|
||||||
|
size="small"
|
||||||
|
aria-label="绘图区下边距"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section class="control-section">
|
<section class="control-section">
|
||||||
<div class="control-section__header">
|
<div class="control-section__header">
|
||||||
<h2>零值参考线</h2>
|
<h2>零值参考线</h2>
|
||||||
|
|||||||
29
src/demo/plotMarginControls.test.ts
Normal file
29
src/demo/plotMarginControls.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { flushPromises, mount } from '@vue/test-utils'
|
||||||
|
import { InputNumber } from 'ant-design-vue'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import App from '../App.vue'
|
||||||
|
import { WaveformChart } from '../components'
|
||||||
|
|
||||||
|
describe('plot margin demo controls', () => {
|
||||||
|
it('updates the chart top and bottom margins from the sidebar', async () => {
|
||||||
|
const wrapper = mount(App)
|
||||||
|
await flushPromises()
|
||||||
|
const chart = wrapper.getComponent(WaveformChart)
|
||||||
|
const controls = wrapper.get('.plot-margin-controls').findAllComponents(InputNumber)
|
||||||
|
|
||||||
|
expect(controls).toHaveLength(2)
|
||||||
|
expect(controls[0]?.props('value')).toBe(18)
|
||||||
|
expect(controls[1]?.props('value')).toBe(52)
|
||||||
|
expect(chart.props('plotMargin')).toEqual({ top: 18, bottom: 52 })
|
||||||
|
|
||||||
|
controls[0]?.vm.$emit('update:value', 30)
|
||||||
|
controls[1]?.vm.$emit('update:value', 70)
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(chart.props('plotMargin')).toEqual({ top: 30, bottom: 70 })
|
||||||
|
expect(wrapper.get('.waveform-chart').attributes('data-plot-margin-top')).toBe('30')
|
||||||
|
expect(wrapper.get('.waveform-chart').attributes('data-plot-margin-bottom')).toBe('70')
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
WaveformLegendPosition,
|
WaveformLegendPosition,
|
||||||
WaveformLineStyle,
|
WaveformLineStyle,
|
||||||
WaveformOverlayMode,
|
WaveformOverlayMode,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformZeroLineOptions,
|
WaveformZeroLineOptions,
|
||||||
WaveformZoomEndPayload,
|
WaveformZoomEndPayload,
|
||||||
@@ -26,6 +27,8 @@ export interface DemoControlPanelModel {
|
|||||||
displayMode: WaveformDisplayMode
|
displayMode: WaveformDisplayMode
|
||||||
overlayMode: WaveformOverlayMode
|
overlayMode: WaveformOverlayMode
|
||||||
showTooltip: boolean
|
showTooltip: boolean
|
||||||
|
plotMarginTop: number
|
||||||
|
plotMarginBottom: number
|
||||||
cleanView: boolean
|
cleanView: boolean
|
||||||
presentationMode: boolean
|
presentationMode: boolean
|
||||||
selectedSeriesId: string
|
selectedSeriesId: string
|
||||||
@@ -99,6 +102,7 @@ export interface DemoChartModel {
|
|||||||
cleanView: boolean
|
cleanView: boolean
|
||||||
presentationMode: boolean
|
presentationMode: boolean
|
||||||
showTooltip: boolean
|
showTooltip: boolean
|
||||||
|
plotMargin: WaveformPlotMargin
|
||||||
zeroLine: WaveformZeroLineOptions
|
zeroLine: WaveformZeroLineOptions
|
||||||
frameWatermarkVisible: boolean
|
frameWatermarkVisible: boolean
|
||||||
annotations: WaveformAnnotation[]
|
annotations: WaveformAnnotation[]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export type {
|
|||||||
WaveformAnnotationStyle,
|
WaveformAnnotationStyle,
|
||||||
WaveformAnnotation,
|
WaveformAnnotation,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleTextStyle,
|
WaveformTitleTextStyle,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformLegendPosition,
|
WaveformLegendPosition,
|
||||||
|
|||||||
@@ -195,6 +195,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.frame-style-controls,
|
.frame-style-controls,
|
||||||
|
.plot-margin-controls,
|
||||||
.auxiliary-style-controls {
|
.auxiliary-style-controls {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ export interface WaveformRenderingOptions {
|
|||||||
errorBarMinSpacing?: number
|
errorBarMinSpacing?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Pixel margins reserved above and below the waveform plotting area. */
|
||||||
|
export interface WaveformPlotMargin {
|
||||||
|
/** Space between the top of the drawing SVG and the plotting area. Defaults to 18. */
|
||||||
|
top?: number
|
||||||
|
/** Space between the plotting area and the bottom of the drawing SVG. Defaults to 52. */
|
||||||
|
bottom?: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Text styling for the chart-level title. */
|
/** Text styling for the chart-level title. */
|
||||||
export interface WaveformTitleTextStyle {
|
export interface WaveformTitleTextStyle {
|
||||||
color?: string
|
color?: string
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type {
|
|||||||
WaveformAnnotationStyle,
|
WaveformAnnotationStyle,
|
||||||
WaveformAnnotation,
|
WaveformAnnotation,
|
||||||
WaveformRenderingOptions,
|
WaveformRenderingOptions,
|
||||||
|
WaveformPlotMargin,
|
||||||
WaveformTitleTextStyle,
|
WaveformTitleTextStyle,
|
||||||
WaveformTitleOptions,
|
WaveformTitleOptions,
|
||||||
WaveformLegendPosition,
|
WaveformLegendPosition,
|
||||||
|
|||||||
Reference in New Issue
Block a user