Как отображать потоковые данные реального времени с использованием 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, указанный в его комментарии:

Этот вопрос был перекрестком (и хорошо ответил) на <a href="https://forums.oracle.com/forums/thread.jspa?threadID=2411087" rel="nofollow noreferrer" target="_blank">форуме JavaFX форума</a>.

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

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


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

licensed under cc by-sa 3.0 with attribution.