Производительность JavaFX LineChart

Я пытаюсь улучшить производительность LineChart в JavaFX, но без особого успеха. Я также обнаружил, что это, по-видимому, общая проблема, которую некоторые программисты обнаружили при попытке отобразить большие данные (здесь большой объем данных составляет 10 000). Например, такие данные довольно распространены в науке и технике, и было бы здорово, если бы мы смогли выяснить, как ускорить работу LineChart в JavaFX.

Ну, я нашел две записи здесь в stackoverflow с аналогичным вопросом Проблема производительности с JavaFX LineChart с 65000 точками данных и JavaFX LineChart - рисовать массив. Тема Проблема производительности с JavaFX LineChart с 65000 точками данных дает предложение (от Адама) использовать алгоритм Рамера-Дугласа-Пьюкера! для уменьшения количества точек данных в LineChart для ускорения.

Однако в научных и технических данных нам обычно нужно увидеть форму сюжета, а затем увеличить масштаб, чтобы увидеть детали в определенных частях сюжета. Следовательно, если мы будем использовать алгоритм Рамера-Дугласа-Пьюкера, нам потребуется перерисовать 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);
}
}

Как вы можете видеть, запускаете ли вы этот код, наибольшая стоимость заключается в рендеринге, который я не мог захватить с помощью этих таймингов. Тогда, на мой взгляд, улучшение должно быть сосредоточено там, но я не знаю, как это сделать.

Спасибо.

Ответ 1

Я также использую диаграммы JavaFX для научного приложения с десятками тысяч точек данных и смог в реальном времени достичь скорости обновления и построения графика. Есть две основные вещи, которые вам нужно сделать.

Во-первых, я думаю, что алгоритм Рамера-Дугласа-Пекера неоправданно сложен. Предполагая, что вы работаете с хорошей простой непрерывной функцией, гораздо легче заметить, что у нас только ограниченное разрешение экрана, и нам не нужно более трех или четырех точек данных для каждого пикселя в домене, чтобы передать как можно больше информации. насколько это возможно. Например, по одному для каждой первой и последней точки данных в пикселе, а по одному для максимума и минимума в пикселе.

Есть несколько вариантов этой стратегии, которые вы можете примерить, но основная идея - просто хороший быстрый однопроходный даунсампл, и он должен быть практически без потерь. (Или, точнее, это не должно добавить дополнительных потерь при представлении поверх потерь при растеризации.) Это также ограничивает количество точек чем-то управляемым, и, по моему опыту, достаточно быстр для перерисовки при масштабировании или для обновления данных в реальном времени. Однако это может вызвать проблемы, если вы используете масштабирование экрана для HiDPI или иным образом масштабируете компоненты графика.

Вторая часть также важна: даже если вы установили CSS, чтобы не рисовать фигуры точек данных, все равно требуется необъяснимо много времени, чтобы добавить узлы к сцене. Чтобы решить эту проблему, кажется, достаточно LineChart подкласс LineChart и переопределить метод dataItemAdded чтобы он не использовался в том случае, если вы не хотите рисовать фигуры. Вы также должны повторно использовать точки данных, которые уже добавлены в серию, где это возможно, а не добавлять новые, т. series.getData().get(i).setXValue(...) Предпочитайте series.getData().get(i).setXValue(...) и series.getData().get(i).setYValue(...) в series.setData(...) или series.getData().add(...).

Ответ 2

Надеюсь, что этот комментарий не напрасен или приходит слишком поздно:

Некоторые ограничения производительности являются неотъемлемой частью реализации JavaFX: т.е. многие операции вычисляются в JVM, а не передаются в базовый HW на основе OpenGL, точки данных - это чрезмерно большие Nodes нарисованные в графе сцены, а не сокращение данных на лету и использование Canvas... и многие другие., К сожалению, мы обнаружили, что многие из этих проблем (и ошибок) не могут быть решены без больших обходных путей (например, final методов API) вокруг исходного API-интерфейса JavaFX Chart.

Таким образом, мы разработали/перепроектировали (для нашего собственного приложения), с открытым исходным кодом и - в надежде, что другие сочтут это полезным или захотят внести свой вклад - опубликовали нашу библиотеку диаграмм на основе 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.png https://github.com/GSI-CS-CO/chart-fx/raw/master/docs/pics/chartfx-performance1a.png https://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 это мой первый пост, поэтому нет встроенных изображений

Ответ 3

Что улучшает производительность диаграмм со многими точками данных, заключается в удалении "формы" точек данных.

Свыше 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. datapoint или вычислите среднее количество точек данных.

Ответ 4

Спустя 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);

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