要求 - 使用实时流数据构建动画AreaChart。也许每1秒绘制300个数据点。
详细信息 - 因此,我需要从医疗设备读取患者呼吸模式的实时流数据,并使用JavaFX中的AreaChart以波形方式显示它。 我是JavaFX的新手,因此我构建了一个小型POC,以了解JavaFX中的并发和动画是如何工作的。
该概念有效,我对基本测试感到满意,就实现功能而言。但是我对下面代码中的性能表示不满意。
在下面的工作代码中,我创建了一个单独的线程来模拟从医疗设备获取数据。该线程只生成一个随机数并将其添加到ConcurrentLinkedQueue。
JavaFX Application线程通过时间轴从队列中提取这些数据,并将其添加到AreaChart系列。
这种类型为我提供了我需要的动画,并且在运行时添加了数据。您可以复制粘贴此代码并对其进行测试。它应该可以正常工作。
但性能并不令人印象深刻 - CPU使用率达到56% - 我的笔记本电脑上有一块Intel Core 2 Duo @ 2.53 GHZ和4GB内存。我的显卡是采用最新驱动程序的Mobile Intel 4 Series Express。
如何改善此动画或绘制实时数据,以获得更好的性能?
注意:如果它是瓶颈,我愿意在动画上妥协。我对这里显示的实现持开放态度 http://smoothiecharts.org/其中波形只是预建,只是从右到左流式传输。
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.chart.AreaChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
import javafx.util.Duration;
/**
* A chart that fills in the area between a line of data points and the axes.
* Good for comparing accumulated totals over time.
*
* @see javafx.scene.chart.Chart
* @see javafx.scene.chart.Axis
* @see javafx.scene.chart.NumberAxis
* @related charts/line/LineChart
* @related charts/scatter/ScatterChart
*/
public class AreaChartSample extends Application {
private Series series;
private int xSeriesData=0;
private ConcurrentLinkedQueue<Number> dataQ = new ConcurrentLinkedQueue<Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private Timeline timeline2;
private SequentialTransition animation;
private void init(Stage primaryStage) {
Group root = new Group();
primaryStage.setScene(new Scene(root));
NumberAxis xAxis = new NumberAxis();
xAxis.setAutoRanging(true);
NumberAxis yAxis = new NumberAxis();
yAxis.setAutoRanging(true);
//-- Chart
final AreaChart<Number,Number> sc = new AreaChart<Number,Number>(xAxis,yAxis);
sc.setId("liveAreaChart");
sc.setTitle("Animated Area Chart");
//-- Chart Series
series=new AreaChart.Series<Number,Number>();
series.setName("Area Chart Series");
series.getData().add(new AreaChart.Data<Number, Number>(5d, 5d));
sc.getData().add(series);
root.getChildren().add(sc);
}
@Override public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
//-- Prepare Executor Services
executor = Executors.newCachedThreadPool();
addToQueue=new AddToQueue();
executor.execute(addToQueue);
//-- Prepare Timeline
prepareTimeline();
}
public static void main(String[] args) { launch(args); }
private class AddToQueue extends Thread {
public void run(){
try {
Thread.currentThread().setName(Thread.currentThread().getId()+"-DataAdder");
//-- Add Random numbers to Q
dataQ.add(Math.random());
Thread.sleep(50);
executor.execute(addToQueue);
} catch (InterruptedException ex) {
Logger.getLogger(AreaChartSample.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
//-- Timeline gets called in the JavaFX Main thread
private void prepareTimeline(){
//-- Second slower timeline
timeline2 = new Timeline();
//-- This timeline is indefinite.
timeline2.setCycleCount(Animation.INDEFINITE);
timeline2.getKeyFrames().add(
new KeyFrame(Duration.millis(100), new EventHandler<ActionEvent>() {
@Override public void handle(ActionEvent actionEvent) {
addDataToSeries();
}
})
);
//-- Set Animation- Timeline is created now.
animation = new SequentialTransition();
animation.getChildren().addAll(timeline2);
animation.play();
}
private void addDataToSeries(){
for(int i=0;i<20;i++){ //-- add 20 numbers to the plot
if(dataQ.isEmpty()==false) {
series.getData().add(new AreaChart.Data(xSeriesData++,dataQ.remove()));
//-- Get rid of a bunch from the chart
if (series.getData().size() > 1000) {
series.getData().remove(0,999);
}
}
else{
return;
}
}
}
}
答案 0 :(得分:1)
正如jewelsea在他/她的评论中所述:
这个问题在Oracle JavaFX forum thread上交叉发布(并回答得很好)。
总而言之,解决方案包括:
- 关闭动画,因为它设计用于较慢变化的数据,以便在到达时进行动画制作;
- 将
Timeline
更改为AnimationTimer
,因为每个帧都需要更新图表,以便与传入的数据保持同步并尽可能顺利地移动;- 修复线程,因为OP在使用Executor时不需要扩展Thread。更改执行程序服务的创建。
答案 1 :(得分:0)
您的数据收集可能会带来巨大的性能下降。没有理由使用ExecutorService
并继续添加新的Threads
以便执行,以便重复添加数据。您可以选择单个线程来读取/接收数据并将其添加到队列中并通过调用addToQueue.start()
来启动它。为了使它正常工作,你需要一个循环在线程中连续运行,并在每次迭代结束时延迟。