Как отображать потоковые данные реального времени с использованием AreaChart в JAVAFX 2 - Concurrency, анимация, диаграмма

Требование. Создайте анимированный AreaChart с потоковыми данными реального времени. Может составлять 300 точек данных каждые 1 сек.

детали- Поэтому мне нужно читать данные в реальном времени с помощью медицинского устройства, шаблона дыхания пациента и отображать его в форме волны с использованием AreaChart в JavaFX. Я новичок в JavaFX, поэтому я создал небольшую POC, чтобы увидеть, как concurrency и анимация работают в JavaFX.

Концепция работает, и я доволен базовым тестом, что касается реализации функциональности. Но меня не устраивает производительность, которую я получаю от кода ниже.

В приведенном ниже рабочем коде я создаю отдельный поток для имитации сбора данных из медицинского устройства. Поток просто генерирует случайное число и добавляет его в ConcurrentLinkedQueue.

Прикладной поток JavaFX извлекает эти данные из очереди через временную шкалу и добавляет ее в серию AreaChart.

Этот вид дает мне необходимую мне анимацию и данные добавляются во время выполнения. Вы можете скопировать и вставить этот код и протестировать его. Он должен работать.

НО производительность не впечатляет - процессор идет на 56% - у меня на моем ноутбуке установлен ядро ​​Intel Core 2 Duo @2.53 GHZ и 4 ГБ. Моя графическая карта - 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;
 }
 }
 }
 }
</actionevent></number,></number,number></number,number></number,number></number></number>
2 ответа

Как jewelsea, указанный в его комментарии:

Этот вопрос был перекрестком (и хорошо ответил) на форуме JavaFX форума.

Подводя итог, решение состояло в следующем:

  • Отключение анимации, поскольку оно предназначено для медленного изменения данных, чтобы оно было анимировано по прибытии;
  • Изменение Timeline на AnimationTimer, так как требуется обновить график каждого кадра, чтобы синхронизировать с входящими данными и двигаться как можно плавно;
  • Фиксирование резьбы как OP не требовало расширения Thread при использовании Executor. Изменение создания службы-исполнителя.


Большое падение производительности может произойти из сбора данных. Нет причин использовать ExecutorService и постоянно добавлять новый Threads для его выполнения, чтобы получить повторное добавление данных. Вы можете согласиться на один поток, который считывает/принимает данные и добавляет их в очередь и запускает их, вызывая addToQueue.start(). Чтобы он работал правильно, вы хотите, чтобы цикл работал непрерывно в потоке с задержкой в ​​конце каждой итерации.

licensed under cc by-sa 3.0 with attribution.