Производительность JavaFX LineChart
Я пытался улучшить производительность LineChart в JavaFX, но без особого успеха. Я также обнаружил, что это, кажется, распространенная проблема, которую некоторые программисты обнаружили при попытке отобразить большие данные (большой здесь означает размер данных> 10000). Например, такие данные довольно распространены в науке и технике, и было бы здорово, если бы мы могли выяснить, как ускорить LineChart в JavaFX.
Что ж, я нашел здесь два сообщения в потоке стека с похожим вопросом: проблема производительности в JavaFX LineChart с 65000 точками данных и JavaFX LineChart - массив рисования. Тема Проблемы производительности с JavaFX LineChart с 65000 точками данных заканчивается предложением (Адамом) использовать алгоритм Рамера-Дугласа-Пекера! уменьшить количество точек данных в LineChart, чтобы ускорить.
Однако в научных и технических данных нам обычно нужно видеть форму графика, а затем увеличивать масштаб, чтобы увидеть детали в определенных частях графика. Следовательно, если мы используем алгоритм Ramer-Douglas-Peucker, мы должны будем перерисовывать LineChart каждый раз, когда пользователь увеличивает / уменьшает масштаб, что, я думаю, будет стоить больших затрат на обработку.
Поэтому я хотел бы знать, есть ли у кого-нибудь несколько советов, как ускорить LineChart в JavaFX. Вот пример кода, содержащий то, что я изучил до сих пор.
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class TestingLineChart extends Application {
@Override
public void start(Stage primaryStage) {
long startTime, endTime;
startTime = System.nanoTime();
StackPane root = new StackPane();
NumberAxis xAxis = new NumberAxis();
NumberAxis yAxis = new NumberAxis();
LineChart<Number, Number> lineChartPlot = new LineChart<>(xAxis, yAxis);
// set them false to make the plot faster
lineChartPlot.setAnimated(false);
lineChartPlot.setCreateSymbols(false);
List<XYChart.Data<Double, Double>> data = new ArrayList<>();
Scene scene = new Scene(root, 300, 250);
endTime = System.nanoTime();
System.out.println("Time (ms) for creation: " + (endTime - startTime)/1e6);
startTime = System.nanoTime();
for (int n = 0; n < 1e5; n++) {
data.add(new XYChart.Data(n, Math.random()));
}
endTime = System.nanoTime();
System.out.println("Time (ms) for adding data: " + (endTime - startTime)/1e6);
startTime = System.nanoTime();
XYChart.Series dataSeries = new XYChart.Series<>();
dataSeries.setName("data"); // taking the data
dataSeries.getData().addAll(data); // taking the data
endTime = System.nanoTime();
System.out.println("Time (ms) for adding data to series: " + (endTime - startTime)/1e6);
startTime = System.nanoTime();
lineChartPlot.getData().add(dataSeries);
endTime = System.nanoTime();
System.out.println("Time (ms) for adding data to LineChart: " + (endTime - startTime)/1e6);
startTime = System.nanoTime();
root.getChildren().add(lineChartPlot);
endTime = System.nanoTime();
System.out.println("Time (ms) for adding LineChart StackPane: " + (endTime - startTime)/1e6);
startTime = System.nanoTime();
primaryStage.setTitle("Hello World!");
primaryStage.setScene(scene);
primaryStage.show();
endTime = System.nanoTime();
System.out.println("Time (ms) for showing: " + (endTime - startTime)/1e6);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Как вы можете видеть, запускаете ли вы этот код, самая большая стоимость - рендеринг, который я не смог отловить, используя эти временные параметры. Тогда, на мой взгляд, улучшение должно быть сосредоточено там, но я не знаю как.
Спасибо.
2 ответа
Я также использую диаграммы JavaFX для научного приложения с десятками тысяч точек данных и смог достичь скорости в реальном времени для обновления и рисования графика. Есть две основные вещи, которые вам нужно сделать.
Во-первых, я думаю, что алгоритм Рамера-Дугласа-Пекера неоправданно сложен. Предполагая, что вы работаете с хорошей простой непрерывной функцией, гораздо легче заметить, что у нас только ограниченное разрешение экрана, и нам не нужно больше, чем максимум три или четыре точки данных для каждого пикселя в домене, чтобы передать как можно больше информации. насколько это возможно. Например, по одному для каждой первой и последней точки данных в пикселе, а по одному для максимума и минимума в пикселе.
Есть несколько вариантов этой стратегии, которые вы можете примерить, но основная идея - просто хороший быстрый однопроходный даунсампл, и он должен быть практически без потерь. (Или, точнее, это не должно добавить дополнительных потерь при представлении поверх потерь при растеризации.) Это также ограничивает количество точек чем-то управляемым, и, по моему опыту, достаточно быстр для перерисовки при масштабировании или для обновления данных в реальном времени. Однако это может вызвать проблемы, если вы используете масштабирование экрана для HiDPI или иным образом масштабируете компоненты графика.
Вторая часть также важна: даже если вы установили CSS, чтобы не рисовать фигуры точек данных, все равно требуется необъяснимо много времени, чтобы добавить узлы к сцене. Для решения этого кажется достаточным для подкласса LineChart
и переопределить dataItemAdded
метод, чтобы быть неактивным в том случае, если вы не хотите рисовать фигуры. Вы должны также повторно использовать точки данных, которые уже добавлены к серии, где это возможно, а не добавлять новые, т.е. series.getData().get(i).setXValue(...)
а также series.getData().get(i).setYValue(...)
в series.setData(...)
или же series.getData().add(...)
,
Надеюсь, этот комментарий не напрасен или придет слишком поздно:
Некоторые ограничения производительности присущи реализации JavaFX: т.е. многие операции вычисляются в JVM, а не передаются в базовое аппаратное обеспечение на основе OpenGL, точки данных являются чрезмерно большимиNodes
нарисованный внутри графа сцены, а не сокращение данных на лету и использование Canvas
... назвать несколько. К сожалению, мы обнаружили, что многие из этих проблем (и ошибок) не могут быть решены без более серьезных обходных путей (например,final
Методы API) вокруг оригинального JavaFX Chart
API.
Таким образом, мы разработали / перепроектировали (для нашего внутреннего приложения), открыли исходный код и - в надежде, что другие сочтут его полезным или захотят внести свой вклад - опубликовали нашу библиотеку диаграмм на основе JavaFX на GitHub:
https://github.com/GSI-CS-CO/chart-fx
Его основная цель: оптимизированная для производительности визуализация данных в реальном времени с частотой обновления 25 Гц для наборов данных от нескольких 10 тысяч до 5 миллионов точек данных, обычных в приложениях для обработки цифровых сигналов. Графики производительности, примеры и документация доступны на GitHub:
https://github.com/GSI-CS-CO/chart-fx/raw/master/docs/pics/chartfx-example1.pnghttps://github.com/GSI-CS-CO/chart-fx/raw/master/docs/pics/chartfx-performance1a.pnghttps://github.com/GSI-CS-CO/chart-fx/raw/master/docs/pics/chartfx-performance1.png
Мотивы, по которым была необходима новая библиотека JavaFX, и сравнение производительности с другими библиотеками на основе Java и C++/Qt были представлены на IPAC'19: https://ipac2019.vrws.de/papers/thprb028.pdf
NB, это мой первый пост, поэтому встроенных изображений нет
Что улучшает производительность диаграмм с множеством точек данных, так это удаление "формы" точек данных.
Более CSS, например:
.chart-line-symbol {
-fx-shape: "";
}
или чтобы убедиться, что он вообще не виден:
.chart-line-symbol {
-fx-background-insets: 0,0;
-fx-background-radius: 0px;
-fx-padding: 0px;
-fx-shape: "";
-fx-background-color: transparent;
}
Другой подход заключается в удалении некоторых точек данных с вашего графика. Так, например, используйте только 3. каждую точку данных или рассчитайте среднее значение для нескольких точек данных.
Спустя 3 года это не окончательное решение, так как есть проблемы с производительностью больших данных, но это очень помогает (конечно, с этим диаграмма будет проще):
CSS (вы также можете немного поиграть с другими элементами в CSS)
.chart-vertical-grid-lines, .chart-horizontal-grid-lines {
-fx-stroke: transparent;
}
Контроллер (эта часть занимает больше времени)
lineChart.setCreateSymbols(false); //this part is the one that consumes more time
lineChart.setAnimated(false);
Примечание: эта проблема одинакова для любого графика с большим количеством данных.