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) {
...
}
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With