diff --git a/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java b/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
index 008b404d..3c042ea6 100644
--- a/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
+++ b/xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
@@ -6,6 +6,8 @@
import static org.knowm.xchart.style.Styler.ChartTheme.GGPlot2;
import static org.knowm.xchart.style.Styler.ChartTheme.XChart;
+import java.awt.Color;
+import java.awt.image.BufferedImage;
import java.util.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -13,6 +15,7 @@
import org.knowm.xchart.custom.CustomTheme;
import org.knowm.xchart.internal.series.Series;
import org.knowm.xchart.style.Styler;
+import org.knowm.xchart.style.Styler.ChartTheme;
public class CategoryChartTest {
@@ -245,4 +248,75 @@ void paint() {
.contains(chart.getStyler().getDefaultSeriesRenderStyle());
}
}
+
+ /**
+ * Regression test for issue 707.
+ *
+ *
The last bar in a Bar-style CategoryChart was drawn twice (overwriting its label) because
+ * the bar loop reused the shared {@code path} variable that {@code closePath()} consumes after
+ * the loop. The fix uses a local {@code barPath} so {@code path} stays {@code null} for bar
+ * series and {@code closePath()} becomes a no-op.
+ *
+ *
To catch a visual regression we render to a {@link BufferedImage} and compare
+ * pixel-diff counts (labels-on minus labels-off) between:
+ *
+ *
+ * - A 5-bar chart with all values present (all 5 labels rendered).
+ *
- The same 5-bar chart where the last value is {@link Double#NaN} (4 labels rendered,
+ * same x/y axis extents and bar positions).
+ *
+ *
+ * When the bug is reintroduced the 5th label is overwritten by the bar fill, making its
+ * diff indistinguishable from the NaN chart's diff. The assertion {@code diff5 > diffNaN}
+ * therefore fails precisely when and only when the 5th label is missing.
+ */
+ @Test
+ void issue707LastBarLabelIsVisibleAndNotOverwritten() throws Exception {
+ List xData = Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0);
+
+ // Chart A — all 5 values present; should render 5 labels.
+ CategoryChart chart5On = buildIssue707Chart(xData, Arrays.asList(2.0, 1.5, 4.0, 3.77, 2.5), true);
+ CategoryChart chart5Off = buildIssue707Chart(xData, Arrays.asList(2.0, 1.5, 4.0, 3.77, 2.5), false);
+
+ // Chart B — last value is NaN so bar 5 is skipped; only 4 labels are rendered.
+ CategoryChart chartNaNOn =
+ buildIssue707Chart(
+ xData, Arrays.asList(2.0, 1.5, 4.0, 3.77, Double.NaN), true);
+ CategoryChart chartNaNOff =
+ buildIssue707Chart(
+ xData, Arrays.asList(2.0, 1.5, 4.0, 3.77, Double.NaN), false);
+
+ int diff5 = countDiffPixels(BitmapEncoder.getBufferedImage(chart5On), BitmapEncoder.getBufferedImage(chart5Off));
+ int diffNaN = countDiffPixels(BitmapEncoder.getBufferedImage(chartNaNOn), BitmapEncoder.getBufferedImage(chartNaNOff));
+
+ // When the fix is in place, diff5 > diffNaN because the 5th label contributes pixels.
+ // When the bug is reintroduced, the 5th label is overwritten and diff5 == diffNaN.
+ assertThat(diff5)
+ .as(
+ "labels-on/off pixel diff for 5-bar chart (%d) must exceed that of the same chart "
+ + "with NaN last bar (%d); equality means the 5th label was overwritten",
+ diff5,
+ diffNaN)
+ .isGreaterThan(diffNaN);
+ }
+
+ private CategoryChart buildIssue707Chart(
+ List xData, List yData, boolean labelsVisible) {
+ CategoryChart chart = new CategoryChart(800, 600, ChartTheme.Matlab);
+ chart.getStyler().setLabelsVisible(labelsVisible);
+ chart.getStyler().setLabelsFontColorAutomaticEnabled(false);
+ chart.getStyler().setLabelsFontColor(new Color(255, 0, 255));
+ chart.addSeries("y(x)", xData, yData);
+ return chart;
+ }
+
+ private int countDiffPixels(BufferedImage a, BufferedImage b) {
+ int count = 0;
+ for (int x = 0; x < a.getWidth(); x++) {
+ for (int y = 0; y < a.getHeight(); y++) {
+ if (a.getRGB(x, y) != b.getRGB(x, y)) count++;
+ }
+ }
+ return count;
+ }
}