如何在 JavaFX 中创建动画折线图?
How do you create an animated LineChart in JavaFX?
我已经在 SceneBuilder 中创建了一个 LineChart,并且正在尝试练习如何向它输入数据,以便实时绘制它。
在我的 Controller
class 中,我有:
public class Controller {
...
@FXML private LineChart<Number, Number> chart;
@FXML private NumberAxis xAxis, yAxis;
private XYChart.Series<Number, Number> series;
private boolean run = true;
...
public init() {
xAxis.setAutoRanging(false);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setMinorTickVisible(false);
series = new XYChart.Series<Number, Number>();
series.setName("Data plot");
chart.setAnimated(false);
chart.getData().addAll(series);
}
...
//When a button is clicked, a signal is sent to my server that starts a
//tracking application. Then it enters the while loop. This code
//then makes requests to the server for data (getData()).
//This is the data that I would like to display.
@FXML
private track() {
connection.startTracking();
Platform.runLater(new Runnable() {
@Override
public void run() {
while(run) {
int[] data = connection.getData() //from somewhere else
dataManager.dataToSeries(data, series);
if(!series.getData.isEmpty()) {
chart.getData().retainAll();
chart.getData.addAll(series);
}
}
}
}
}
这里是 dataController
中的 dataToSeries()
方法
...
private void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
int yValue = 0;
int len = data.length;
for (int i = 0; i < len; i++) {
series.getData().add(new XYChart.Data<Number, Number>(data[i], yValue);
yValue++;
}
}
如果我打印出 Controller
中的系列,我会得到我期望的数据,但是它的格式是:
Series: [Data[11,1,null], Data[16,2,null], Data[21,3,null]
谁能解释为什么我的 LineChart
上没有显示这些数据?
您使用 Platform.runLater
.
在应用程序线程上 post 使用 Runnable
阻塞了 UI 线程
由于 track()
方法用 @FXML
注释,我怀疑这在 fxml 文件中用作 EventHandler
,因此无论如何在应用程序线程上使用 运行s这消除了使用 Platform.runLater
到 运行 应用程序线程上的代码的必要性。
为了不阻塞 UI 线程,循环应该在非应用程序线程上 运行 并且只有 UI 更新应该在应用程序线程上完成。此外,更新之间的小中断可能没有错。在这种情况下,ScheduledExecutorService
可以提供合适的调度:
示例 Application
代码:
private final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture future;
private Random random = new Random();
private int[] getData() {
int[] result = new int[10];
for (int i = 0; i < result.length; i++) {
result[i] = random.nextInt(10);
}
return result;
}
private static void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
int len = data.length;
int size = series.getData().size();
if (size > len) {
series.getData().subList(len, series.getData().size()).clear();
} else if (size < len) {
for (; size < len; size++) {
series.getData().add(new XYChart.Data<>(0, size));
}
}
for (int i = 0; i < len; i++) {
series.getData().get(i).setXValue(data[i]);
}
}
@Override
public void start(Stage primaryStage) {
ToggleButton btn = new ToggleButton("updating");
btn.setSelected(false);
XYChart.Series<Number, Number> series = new XYChart.Series<>();
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis(), FXCollections.observableArrayList(series));
chart.setAnimated(false);
Runnable dataGetter = () -> {
try {
// simulate some delay caused by the io operation
Thread.sleep(100);
} catch (InterruptedException ex) {
}
int[] data = getData();
Platform.runLater(() -> {
// update ui
dataToSeries(data, series);
});
};
btn.selectedProperty().addListener((a, b, newValue) -> {
if (newValue) {
// update every second
future = service.scheduleWithFixedDelay(dataGetter, 0, 1, TimeUnit.SECONDS);
} else {
// stop updates
future.cancel(true);
future = null;
}
});
VBox root = new VBox(10, chart, btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void stop() throws Exception {
service.shutdownNow();
}
基于Java SE Development Demos and Samples Downloads在rootFoder -> jdk.x.x.x -> demo -> javafx_samples
执行Ensemble8.jar.
在应用程序中转到 charts
部分并搜索 LineChart
你应该看到类似的东西
Image from Ensembre Application
然后点击查看源代码
这是一个实现示例。
@FXML
private LineChart<Number, Number> lineChart;
@FXML
private NumberAxis xAxis;
@FXML
private NumberAxis yAxis;
@Override
public void initialize(URL location, ResourceBundle resources) {
lineChart.setTitle("Stock Monitoring, 2010");
ObservableList<XYChart.Series<Number,Number>> dataChart =
FXCollections.observableArrayList(
new LineChart.Series("Serie 1",FXCollections.observableArrayList(
new XYChart.Data<>(1,2),
new XYChart.Data<>(5,3),
new XYChart.Data<>(9,3),
new XYChart.Data<>(2,7),
new XYChart.Data<>(6,9),
new XYChart.Data<>(1,2)
)),
new LineChart.Series<>("Series 2",FXCollections.observableArrayList(
new XYChart.Data<>(5,7),
new XYChart.Data<>(3,4),
new XYChart.Data<>(8,2),
new XYChart.Data<>(6,9),
new XYChart.Data<>(1,3),
new XYChart.Data<>(9,7)
)));
lineChart.setData(dataChart);
例如,要更改第一个列表的第二个点的 the "y" axis
Serie 1
,其当前值为 (x: 5, y: 3) 看起来像这样
dataChart.get(0).getData().get(1).setYValue(9);
我已经在 SceneBuilder 中创建了一个 LineChart,并且正在尝试练习如何向它输入数据,以便实时绘制它。
在我的 Controller
class 中,我有:
public class Controller {
...
@FXML private LineChart<Number, Number> chart;
@FXML private NumberAxis xAxis, yAxis;
private XYChart.Series<Number, Number> series;
private boolean run = true;
...
public init() {
xAxis.setAutoRanging(false);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
xAxis.setMinorTickVisible(false);
series = new XYChart.Series<Number, Number>();
series.setName("Data plot");
chart.setAnimated(false);
chart.getData().addAll(series);
}
...
//When a button is clicked, a signal is sent to my server that starts a
//tracking application. Then it enters the while loop. This code
//then makes requests to the server for data (getData()).
//This is the data that I would like to display.
@FXML
private track() {
connection.startTracking();
Platform.runLater(new Runnable() {
@Override
public void run() {
while(run) {
int[] data = connection.getData() //from somewhere else
dataManager.dataToSeries(data, series);
if(!series.getData.isEmpty()) {
chart.getData().retainAll();
chart.getData.addAll(series);
}
}
}
}
}
这里是 dataController
dataToSeries()
方法
...
private void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
int yValue = 0;
int len = data.length;
for (int i = 0; i < len; i++) {
series.getData().add(new XYChart.Data<Number, Number>(data[i], yValue);
yValue++;
}
}
如果我打印出 Controller
中的系列,我会得到我期望的数据,但是它的格式是:
Series: [Data[11,1,null], Data[16,2,null], Data[21,3,null]
谁能解释为什么我的 LineChart
上没有显示这些数据?
您使用 Platform.runLater
.
Runnable
阻塞了 UI 线程
由于 track()
方法用 @FXML
注释,我怀疑这在 fxml 文件中用作 EventHandler
,因此无论如何在应用程序线程上使用 运行s这消除了使用 Platform.runLater
到 运行 应用程序线程上的代码的必要性。
为了不阻塞 UI 线程,循环应该在非应用程序线程上 运行 并且只有 UI 更新应该在应用程序线程上完成。此外,更新之间的小中断可能没有错。在这种情况下,ScheduledExecutorService
可以提供合适的调度:
示例 Application
代码:
private final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture future;
private Random random = new Random();
private int[] getData() {
int[] result = new int[10];
for (int i = 0; i < result.length; i++) {
result[i] = random.nextInt(10);
}
return result;
}
private static void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
int len = data.length;
int size = series.getData().size();
if (size > len) {
series.getData().subList(len, series.getData().size()).clear();
} else if (size < len) {
for (; size < len; size++) {
series.getData().add(new XYChart.Data<>(0, size));
}
}
for (int i = 0; i < len; i++) {
series.getData().get(i).setXValue(data[i]);
}
}
@Override
public void start(Stage primaryStage) {
ToggleButton btn = new ToggleButton("updating");
btn.setSelected(false);
XYChart.Series<Number, Number> series = new XYChart.Series<>();
LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis(), FXCollections.observableArrayList(series));
chart.setAnimated(false);
Runnable dataGetter = () -> {
try {
// simulate some delay caused by the io operation
Thread.sleep(100);
} catch (InterruptedException ex) {
}
int[] data = getData();
Platform.runLater(() -> {
// update ui
dataToSeries(data, series);
});
};
btn.selectedProperty().addListener((a, b, newValue) -> {
if (newValue) {
// update every second
future = service.scheduleWithFixedDelay(dataGetter, 0, 1, TimeUnit.SECONDS);
} else {
// stop updates
future.cancel(true);
future = null;
}
});
VBox root = new VBox(10, chart, btn);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
@Override
public void stop() throws Exception {
service.shutdownNow();
}
基于Java SE Development Demos and Samples Downloads在rootFoder -> jdk.x.x.x -> demo -> javafx_samples
执行Ensemble8.jar.
在应用程序中转到 charts
部分并搜索 LineChart
你应该看到类似的东西 Image from Ensembre Application
然后点击查看源代码
这是一个实现示例。
@FXML
private LineChart<Number, Number> lineChart;
@FXML
private NumberAxis xAxis;
@FXML
private NumberAxis yAxis;
@Override
public void initialize(URL location, ResourceBundle resources) {
lineChart.setTitle("Stock Monitoring, 2010");
ObservableList<XYChart.Series<Number,Number>> dataChart =
FXCollections.observableArrayList(
new LineChart.Series("Serie 1",FXCollections.observableArrayList(
new XYChart.Data<>(1,2),
new XYChart.Data<>(5,3),
new XYChart.Data<>(9,3),
new XYChart.Data<>(2,7),
new XYChart.Data<>(6,9),
new XYChart.Data<>(1,2)
)),
new LineChart.Series<>("Series 2",FXCollections.observableArrayList(
new XYChart.Data<>(5,7),
new XYChart.Data<>(3,4),
new XYChart.Data<>(8,2),
new XYChart.Data<>(6,9),
new XYChart.Data<>(1,3),
new XYChart.Data<>(9,7)
)));
lineChart.setData(dataChart);
例如,要更改第一个列表的第二个点的 the "y" axis
Serie 1
,其当前值为 (x: 5, y: 3) 看起来像这样
dataChart.get(0).getData().get(1).setYValue(9);