diff --git a/timeserieschart/src/TimeSeriesChartPanel.test.tsx b/timeserieschart/src/TimeSeriesChartPanel.test.tsx index dd1ebd2bb..c06797fa5 100644 --- a/timeserieschart/src/TimeSeriesChartPanel.test.tsx +++ b/timeserieschart/src/TimeSeriesChartPanel.test.tsx @@ -38,6 +38,16 @@ vi.mock('@perses-dev/components', async (importOriginal) => { return { ...actual, getTimeSeriesValues: vi.fn(actual.getTimeSeriesValues), + // Published @perses-dev/components does not export this yet (shared#321). + getFormattedMultipleYAxesLayout: ( + ...args: Parameters + ): { axes: ReturnType; rightGridPadding: number } => { + const additionalFormats = args[2]; + return { + axes: actual.getFormattedMultipleYAxes(...args), + rightGridPadding: additionalFormats.length > 0 ? additionalFormats.length * 52 + 12 : 20, + }; + }, EChart: (props: { option: EChartsCoreOption }): ReactElement => { lastChartOption.current = props.option; return
; diff --git a/timeserieschart/src/TimeSeriesChartPanel.tsx b/timeserieschart/src/TimeSeriesChartPanel.tsx index 9a24f1387..eecee3673 100644 --- a/timeserieschart/src/TimeSeriesChartPanel.tsx +++ b/timeserieschart/src/TimeSeriesChartPanel.tsx @@ -29,7 +29,7 @@ import { ContentWithLegend, useId, DEFAULT_TOOLTIP_CONFIG, - getFormattedMultipleYAxes, + getFormattedMultipleYAxesLayout, DEFAULT_LEGEND, formatValue, getTimeSeriesValues, @@ -432,19 +432,17 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen formatToYAxisIndex, ]); - // Create multiple Y axes if there are additional formats - // Uses max values from data to compute dynamic offsets that adapt to label widths - const multipleYAxes = useMemo(() => { + const multipleYLayout = useMemo(() => { if (additionalFormats.length === 0) { - return undefined; // Use single Y axis (default behavior) + return undefined; } - // Build array of max values for each additional format (in order) const maxValues = additionalFormats.map((fmt) => { const unitKey = fmt.unit; return unitKey ? (maxValuesByFormat?.get(unitKey) ?? 1000) : 1000; }); - return getFormattedMultipleYAxes(echartsYAxis, format, additionalFormats, maxValues); + return getFormattedMultipleYAxesLayout(echartsYAxis, format, additionalFormats, maxValues); }, [echartsYAxis, format, additionalFormats, maxValuesByFormat]); + const multipleYAxes = multipleYLayout?.axes; // Translate the legend values into columns for the table legend. const legendColumns = useMemo(() => { @@ -484,23 +482,23 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen }, [legend?.values, format]); const gridOverrides: GridComponentOption = useMemo(() => { - // When Y axes are hidden, disable containLabel to prevent auto-spacing, but add bottom padding for X axis - return echartsYAxis.show === false - ? { - left: 0, - right: 0, - bottom: 30, - containLabel: false, - } - : { - left: yAxis && yAxis.label ? 30 : 20, - // With containLabel: true in theme, ECharts auto-reserves space for axis labels. - // For multiple right axes, add extra padding for the last axis labels that extend beyond the grid. - right: additionalFormats.length > 0 ? 10 : 20, - bottom: 0, - containLabel: true, - }; - }, [echartsYAxis.show, yAxis, additionalFormats.length]); + if (echartsYAxis.show === false) { + return { + left: 0, + right: 0, + bottom: 30, + containLabel: false, + }; + } + // rightGridPadding is the cumulative width of the right axes (shared#321). + const rightPad = multipleYLayout?.rightGridPadding ?? 20; + return { + left: yAxis && yAxis.label ? 30 : 20, + right: rightPad, + bottom: 0, + containLabel: true, + }; + }, [echartsYAxis.show, yAxis, multipleYLayout]); const handleDataZoom = useCallback( (event: ZoomEventData): void => { diff --git a/timeserieschart/src/perses-components-multi-y.d.ts b/timeserieschart/src/perses-components-multi-y.d.ts new file mode 100644 index 000000000..e1ea6d896 --- /dev/null +++ b/timeserieschart/src/perses-components-multi-y.d.ts @@ -0,0 +1,29 @@ +// Copyright The Perses Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import type { FormatOptions } from '@perses-dev/components'; +import type { YAXisComponentOption } from 'echarts'; + +// Published @perses-dev/components does not export this yet. The signature +// matches shared#321 so this plugin typechecks until that release. +declare module '@perses-dev/components' { + export function getFormattedMultipleYAxesLayout( + baseAxis: YAXisComponentOption | undefined, + baseFormat: FormatOptions | undefined, + additionalFormats: FormatOptions[], + maxValues?: number[], + ): { + axes: YAXisComponentOption[]; + rightGridPadding: number; + }; +}