Sawyer
Sawyer

Reputation: 15927

Use JavaFX chart API to draw chart image

I only want to generate a chart image from JavaFX chart API. I don't want to show the app Window, or launch the application(if it's not necessary).

public class LineChartSample extends Application {
    private List<Integer> data;

    @Override public void start(Stage stage) {
        stage.setTitle("Line Chart Sample");
        final CategoryAxis xAxis = new CategoryAxis();
        final NumberAxis yAxis = new NumberAxis();
        xAxis.setLabel("Month");       

        final LineChart<String,Number> lineChart = 
                new LineChart<String,Number>(xAxis,yAxis);

        lineChart.setTitle("Stock Monitoring, 2010");

        XYChart.Series series = new XYChart.Series();
        series.setName("My portfolio");

        series.getData().add(new XYChart.Data("Jan", 23));
        series.getData().add(new XYChart.Data("Feb", 14));        

        Scene scene  = new Scene(lineChart,800,600);
        lineChart.getData().add(series);

        WritableImage image = scene.snapshot(null);
        ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", chartFile);

        //stage.setScene(scene);
        //stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }

   public setData(List<Integer> data) {this.data = data;}
}

Inside the start method, I actually need to access outside data in order to build up the series data, but there seems no way to access outside data from start method, if I store the data inside the member variable data, it's null when the start is called. I actually don't care about stage and scene object, as long as the chart image can be rendered, how should I solve the problem? I want to build a API that can be called with input data and draw the chart with the data, and returns the file.

public File toLineChart(List<Integer> data) {
...
}

Upvotes: 8

Views: 3200

Answers (2)

SedJ601
SedJ601

Reputation: 13859

From command line:

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.chart.*;
import javafx.scene.Group;
import javafx.scene.image.WritableImage;
import javax.imageio.ImageIO;

public class PieChartSample extends Application {

    private static String[] arguments; 

    @Override public void start(Stage stage) {
        Scene scene = new Scene(new Group());
        stage.setTitle("Imported Fruits");
        stage.setWidth(500);
        stage.setHeight(500);

        ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList();

//               
        final PieChart chart = new PieChart(pieChartData);
        chart.setTitle("Imported Fruits");
        for(int i = 0; i < arguments.length; i+=2)
        {
            System.out.println(arguments[i] + " " + arguments[i+1]);
            chart.getData().add(new PieChart.Data(arguments[i], Double.parseDouble(arguments[i+1])));
        }

        ((Group) scene.getRoot()).getChildren().add(chart);

        saveAsPng(scene);
        System.out.println("Done!");
        System.exit(0);
        //stage.setScene(scene);
        //stage.show();
    }

    public static void main(String[] args) {
        arguments = args;
        launch(args);
    }

    static void saveAsPng(Scene scene){
        try 
        {
            WritableImage image = scene.snapshot(null);

            File file = new File("tempPieChart.png");

            ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", file);
        } 
        catch (IOException ex) 
        {
            Logger.getLogger(PieChartSample.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}

Next: Clean and build your program. Then: Find you jar file in your dist folder After that: Navigate your command prompt to the dist folder your jar is in. Then run: java -jar PieChartSample.jar Banana 14 Orange 20 Grape 15

enter image description here

Results: Located in the same folder as your PieChartSample.jar file

enter image description here

Upvotes: 1

DVarga
DVarga

Reputation: 21829

You don't need to show a Stage but the Node must be attached to a Scene. From the doc of snapshot:

NOTE: In order for CSS and layout to function correctly, the node must be part of a Scene (the Scene may be attached to a Stage, but need not be).

One restriction to modify a Scene is, that it must happen on the JavaFX Application Thread, which has the pre-requisite that the JavaFX Toolkit must be initialized.

The initialization can be done by extending the Application class where the launch method will do it for you, or as a workaround you can create a new JFXPanel instance on the Swing Event Dispatcher Thread.

If you are extending Application and you execute some code in the start method, it is ensured that this code will be executed on the JavaFX Application Thread, otherwise you can use the Platform.runLater(...) block called from a different thread to ensure the same.

Here is a possible example:

The class provides one static method to plot a chart into a file and returns the File or null if the creation was successful or not.

In this method the JavaFX Toolkit is initialized by creating a JFXPanel on the Swing EDT then the chart creation is done JavaFX Application Thread. Two booleans are used in the method to store that the operation is completed and successful.

The method will not return until the completed flag switches to true.

Note: This one is really just a (working) example which could be improved a lot.

public class JavaFXPlotter {

    public static File toLineChart(String title, String seriesName, List<Integer> times, List<Integer> data) {

        File chartFile = new File("D:\\charttest.png");

        // results: {completed, successful}
        Boolean[] results = new Boolean[] { false, false };

        SwingUtilities.invokeLater(() -> {

            // Initialize FX Toolkit
            new JFXPanel();

            Platform.runLater(() -> {
                final NumberAxis xAxis = new NumberAxis();
                final NumberAxis yAxis = new NumberAxis();

                final LineChart<Number, Number> lineChart = new LineChart<Number, Number>(xAxis, yAxis);

                lineChart.setTitle(title);

                XYChart.Series<Number, Number> series = new XYChart.Series<>();
                series.setName(seriesName);

                for (int i = 0; i < times.size(); i++)
                    series.getData().add(new XYChart.Data<Number, Number>(times.get(i), data.get(i)));

                lineChart.getData().add(series);

                Scene scene = new Scene(lineChart, 800, 600);

                WritableImage image = scene.snapshot(null);

                try {
                    ImageIO.write(SwingFXUtils.fromFXImage(image, null), "png", chartFile);
                    results[1] = true;
                } catch (Exception e) {
                    results[0] = true;
                } finally {
                    results[0] = true;
                }
            });
        });

        while (!results[0]) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return (results[1]) ? chartFile : null;
    }


}

and a possible usage

List<Integer> times = Arrays.asList(new Integer[] { 0, 1, 2, 3, 4, 5 });
List<Integer> data = Arrays.asList(new Integer[] { 4, 1, 5, 3, 0, 7 });

File lineChart = JavaFXPlotter.toLineChart("Sample", "Some sample data", times, data);

if (lineChart != null)
    System.out.println("Image generation is done! Path: " + lineChart.getAbsolutePath());
else
    System.out.println("File creation failed!");

System.exit(0);

and the generated picture (charttest.png)

enter image description here

Upvotes: 7

Related Questions