Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions xchart/src/test/java/org/knowm/xchart/CategoryChartTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
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;
import org.knowm.xchart.custom.CustomGraphic;
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 {

Expand Down Expand Up @@ -245,4 +248,75 @@ void paint() {
.contains(chart.getStyler().getDefaultSeriesRenderStyle());
}
}

/**
* Regression test for <a href="https://github.com/knowm/XChart/issues/707">issue 707</a>.
*
* <p>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.
*
* <p>To catch a visual regression we render to a {@link BufferedImage} and compare
* pixel-diff counts (labels-on minus labels-off) between:
*
* <ol>
* <li>A 5-bar chart with all values present (all 5 labels rendered).
* <li>The same 5-bar chart where the last value is {@link Double#NaN} (4 labels rendered,
* same x/y axis extents and bar positions).
* </ol>
*
* <p>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<Double> 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<Double> xData, List<Double> 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;
}
}