From 6501fe91fe7389f2c99735cff6586369bd941701 Mon Sep 17 00:00:00 2001 From: Tim Molter Date: Wed, 7 Oct 2026 13:49:18 +0200 Subject: [PATCH] Add Chart.setSize so a chart can be resized before export A chart's width and height could only be set through the builder, so a chart constructed by code the caller does not control could not be exported at a chosen size. setSize is a public fluent setter that updates both dimensions, which every exporter already reads from the chart. Fixes #807 Co-Authored-By: Claude Fable 5.1 --- README.md | 7 ++ .../standalone/issues/TestForIssue807.java | 36 +++++++ .../xchart/internal/chartpart/Chart.java | 20 ++++ .../java/org/knowm/xchart/ChartSizeTest.java | 94 +++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue807.java create mode 100644 xchart/src/test/java/org/knowm/xchart/ChartSizeTest.java diff --git a/README.md b/README.md index 56140d4a..b14b8078 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,13 @@ ChartEncoder.saveChart(chart, outputStream, "png"); byte[] bytes = ChartEncoder.getBytes(chart, "png"); ``` +To export a chart at a different size than it was built with, for example a chart constructed by code you don't control, resize it first with `setSize`: + +```java +chart.setSize(1920, 1080); +ChartEncoder.saveChart(chart, "./MyChart", "png"); +``` + * **Raster formats** (`png`, `jpg`, `bmp`, `gif`, `tiff`) are delegated to `javax.imageio.ImageIO`. Any other format for which an `ImageWriter` plugin is registered on the classpath works too — e.g. add `com.github.usefulness:webp-imageio` to export `webp` (see the `ExampleWebP` demo), or an AVIF plugin to export `avif`. Call `ChartEncoder.getSupportedRasterFormats()` to see what the current classpath can write. For AVIF, where no bundleable ImageIO writer exists yet, the `ExampleAvif` demo shows a portable fallback that pipes a PNG through the `avifenc` CLI (`brew install libavif`). * **Vector formats** (`svg`, `eps`, `pdf`) require the optional dependencies below. diff --git a/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue807.java b/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue807.java new file mode 100644 index 00000000..6730273e --- /dev/null +++ b/xchart-demo/src/main/java/org/knowm/xchart/standalone/issues/TestForIssue807.java @@ -0,0 +1,36 @@ +package org.knowm.xchart.standalone.issues; + +import java.io.IOException; +import org.knowm.xchart.ChartEncoder; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.XYChartBuilder; + +/** + * Demonstrates resizing an already constructed chart before export, so a chart received from + * elsewhere can be saved at any dimensions. https://github.com/knowm/XChart/issues/807 + */ +public class TestForIssue807 { + + public static void main(String[] args) throws IOException { + + XYChart chart = getChart(); + chart.setSize(1280, 720); + ChartEncoder.saveChart(chart, "./TestForIssue807", "png"); + new SwingWrapper<>(chart).displayChart(); + } + + public static XYChart getChart() { + + XYChart chart = + new XYChartBuilder() + .width(640) + .height(480) + .title("Resized before export") + .xAxisTitle("Time") + .yAxisTitle("Value") + .build(); + chart.addSeries("Sample", new double[] {0, 1, 2, 3, 4}, new double[] {1, 3, 2, 5, 4}); + return chart; + } +} diff --git a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java index 8c63fab1..d3924370 100644 --- a/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java +++ b/xchart/src/main/java/org/knowm/xchart/internal/chartpart/Chart.java @@ -96,6 +96,26 @@ protected void setHeight(int height) { this.height = height; } + /** + * Sets the chart's width and height in pixels. This is useful for a chart constructed elsewhere + * that should be exported or displayed at a different size than it was built with. Exporters + * such as {@code ChartEncoder} lay the chart out at these dimensions. + * + * @param width the new width, greater than 0 + * @param height the new height, greater than 0 + * @return this chart, for chaining + * @throws IllegalArgumentException if either dimension is not positive + */ + public Chart setSize(int width, int height) { + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Chart width and height must be greater than 0 !!!"); + } + this.width = width; + this.height = height; + return this; + } + // TODO remove public public String getTitle() { diff --git a/xchart/src/test/java/org/knowm/xchart/ChartSizeTest.java b/xchart/src/test/java/org/knowm/xchart/ChartSizeTest.java new file mode 100644 index 00000000..dc7b945a --- /dev/null +++ b/xchart/src/test/java/org/knowm/xchart/ChartSizeTest.java @@ -0,0 +1,94 @@ +package org.knowm.xchart; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.imageio.ImageIO; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.junit.jupiter.api.Test; + +/** https://github.com/knowm/XChart/issues/807 */ +public class ChartSizeTest { + + private static XYChart sampleChart() { + XYChart chart = new XYChartBuilder().width(500).height(400).title("Sample").build(); + chart.addSeries("y(x)", new double[] {0, 1, 2}, new double[] {2, 1, 0}); + return chart; + } + + @Test + public void setSizeUpdatesDimensionsAndChains() { + + XYChart chart = sampleChart(); + assertThat(chart.setSize(1920, 1080)).isSameAs(chart); + assertThat(chart.getWidth()).isEqualTo(1920); + assertThat(chart.getHeight()).isEqualTo(1080); + } + + @Test + public void setSizeRejectsNonPositiveDimensions() { + + XYChart chart = sampleChart(); + for (int[] size : new int[][] {{0, 100}, {100, 0}, {-1, 100}, {100, -1}}) { + assertThatThrownBy(() -> chart.setSize(size[0], size[1])) + .isInstanceOf(IllegalArgumentException.class); + } + assertThat(chart.getWidth()).isEqualTo(500); + assertThat(chart.getHeight()).isEqualTo(400); + } + + @Test + public void rasterExportUsesNewSize() throws IOException { + + XYChart chart = sampleChart(); + chart.setSize(800, 300); + BufferedImage image = + ImageIO.read(new ByteArrayInputStream(ChartEncoder.getBytes(chart, "png"))); + assertThat(image.getWidth()).isEqualTo(800); + assertThat(image.getHeight()).isEqualTo(300); + } + + @Test + public void vectorExportUsesNewSize() throws IOException { + + XYChart chart = sampleChart(); + chart.setSize(720, 360); + + String svg = new String(ChartEncoder.getBytes(chart, "svg"), StandardCharsets.UTF_8); + Matcher viewBox = Pattern.compile("viewBox=\"([^\"]+)\"").matcher(svg); + assertThat(viewBox.find()).isTrue(); + String[] bounds = viewBox.group(1).trim().split("\\s+"); + assertThat(Double.parseDouble(bounds[2])).isEqualTo(720.0); + assertThat(Double.parseDouble(bounds[3])).isEqualTo(360.0); + + try (PDDocument pdf = Loader.loadPDF(ChartEncoder.getBytes(chart, "pdf"))) { + assertThat(pdf.getPage(0).getMediaBox().getWidth()).isEqualTo(720.0f); + assertThat(pdf.getPage(0).getMediaBox().getHeight()).isEqualTo(360.0f); + } + } + + @Test + public void setSizeWorksForOtherChartTypes() throws IOException { + + PieChart pie = new PieChartBuilder().width(500).height(400).build(); + pie.addSeries("A", 3); + pie.setSize(300, 600); + BufferedImage image = ImageIO.read(new ByteArrayInputStream(ChartEncoder.getBytes(pie, "png"))); + assertThat(image.getWidth()).isEqualTo(300); + assertThat(image.getHeight()).isEqualTo(600); + + CategoryChart bars = new CategoryChartBuilder().width(500).height(400).build(); + bars.addSeries("Sample", new double[] {1, 2, 3}, new double[] {2, 4, 3}); + bars.setSize(640, 200); + image = ImageIO.read(new ByteArrayInputStream(ChartEncoder.getBytes(bars, "png"))); + assertThat(image.getWidth()).isEqualTo(640); + assertThat(image.getHeight()).isEqualTo(200); + } +}