This is an automated email from the ASF dual-hosted git repository.

enzomartellucci pushed a commit to branch enxdev/fix/echarts-x-axis
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 0a06daae6c4b93d718d56fbdaa23e045c22bd84b
Author: Enzo Martellucci <[email protected]>
AuthorDate: Fri Feb 6 14:02:37 2026 +0100

    fix(chart): prevent x-axis date labels from disappearing when rotated
---
 .../src/Timeseries/transformProps.ts               |  28 ++++-
 .../test/Timeseries/transformers.test.ts           | 113 +++++++++++++++------
 2 files changed, 105 insertions(+), 36 deletions(-)

diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
index 7acd63132b9..bde688c1186 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts
@@ -606,14 +606,16 @@ export default function transformProps(
     nameGap: convertInteger(xAxisTitleMargin),
     nameLocation: 'middle',
     axisLabel: {
-      hideOverlap: true,
+      // When rotation is applied on time axes, hideOverlap can
+      // aggressively hide the last label. Rotated labels already
+      // have less overlap, so disabling hideOverlap is safe.
+      // At 0° rotation, keep hideOverlap to prevent long labels
+      // from overlapping each other.
+      hideOverlap:
+        !(xAxisType === AxisType.Time && xAxisLabelRotation !== 0),
       formatter: xAxisFormatter,
       rotate: xAxisLabelRotation,
       interval: xAxisLabelInterval,
-      ...(xAxisType === AxisType.Time && {
-        showMaxLabel: true,
-        alignMaxLabel: 'right',
-      }),
     },
     minorTick: { show: minorTicks },
     minInterval:
@@ -659,6 +661,22 @@ export default function transformProps(
     nameLocation: yAxisTitlePosition === 'Left' ? 'middle' : 'end',
   };
 
+  // Increase right padding for rotated time axis labels to prevent
+  // the last label from being clipped at the chart boundary.
+  if (
+    xAxisType === AxisType.Time &&
+    xAxisLabelRotation !== 0 &&
+    !isHorizontal
+  ) {
+    padding.right = Math.max(
+      padding.right || 0,
+      TIMESERIES_CONSTANTS.gridOffsetRight +
+        Math.ceil(
+          Math.abs(Math.sin((xAxisLabelRotation * Math.PI) / 180)) * 80,
+        ),
+    );
+  }
+
   if (isHorizontal) {
     [xAxis, yAxis] = [yAxis, xAxis];
     [padding.bottom, padding.left] = [padding.left, padding.bottom];
diff --git 
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
 
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
index f5770b6fe3f..ba1c7806362 100644
--- 
a/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
+++ 
b/superset-frontend/plugins/plugin-chart-echarts/test/Timeseries/transformers.test.ts
@@ -227,47 +227,98 @@ describe('transformNegativeLabelsPosition', () => {
   });
 });
 
-test('should configure time axis labels to show max label for last month 
visibility', () => {
-  const formData = {
-    colorScheme: 'bnbColors',
-    datasource: '3__table',
-    granularity_sqla: 'ds',
-    metric: 'sum__num',
-    viz_type: 'my_viz',
-  };
-  const queriesData = [
-    {
-      data: [
-        { sum__num: 100, __timestamp: new Date('2026-01-01').getTime() },
-        { sum__num: 200, __timestamp: new Date('2026-02-01').getTime() },
-        { sum__num: 300, __timestamp: new Date('2026-03-01').getTime() },
-        { sum__num: 400, __timestamp: new Date('2026-04-01').getTime() },
-        { sum__num: 500, __timestamp: new Date('2026-05-01').getTime() },
-      ],
-      colnames: ['sum__num', '__timestamp'],
-      coltypes: [GenericDataType.Numeric, GenericDataType.Temporal],
+function buildTimeseriesChartProps(
+  overrides: Record<string, unknown> = {},
+): EchartsTimeseriesChartProps {
+  return new ChartProps({
+    formData: {
+      colorScheme: 'bnbColors',
+      datasource: '3__table',
+      granularity_sqla: 'ds',
+      metric: 'sum__num',
+      viz_type: 'my_viz',
+      ...overrides,
     },
-  ];
-  const chartProps = new ChartProps({
-    formData,
     width: 800,
     height: 600,
-    queriesData,
+    queriesData: [
+      {
+        data: [
+          { sum__num: 100, __timestamp: new Date('2026-01-01').getTime() },
+          { sum__num: 200, __timestamp: new Date('2026-04-01').getTime() },
+          { sum__num: 300, __timestamp: new Date('2026-07-01').getTime() },
+          { sum__num: 400, __timestamp: new Date('2026-10-01').getTime() },
+          { sum__num: 500, __timestamp: new Date('2026-12-01').getTime() },
+        ],
+        colnames: ['sum__num', '__timestamp'],
+        coltypes: [GenericDataType.Numeric, GenericDataType.Temporal],
+      },
+    ],
     theme: supersetTheme,
-  });
+  }) as unknown as EchartsTimeseriesChartProps;
+}
+
+test('x-axis dates do not overlap when horizontal (rotation 0°)', () => {
+  const result = transformProps(buildTimeseriesChartProps());
+  const { axisLabel } = result.echartOptions.xAxis as Record<string, any>;
 
+  expect(axisLabel.hideOverlap).toBe(true);
+  expect(axisLabel.showMaxLabel).toBeUndefined();
+  expect(axisLabel.alignMaxLabel).toBeUndefined();
+});
+
+test('last x-axis date is visible and not cut off when rotated -45°', () => {
+  const lastDataPointTimestamp = new Date('2026-12-01').getTime();
   const result = transformProps(
-    chartProps as unknown as EchartsTimeseriesChartProps,
+    buildTimeseriesChartProps({
+      xAxisLabelRotation: -45,
+      x_axis_time_format: '%d-%m-%Y %H:%M:%S',
+    }),
   );
+  const { xAxis, grid } = result.echartOptions as Record<string, any>;
+  const { axisLabel } = xAxis;
+
+  // The formatter renders the last data point's date as a full string
+  const lastDateLabel = axisLabel.formatter(lastDataPointTimestamp);
+  expect(lastDateLabel).toMatch(/01-12-2026/);
+  expect(lastDateLabel).not.toBe('');
+
+  // Labels are not aggressively hidden so the last date stays visible
+  expect(axisLabel.hideOverlap).toBe(false);
+  expect(axisLabel.rotate).toBe(-45);
+  // No phantom label at a position that doesn't correspond to any bar
+  expect(axisLabel.showMaxLabel).toBeUndefined();
+  // Enough right padding so the last rotated label is not clipped
+  expect(grid.right).toBeGreaterThan(TIMESERIES_CONSTANTS.gridOffsetRight);
+});
 
-  expect(result.echartOptions.xAxis).toEqual(
-    expect.objectContaining({
-      axisLabel: expect.objectContaining({
-        showMaxLabel: true,
-        alignMaxLabel: 'right',
-      }),
+test('last x-axis date is visible and not cut off when rotated 45°', () => {
+  const lastDataPointTimestamp = new Date('2026-12-01').getTime();
+  const result = transformProps(
+    buildTimeseriesChartProps({
+      xAxisLabelRotation: 45,
+      x_axis_time_format: '%d-%m-%Y %H:%M:%S',
     }),
   );
+  const { xAxis, grid } = result.echartOptions as Record<string, any>;
+
+  const lastDateLabel = xAxis.axisLabel.formatter(lastDataPointTimestamp);
+  expect(lastDateLabel).toMatch(/01-12-2026/);
+  expect(lastDateLabel).not.toBe('');
+
+  expect(xAxis.axisLabel.hideOverlap).toBe(false);
+  expect(xAxis.axisLabel.rotate).toBe(45);
+  expect(grid.right).toBeGreaterThan(TIMESERIES_CONSTANTS.gridOffsetRight);
+});
+
+test('no phantom date label appears at the axis boundary', () => {
+  const result = transformProps(
+    buildTimeseriesChartProps({ xAxisLabelRotation: -45 }),
+  );
+  const { axisLabel } = result.echartOptions.xAxis as Record<string, any>;
+
+  expect(axisLabel.showMaxLabel).toBeUndefined();
+  expect(axisLabel.showMinLabel).toBeUndefined();
 });
 
 function setupGetChartPaddingMock(): jest.SpyInstance {

Reply via email to