Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX LineChart Performance

I've been trying to improve the performance of LineChart in JavaFX but without great success. I also have found that this seems to be a common problem that some programmers have found when trying to display big data (big here stands for datasize > 10,000). For instance, this kind of data is pretty common in the science and engineering and it would be great if we could figure out how to speed up the LineChart in JavaFX.

Well, I found two posts here in the stackoverflow with a similar question Performance issue with JavaFX LineChart with 65000 data points and JavaFX LineChart - draw array. The topic Performance issue with JavaFX LineChart with 65000 data points ends up with a suggestion (by Adam) to use the Ramer–Douglas–Peucker algorithm! to reduce the number of data points into the LineChart in order to speed up.

However, in scientific and engineering data, we usually need to see the plot shape and then zoom in to see the details in specific parts of the plot. Hence, if we use Ramer-Douglas-Peucker algorithm, we would be required to redraw the LineChart every time the user zoom in/out that, I think, would cost a lot of processing.

Therefore, I would like to know if someone have some tips in how to speed up the LineChart in JavaFX. Here is an example code containing what I've learned so far.

    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);
}
}

As you can see if you run this code, the greatest cost is in rendering, which I could not capture using those timings. Then, in my view, the improvement should be focused there but I do not know how.

Thank you.

like image 514
Hugo Fernando Maia Milan Avatar asked Jan 13 '16 16:01

Hugo Fernando Maia Milan


2 Answers

I am also using JavaFX charts for a scientific application with tens of thounsands of data points, and have been able to achieve real-time speeds for updating and drawing a graph. There are two main things you need to do.

Firstly, the Ramer-Douglas-Peucker algorithm is unnecessarily complex, I think. Assuming you're working with a nice simple continuous function it's much easier to observe that we only have a limited display resolution and we don't need more than at most three or four data points for every pixel in the domain to convey as much information as possible. For example, one each for the first and last data point to occur within a pixel, and one each for the maximum and minimum within the pixel.

There are a few variations you can try on this strategy but the basic idea is just a nice quick single-pass downsample, and it should basically be lossless. (Or more accurately, it should add no extra representational loss on top of that of rasterisation.) It also limits the number of points to something manageable, and in my experience is fast enough to redraw on zoom or for real-time data updates. It may however cause problems if you have display scaling for HiDPI or are otherwise scaling your graph components for any reason.

The second part is just as important: even if you set the css to not draw the data point shapes, it still takes an inexplicably long time to add the nodes to the scene. To address this it seems sufficient to subclass LineChart and override the dataItemAdded method to be a no-op in the case that you don't want shapes drawn. You should also re-use the data points which are already added to the series where possible rather than add new ones, i.e. prefer series.getData().get(i).setXValue(...) and series.getData().get(i).setYValue(...) to series.setData(...) or series.getData().add(...).

like image 174
Elias Vasylenko Avatar answered Oct 21 '22 04:10

Elias Vasylenko


Hope this comment is not in vain or comes too late:

Some of the performance limitations are intrinsic to the JavaFX implementation: ie. many operations being computed within the JVM rather than being pushed to the underlying OpenGL-based HW, data points being excessively large Nodes drawn within a scene graph rather than on-the-fly data reduction and usage of Canvas... to name a few. Unfortunately, we found that many of these issues (and bugs) could not be solved without larger workarounds (e.g.final API methods) around the original JavaFX Chart API.

We thus developed/re-designed (for our in-house application), open-sourced, and -- in the hope that others find it useful or want to contribute -- published our JavaFX-based charting library at GitHub:

https://github.com/GSI-CS-CO/chart-fx

Its primary focus: performance optimised real-time data visualisation at 25 Hz update rates for data sets with a few 10 thousand up to 5 million data points common in digital signal processing applications. Performance plots, examples and documentation are available at 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

The motivations why a new JavaFX library was necessary and performance comparisons w.r.t. other Java and C++/Qt based libraries have been presented at IPAC'19: https://ipac2019.vrws.de/papers/thprb028.pdf

N.B. this is my first post, thus no in-line images

like image 40
Angrond Avatar answered Oct 21 '22 02:10

Angrond