如何使用JAVAFX 2中的AreaChart绘制实时流数据 – 并发,动画,图表

要求 – 使用实时流数据构建动画AreaChart。 也许每1秒绘制300个数据点。

详细信息 – 因此,我需要从医疗设备读取患者呼吸模式的实时流数据,并使用JavaFX中的AreaChart以波形方式显示它。 我是JavaFX的新手,因此我构建了一个小型POC,以了解JavaFX中的并发和动画是如何工作的。

这个概念有效,我对基本测试感到满意,就实现function而言。 但是我对下面代码中的性能表示不满意。

在下面的工作代码中,我创建了一个单独的线程来模拟从医疗设备获取数据。 该线程只生成一个随机数并将其添加到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 dataQ = new ConcurrentLinkedQueue(); 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 sc = new AreaChart(xAxis,yAxis); sc.setId("liveAreaChart"); sc.setTitle("Animated Area Chart"); //-- Chart Series series=new AreaChart.Series(); series.setName("Area Chart Series"); series.getData().add(new AreaChart.Data(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() { @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 1000) { series.getData().remove(0,999); } } else{ return; } } } } 

正如珠宝在他/她的评论中所说:

这个问题在Oracle JavaFX论坛post上交叉发布(并且回答得很好)。

总而言之,解决方案包括:

  • 关闭动画,因为它设计用于较慢变化的数据,以便在到达时进行动画处理;
  • Timeline更改为AnimationTimer ,因为需要每帧更新图表,以便与传入数据保持同步并尽可能顺利地移动;
  • 修复线程作为OP在使用Executor时不需要扩展Thread。 更改执行程序服务的创建。

您的数据收集可能会带来巨大的性能下降。 没有理由使用ExecutorService并连续添加新Threads以便执行,以便重复添加数据。 您可以选择单个线程来读取/接收数据并将其添加到队列中并通过调用addToQueue.start()启动它。 为了使其正常工作,您希望循环在线程中连续运行,并在每次迭代结束时延迟。