Reputation: 15927
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
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
Results: Located in the same folder as your PieChartSample.jar file
Upvotes: 1
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)
Upvotes: 7