java 如何在 JAVAFX 2 中使用 AreaChart 绘制实时流数据图表 - 并发、动画、图表

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11306710/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 04:36:58  来源:igfitidea点击:

How to Chart real time streaming data using AreaChart in JAVAFX 2 - Concurrency, Animation, Charting

javaconcurrencychartsjavafx-2

提问by FatherFigure

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

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

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.

详细信息 - 所以我需要从医疗设备读取患者呼吸模式的实时流数据,并使用 JavaFX 中的 AreaChart 以波形方式显示它。我是 JavaFX 的新手,所以我构建了一个小型 POC,以了解 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.

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

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

JavaFX 应用程序线程通过时间轴从队列中提取此数据,并将其添加到区域图表系列中。

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.

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

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.

注意:如果是瓶颈,我愿意在动画上妥协。我对像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;
              }
          }
      }


  }

回答by marcelovca90

As jewelseastated in his/her comment:

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

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

这个问题在Oracle JavaFX 论坛线程上交叉发布(并得到了很好的回答)。

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 Timelineto a AnimationTimeras 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.
  • 关闭动画,因为它是为较慢的数据变化而设计的,以便在到达时进行动画处理;
  • 将 更改Timeline为 a AnimationTimer,因为需要每帧更新图表,以便与传入数据保持同步并尽可能平滑移动;
  • 使用 Executor 时,将线程修复为 OP 不需要扩展 Thread。更改执行程序服务的创建。

回答by tomtzook

A big performence drop could come from your data gathering. There is no reason to use an ExecutorServiceand continuosly add new Threadsfor execution by it in order to get repetitive data adding. You could settle for a single thread which reads/receives the data and adds it to the queue and start it by calling addToQueue.start(). For it to work properly, you want a loop to run continously in the thread with a delay at the end of each iteration.

您的数据收集可能会导致性能大幅下降。没有理由使用 anExecutorService并不断添加 newThreads由它执行以获取重复的数据添加。您可以使用一个线程来读取/接收数据并将其添加到队列中并通过调用addToQueue.start(). 为了使其正常工作,您希望循环在线程中连续运行,并在每次迭代结束时延迟。