14

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.

Community
  • 1
  • 1
  • 1
    Since rendering is the time-consuming part, I think the suggestion you have seen is probably likely to work for your scenario. Perform the data reduction on some "lightweight" data representation (e.g. `double[]`) and then create the reduced number of the `XYChart.Data` objects. The reason rendering is slow is that each line segment in the line chart is a node in the scene graph; using it involves layout calculations and application of CSS. Processing the data is likely to be much faster. The other alternative (I have had to do this) is to implement your own chart (e.g. with a canvas). – James_D Jan 13 '16 at 17:55
  • Thank you, I will try that later. For now, I would like to know if someone knows how we can get the time it cost to render. – Hugo Fernando Maia Milan Jan 15 '16 at 20:07
  • Does anyone know of a performant line chart implementation? – Noah Ternullo Jan 31 '17 at 23:54
  • To use this `setCacheShape(true);` can help the JavaFX algorithm to improve the velocity. – Fabian Brandão Dec 27 '18 at 15:46

4 Answers4

15

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(...).

Elias Vasylenko
  • 1,524
  • 11
  • 21
  • Example code? This is answer is referenced as a "complete example," but is just discussion. An example of the pixel to point downsampling code would be helpful. – user1814946 Oct 02 '19 at 16:23
8

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

Angrond
  • 81
  • 2
  • 4
3

What is improving the performance of charts with many data points, is to remove the 'shape' of the data points.

Over css for example:

.chart-line-symbol {
    -fx-shape: "";
}

or to make sure it's not visible at all:

.chart-line-symbol {
   -fx-background-insets: 0,0;
   -fx-background-radius: 0px; 
   -fx-padding: 0px;
   -fx-shape: "";
   -fx-background-color: transparent;
}

Another approach is to remove some data points from your chart. So for example only use every 3. datapoint or calculate the average of multiple data points.

Marcel
  • 1,606
  • 16
  • 29
2

3 years later and is not a final solution as there are performance issues with big data but this help a lot (of course with this the chart will be simpler):

CSS (you can also play with other elements in the css to speed it up, a little)

.chart-vertical-grid-lines, .chart-horizontal-grid-lines {
    -fx-stroke: transparent;
}

Controller (this part is the one that consumes more time)

lineChart.setCreateSymbols(false); //this part is the one that consumes more time
lineChart.setAnimated(false);

Note: this issue is the same for any chart with lots of data.

Harry244
  • 126
  • 1
  • 5