如何使用AreaChart在JAVAFX 2 - 并发,动画,图表中实时流式传输数据 [英] How to Chart real time streaming data using AreaChart in JAVAFX 2 - Concurrency, Animation, Charting

查看:1297
本文介绍了如何使用AreaChart在JAVAFX 2 - 并发,动画,图表中实时流式传输数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



Details -
所以我需要从医疗设备读取实时流数据,患者的呼吸模式,使用JavaFX中的AreaChart以波形方式显示它。
我是JavaFX的新手,所以我创建了一个小的POC,看看并发和动画在JavaFX中是如何工作的。



满足基本测试,就实现功能而言。但我对下面代码的性能不满意。



在下面的工作代码中,我创建一个单独的线程来模拟从医疗装置。线程只是生成一个随机数并将其添加到ConcurrentLinkedQueue。



JavaFX应用程序线程通过时间线将这些数据从队列中拉出,并将其添加到AreaChart系列。



这种给我的​​动画我需要和数据是在运行时添加。您可以复制粘贴此代码并测试它。它应该工作。



但性能不令人印象深刻 - CPU占用率达到56% - 我有一个Intel Core 2 Duo @ 2.53 GHZ和4GB ram在我的笔记本电脑上。我的显卡是Mobile Intel 4 Series express最新的驱动程序。



如何改善这个动画或绘制实时数据,以获得更好的性能? p>

注意:我愿意妥协动画,如果它的瓶颈。我可以使用像这里所示的实现
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;

/ **
*填充数据点线和轴之间区域的图表。
*适用于比较累积总计随时间的变化。
*
* @see javafx.scene.chart.Chart
* @see javafx.scene.chart.Axis
* @see javafx.scene.chart.NumberAxis
* @related图/线/ LineChart
* @related图/散布/ ScatterChart
* /
public class AreaChartSample extends应用程序{
private Series series;
private int xSeriesData = 0;
private ConcurrentLinkedQueue< Number> dataQ = new ConcurrentLinkedQueue< Number>();
private ExecutorService executor;
private AddToQueue addToQueue;
private时间轴timeline2;
private SequentialTransition动画;

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);

// - 图表系列
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();

// - 准备Executor服务
executor = Executors.newCachedThreadPool();
addToQueue = new AddToQueue();
executor.execute(addToQueue);


// - 准备时间线
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);
// - 将随机数添加到Q
dataQ.add(Math.random());
Thread.sleep(50);

executor.execute(addToQueue);

} catch(InterruptedException ex){
Logger.getLogger(AreaChartSample.class.getName())。log(Level.SEVERE,null,ex);
}

}
}

// - 在JavaFX主线程中调用时间线
private void prepareTimeline(){
// - 第二个较慢的时间线
timeline2 = new Timeline();
// - 这个时间线是不确定的。
timeline2.setCycleCount(Animation.INDEFINITE);

timeline2.getKeyFrames()。add(
new KeyFrame(Duration.millis(100),new EventHandler< ActionEvent>(){
@Override public void handle(ActionEvent actionEvent ){
addDataToSeries();

}
})
);

// - 设置动画 - 时间轴现已创建。
animation = new SequentialTransition();
animation.getChildren()。addAll(timeline2);
animation.play();

}

private void addDataToSeries(){

for(int i = 0; i <20; i ++){// 20数字到绘图
if(dataQ.isEmpty()== false){
series.getData()。add(new AreaChart.Data(xSeriesData ++,dataQ.remove()));

// - 从图表中删除一组
if(series.getData()。size()> 1000){
series.getData()。 remove(0,999);
}

}
else {
return;
}
}
}


}

解决方案

jewelsea 在他/她的评论中所述:


这个问题是在 Oracle JavaFX论坛主题


,解决方案包括:



  • 关闭动画,因为它是为慢速变化的数据而设计的,因此动画到达;

  • 时间表更改为 AnimationTimer 希望每一帧更新图表,以便与传入的数据保持同步并尽可能顺利地移动;

  • 将线程固定为OP不需要在使用执行程序时扩展Thread。更改执行程序服务的创建。



Requirement- Build an animated AreaChart with real time streaming data. Maybe plot 300 data points every 1 sec.

Details- So I need to read real time streaming data from a medical device, of a patient's breathing pattern and display it in a waveform fashion using AreaChart in JavaFX. I'm new to JavaFX and so I built a small POC, to see how concurrency and animation works in JavaFX.

The concept works and I'm happy with the basic test, as far as implementing the functionality. But I'm not happy with the performance I'm getting from the code below.

In the working code below, I create a separate thread to simulate data fetching from the medical device. The thread just generates a random number and adds it to a ConcurrentLinkedQueue.

The JavaFX Application thread pulls this data out from queue, via the Timeline, and adds it to an AreaChart Series.

This sort of gives me the animation I need and the data is being added in run time. You can copy-paste this code and test it..It should work.

BUT the performance is not impressive - CPU goes to 56% usage - I have a Intel Core 2 Duo @ 2.53 GHZ and 4GB ram on my laptop. My graphics card is Mobile Intel 4 Series express with latest driver.

How can I improve this animation or plotting of real time data, in order to get better performance?

NOTE: I'm willing to compromise on the animation, if its the bottle neck. I'm open to an implementation like shown here http://smoothiecharts.org/ where the waveform is just prebuilt and just streaming from right to left.

  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;
              }
          }
      }


  }

解决方案

As jewelsea stated in his/her comment:

This question was cross posted (and answered well) on an Oracle JavaFX forum thread.

To summarize, the solution consisted in:

  • Turning animation off as it is designed for slower changing data so that it is animated on arrival;
  • Changing the Timeline to a AnimationTimer as it is desired to update the chart every frame in order to keep in sync with incoming data and move as smoothly as possible;
  • Fixing threading as OP did not need to extend Thread when using a Executor. Changing the creation of the executor service.

这篇关于如何使用AreaChart在JAVAFX 2 - 并发,动画,图表中实时流式传输数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆