Производительность 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);

Примечание: эта проблема одинакова для любого графика с большим количеством данных.

Другие вопросы по тегам