Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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


  }
like image 627
FatherFigure Avatar asked Jul 03 '12 07:07

FatherFigure


1 Answers

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.
like image 181
marcelovca90 Avatar answered Nov 12 '22 04:11

marcelovca90