From 0244de81b8ea5b14bf36f87f976082b1a036f1b7 Mon Sep 17 00:00:00 2001 From: colivi Date: Fri, 25 Sep 2026 22:05:41 +0200 Subject: [PATCH 1/6] fix(timeserieschart): pad grid.right for multi Y-axis labels Right axes with offset are outside ECharts containLabel; reserve ~52px per additional format so outermost tick labels are not clipped. Signed-off-by: colivi --- timeserieschart/src/TimeSeriesChartPanel.tsx | 37 +++++++++----------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/timeserieschart/src/TimeSeriesChartPanel.tsx b/timeserieschart/src/TimeSeriesChartPanel.tsx index 9ddd1fff2..ee423e3e3 100644 --- a/timeserieschart/src/TimeSeriesChartPanel.tsx +++ b/timeserieschart/src/TimeSeriesChartPanel.tsx @@ -432,13 +432,10 @@ export function TimeSeriesChartPanel(props: TimeSeriesChartProps): ReactElement seriesFormatMap, ]); - // 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(() => { 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; @@ -484,22 +481,22 @@ export function TimeSeriesChartPanel(props: TimeSeriesChartProps): ReactElement }, [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, - }; + if (echartsYAxis.show === false) { + return { + left: 0, + right: 0, + bottom: 30, + containLabel: false, + }; + } + // Right axes with offset>0 sit outside containLabel; ~52px per extra format (unit ticks). + const rightPad = additionalFormats.length > 0 ? additionalFormats.length * 52 + 12 : 20; + return { + left: yAxis && yAxis.label ? 30 : 20, + right: rightPad, + bottom: 0, + containLabel: true, + }; }, [echartsYAxis.show, yAxis, additionalFormats.length]); if (adjustedContentDimensions === undefined) { From 8b5c4cfb7773751e3a7596a01d36a83ecb352ba8 Mon Sep 17 00:00:00 2001 From: colivi Date: Fri, 25 Sep 2026 22:07:19 +0200 Subject: [PATCH 2/6] style(timeserieschart): drop verbose comment on multi-Y grid pad Signed-off-by: colivi --- timeserieschart/src/TimeSeriesChartPanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/timeserieschart/src/TimeSeriesChartPanel.tsx b/timeserieschart/src/TimeSeriesChartPanel.tsx index ee423e3e3..ec4ebfaa9 100644 --- a/timeserieschart/src/TimeSeriesChartPanel.tsx +++ b/timeserieschart/src/TimeSeriesChartPanel.tsx @@ -489,7 +489,6 @@ export function TimeSeriesChartPanel(props: TimeSeriesChartProps): ReactElement containLabel: false, }; } - // Right axes with offset>0 sit outside containLabel; ~52px per extra format (unit ticks). const rightPad = additionalFormats.length > 0 ? additionalFormats.length * 52 + 12 : 20; return { left: yAxis && yAxis.label ? 30 : 20, From ac97ef54aedcb082c955657259920841bbae149a Mon Sep 17 00:00:00 2001 From: colivi Date: Mon, 28 Sep 2026 11:12:32 +0200 Subject: [PATCH 3/6] fix(timeserieschart): document multi-Y grid.right heuristic constants Named constants for per-axis width and margins until shared rightGridPadding is available. Signed-off-by: colivi --- timeserieschart/src/TimeSeriesChartPanel.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/timeserieschart/src/TimeSeriesChartPanel.tsx b/timeserieschart/src/TimeSeriesChartPanel.tsx index 57945ba01..274671e77 100644 --- a/timeserieschart/src/TimeSeriesChartPanel.tsx +++ b/timeserieschart/src/TimeSeriesChartPanel.tsx @@ -489,7 +489,16 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen containLabel: false, }; } - const rightPad = additionalFormats.length > 0 ? additionalFormats.length * 52 + 12 : 20; + // Heuristic until @perses-dev/components exposes rightGridPadding from + // getFormattedMultipleYAxesLayout (shared#321): ~52px per right axis + // (typical formatted tick + unit) plus 12px chart margin; 20px when single axis. + const RIGHT_AXIS_WIDTH_PX = 52; + const MULTI_Y_RIGHT_MARGIN_PX = 12; + const SINGLE_Y_RIGHT_PADDING_PX = 20; + const rightPad = + additionalFormats.length > 0 + ? additionalFormats.length * RIGHT_AXIS_WIDTH_PX + MULTI_Y_RIGHT_MARGIN_PX + : SINGLE_Y_RIGHT_PADDING_PX; return { left: yAxis && yAxis.label ? 30 : 20, right: rightPad, From 35997944a01cc769f48143d2df84ad24d0344a46 Mon Sep 17 00:00:00 2001 From: colivi Date: Fri, 2 Oct 2026 17:01:57 +0200 Subject: [PATCH 4/6] fix(timeserieschart): size grid.right from shared rightGridPadding Use getFormattedMultipleYAxesLayout so the chart margin matches the cumulative width of the right axes instead of a fixed 52px heuristic. Signed-off-by: colivi --- timeserieschart/src/TimeSeriesChartPanel.tsx | 21 +++++++------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/timeserieschart/src/TimeSeriesChartPanel.tsx b/timeserieschart/src/TimeSeriesChartPanel.tsx index 274671e77..a7eb91ac7 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,7 +432,7 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen formatToYAxisIndex, ]); - const multipleYAxes = useMemo(() => { + const multipleYLayout = useMemo(() => { if (additionalFormats.length === 0) { return undefined; } @@ -440,8 +440,9 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen 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(() => { @@ -489,23 +490,15 @@ function TimeSeriesChartPanelComponent(props: TimeSeriesChartProps): ReactElemen containLabel: false, }; } - // Heuristic until @perses-dev/components exposes rightGridPadding from - // getFormattedMultipleYAxesLayout (shared#321): ~52px per right axis - // (typical formatted tick + unit) plus 12px chart margin; 20px when single axis. - const RIGHT_AXIS_WIDTH_PX = 52; - const MULTI_Y_RIGHT_MARGIN_PX = 12; - const SINGLE_Y_RIGHT_PADDING_PX = 20; - const rightPad = - additionalFormats.length > 0 - ? additionalFormats.length * RIGHT_AXIS_WIDTH_PX + MULTI_Y_RIGHT_MARGIN_PX - : SINGLE_Y_RIGHT_PADDING_PX; + // 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, additionalFormats.length]); + }, [echartsYAxis.show, yAxis, multipleYLayout]); const handleDataZoom = useCallback( (event: ZoomEventData): void => { From e6277b92d73e1f614bad7a703673474ab774d9da Mon Sep 17 00:00:00 2001 From: colivi Date: Fri, 2 Oct 2026 17:18:39 +0200 Subject: [PATCH 5/6] test(timeserieschart): stub getFormattedMultipleYAxesLayout The published components package does not export the shared#321 helper yet, so the panel test mock and a local type declaration provide it. Signed-off-by: colivi --- .../src/TimeSeriesChartPanel.test.tsx | 10 ++++++++++ .../src/perses-components-multi-y.d.ts | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 timeserieschart/src/perses-components-multi-y.d.ts 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/perses-components-multi-y.d.ts b/timeserieschart/src/perses-components-multi-y.d.ts new file mode 100644 index 000000000..75f324355 --- /dev/null +++ b/timeserieschart/src/perses-components-multi-y.d.ts @@ -0,0 +1,16 @@ +// Published @perses-dev/components does not export this yet. The signature +// matches shared#321 so this plugin typechecks until that release. +import type { YAXisComponentOption } from 'echarts'; +import type { FormatOptions } from '@perses-dev/components'; + +declare module '@perses-dev/components' { + export function getFormattedMultipleYAxesLayout( + baseAxis: YAXisComponentOption | undefined, + baseFormat: FormatOptions | undefined, + additionalFormats: FormatOptions[], + maxValues?: number[], + ): { + axes: YAXisComponentOption[]; + rightGridPadding: number; + }; +} From 1e47421542708e44dc2e6537d2ef58ce45efa7c6 Mon Sep 17 00:00:00 2001 From: colivi Date: Fri, 2 Oct 2026 17:22:48 +0200 Subject: [PATCH 6/6] style(timeserieschart): add license header and format the layout declaration Signed-off-by: colivi --- .../src/perses-components-multi-y.d.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/timeserieschart/src/perses-components-multi-y.d.ts b/timeserieschart/src/perses-components-multi-y.d.ts index 75f324355..e1ea6d896 100644 --- a/timeserieschart/src/perses-components-multi-y.d.ts +++ b/timeserieschart/src/perses-components-multi-y.d.ts @@ -1,8 +1,21 @@ -// Published @perses-dev/components does not export this yet. The signature -// matches shared#321 so this plugin typechecks until that release. -import type { YAXisComponentOption } from 'echarts'; +// 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,