From ad22eb14d60ad2c2e5b9894c8efb4aad62db1b73 Mon Sep 17 00:00:00 2001 From: Tim Molter Date: Sat, 8 Aug 2026 11:45:43 -0600 Subject: [PATCH] Fix x-axis line hidden by plot area with OutsideS legend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Axis_X.preparePaint() subtracted the OutsideS legend height from the top of the x-axis bounds even though Axis_Y had already reserved that space when computing its height (whose bottom defines the plot bottom), so the x-axis bounds started a legend-height inside the plot area. The bounds height did not subtract it, so only the top edge was wrong — harmless while the axis line was positioned bottom-up from the painted tick label geometry, but since the line and tick marks are anchored to the top of the x-axis bounds (#1023) they were drawn inside the plot, which is painted afterwards and covered them. Move the legend height subtraction from the bounds top to the bounds height, keeping the bottom edge unchanged so the bottom-anchored axis title and tick label positioning is unaffected. Fixes #1024 Co-Authored-By: Claude Fable 5 --- .../standalone/issues/TestForIssue1024.java | 59 +++++++++++++++++++ .../xchart/internal/chartpart/Axis_X.java | 5 +- .../chartpart/AxisLinePlotMarginTest.java | 29 ++++++++- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1024.java diff --git a/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1024.java b/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1024.java new file mode 100644 index 000000000..ed59ea1f3 --- /dev/null +++ b/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue1024.java @@ -0,0 +1,59 @@ +package org.knowm.xchart.standalone.issues; + +import java.util.Locale; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.XYChartBuilder; +import org.knowm.xchart.style.Styler.LegendLayout; +import org.knowm.xchart.style.Styler.LegendPosition; +import org.knowm.xchart.style.colors.ChartColor; + +/** + * Demonstrates issue #1024 — after upgrading from 3.8.8 to 4.0.4 the x-axis line disappeared on + * charts with a {@code LegendPosition.OutsideS} legend. + * + *

{@code Axis_X.preparePaint()} subtracted the legend height from the top of the x-axis bounds + * even though the y-axis (whose bottom defines the plot bottom) had already reserved that space, so + * with an {@code OutsideS} legend the x-axis bounds started a legend-height inside the plot + * area. That was harmless while the axis line was positioned from the painted tick label geometry, + * but 4.0.4 anchors the line and tick marks to the top of the x-axis bounds — placing them under + * the plot, which is painted afterwards and covers them. The light grey plot background from the + * original report makes the missing black line obvious. + * + *

Run this and look below the plot: the x-axis line and tick marks sit plotMargin under the grey + * plot area, exactly as in 3.8.8. + */ +public class TestForIssue1024 { + + public static void main(String[] args) { + + new SwingWrapper<>(getChart()).displayChart(); + } + + /** Constructs and returns the chart without launching a window (headless-safe). */ + public static XYChart getChart() { + + XYChart chart = new XYChartBuilder().width(261).height(268).build(); + + // styler settings verbatim from the issue report + chart.getStyler().setPlotGridLinesColor(ChartColor.WHITE.getColor()); + + chart + .getStyler() + .setLocale(Locale.GERMANY) + .setChartTitleVisible(true) + .setPlotBorderVisible(false) + .setLegendBorderColor(null) + .setPlotBackgroundColor(ChartColor.LIGHT_GREY.getColor()) + .setChartBackgroundColor(ChartColor.WHITE.getColor()) + .setLegendPadding(5) + .setLegendPosition(LegendPosition.OutsideS) + .setLegendLayout(LegendLayout.Horizontal); + + // two points sloping from 2 down to 1, x around 30000 so the German locale renders the + // "30.000" tick label seen in the report's screenshots + chart.addSeries("0", new double[] {30000, 31000}, new double[] {2, 1}); + + return chart; + } +} diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis_X.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis_X.java index 3dd84be11..ccc93da03 100644 --- a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis_X.java +++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Axis_X.java @@ -61,7 +61,7 @@ public void preparePaint() { leftYAxisBounds.getY() + leftYAxisBounds.getHeight(), rightYAxisBounds.getY() + rightYAxisBounds.getHeight()); double xOffset = leftYAxisBounds.getWidth() + leftYAxisBounds.getX(); - double yOffset = maxYAxisY + axesChartStyler.getPlotMargin() - legendHeightOffset; + double yOffset = maxYAxisY + axesChartStyler.getPlotMargin(); double legendWidth = 0; if (axesChartStyler.getLegendPosition() == LegendPosition.OutsideE @@ -85,7 +85,8 @@ public void preparePaint() { chart.getHeight() - maxYAxisY - axesChartStyler.getChartPadding() - - axesChartStyler.getPlotMargin(); + - axesChartStyler.getPlotMargin() + - legendHeightOffset; bounds.setRect(xOffset, yOffset, width, height); } diff --git a/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisLinePlotMarginTest.java b/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisLinePlotMarginTest.java index e7a5d557f..d6195e0c3 100644 --- a/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisLinePlotMarginTest.java +++ b/xchart/src/test/java/org/knowm/xchart/internal/chartpart/AxisLinePlotMarginTest.java @@ -8,6 +8,8 @@ import org.knowm.xchart.BitmapEncoder; import org.knowm.xchart.XYChart; import org.knowm.xchart.XYChartBuilder; +import org.knowm.xchart.style.Styler.LegendLayout; +import org.knowm.xchart.style.Styler.LegendPosition; // The axis tick lines that hug the plot area must sit at the same distance (plotMargin) from the // plot on both the y-axis and x-axis sides. Historically the y-axis line was positioned from the @@ -21,13 +23,38 @@ class AxisLinePlotMarginTest { @Test void axisLinesSitAtPlotMarginOnBothSides() throws Exception { + XYChart chart = buildChart(); + chart.getStyler().setLegendVisible(false); + + assertAxisLinesAtPlotMargin(chart); + } + + // Issue #1024: Axis_X.preparePaint() subtracted the OutsideS legend height from the top of the + // x-axis bounds even though the y-axis (whose bottom defines the plot bottom) had already + // reserved that space, so the x-axis line — anchored to that top — was drawn inside the plot + // area and painted over by the plot background. + @Test + void axisLinesSitAtPlotMarginWithOutsideSLegend() throws Exception { + + XYChart chart = buildChart(); + chart.getStyler().setLegendPosition(LegendPosition.OutsideS); + chart.getStyler().setLegendLayout(LegendLayout.Horizontal); + + assertAxisLinesAtPlotMargin(chart); + } + + private static XYChart buildChart() { + XYChart chart = new XYChartBuilder().width(800).height(600).title("t").build(); chart.addSeries("s", new double[] {1, 2, 3}, new double[] {10, 20, 30}); - chart.getStyler().setLegendVisible(false); chart.getStyler().setPlotGridLinesVisible(false); chart.getStyler().setPlotBackgroundColor(Color.YELLOW); chart.getStyler().setPlotBorderVisible(true); chart.getStyler().setPlotBorderColor(Color.RED); + return chart; + } + + private static void assertAxisLinesAtPlotMargin(XYChart chart) throws Exception { BufferedImage img = BitmapEncoder.getBufferedImage(chart);