1

I am developing a small medical reader using javaFX that render almost 24 linecharts using canvas.

JavaFX linechart component does not perform well so I switched to Canvas and performance was enhanced greatly. However, still can\t achieve required FPS. Here's the situation:

1 - 24 Canvas on the same screen (contained in a VBox)

2 - Each canvas render a linechart via gc.beginPath, lineTo...

Here's the code I am using /////////////

gc.beginPath();        
int dataLength = data.getData().length;                
int k = 1;

for(double f: data.getData()) {            
    gc.lineTo(getXPixel(k, dataLength), getYPixel(f, maxPt));
    k++;
}
gc.stroke();

///////////////// I am reading the data from arrays in memory using threads and updating the canvas using platform.runlater. A single linechart could contains 5000 points

I know the performance issue is coming from gc.lineTo and gc.stroke...because when I comment //gc.stroke() the reading is fast without rendering the canvas (stroking the lines).

Is there a way to enhance performance?

https://i.stack.imgur.com/kniRI.jpg

Here's a working code

import com.sun.javafx.perf.PerformanceTracker;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 *
 * @author abadran
 */
public class CanvasLineStress extends Application {

    Canvas[] charts = new Canvas[10];

    double CHART_YPADDING = 26;

    final static double CHART_WIDTH = 1000;

    final static double CHART_HEIGHT = 50;    

    final static int DATA_PTS = 5000;

    Label fpsLabel = new Label("Frame : ");

    private int currentFrame = 0;

    @Override
    public void start(Stage primaryStage) {

        AnimationTimer playCharts = new AnimationTimer() {
            @Override
            public void handle(long now) {
                renderCharts();
                fpsLabel.setText("Frame: " + currentFrame);
                currentFrame++;

            }
        };

        VBox chartContainer = new VBox(5);

        chartContainer.getChildren().add(fpsLabel);

        for(int i = 0; i < charts.length; i++) {
            charts[i] = new Canvas(CHART_WIDTH, CHART_HEIGHT);
            chartContainer.getChildren().add(charts[i]);
        }

        Button btn = new Button();
        btn.setText("Run Charter...");
        btn.setOnAction((ActionEvent event) -> {            
            playCharts.start();
        });

        StackPane root = new StackPane();
        root.getChildren().addAll(chartContainer, btn);

        Scene scene = new Scene(root, 1200, 900);

        primaryStage.setTitle("Canvas Stress Test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    private void renderCharts() {
        for (Canvas chart : charts) {
            GraphicsContext gc = chart.getGraphicsContext2D();
            gc.clearRect(0, 0, chart.getWidth(), chart.getHeight());
            gc.beginPath();        

            for(int k = 0; k < DATA_PTS; k++) {
                Random r = new Random(); 
                double f = -10.0 + r.nextDouble() * 40.0;
                gc.lineTo(getXPixel(k, DATA_PTS, CHART_WIDTH), getYPixel(f, 50, CHART_HEIGHT));
            }
            // comment the stroke action and frame rate will increase dramatically...
            gc.stroke();
        }
    }

    private double getXPixel(int x, int dataLength, double chartWidth) {
            return x * (chartWidth / dataLength);
    }

    private double getYPixel(double y, double maxPt, double chartHeight) {

        return chartHeight - (((chartHeight - CHART_YPADDING) / maxPt) * y) - CHART_YPADDING;
    }

}
egovconcepts
  • 69
  • 1
  • 8
  • 1
    Can you expand this a bit to a [mcve] – pvg Dec 25 '16 at 16:28
  • my code is scattered among couple classes (reading data, tasks..) in a big system...will try to develop a standalone version containing the canvas only. – egovconcepts Dec 25 '16 at 17:56
  • "Charts having more than a few thousand points are effectively unreadable, ", as suggested in the second bullet point seen [here](http://stackoverflow.com/a/40445144/230513). – trashgod Dec 26 '16 at 01:22
  • In medical devices it is normal to have 256 point per second and Doctors might need o see 50 seconds on screen thus 50 * 256 = 12800 points this is very normal in health context. – egovconcepts Dec 26 '16 at 06:43

2 Answers2

0

I've had a similar problem for some time. Something that made it work a bit better for me was not using paths at all but instead using strokeLine().

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
Samuel
  • 289
  • 3
  • 3
0

There is a lot you could do to improve your performance.

First you should pre-process your input data in order to adjust the data density to the pixel density of your output device. It just does not make much sense trying in a brute force mode to draw data at a resolution which is much higher than the resolution of your screen. (In your example it is a ratio of 5000/1000)

I assume that in reality your data does not change completely in every frame. Normally there is only little new data comming in during a frame and you have a lot of old data which is just shifted to one side. Why don't you do the same? Create a setup where you just draw the new data and just shift an image of the old data to one side. With a canvas you could easily do that with a snapshot for example but there are also other possiblities.

JavaFX does not have the fastest graphics but if you only use trivial brute force methods you will always reach the limits of any graphics at some point. So, just be creative and exploit the characteristics of your system :-)

mipa
  • 10,369
  • 2
  • 16
  • 35
  • Your first point makes sense to me..but need to find a way to reduce the data while maintaining the signal form (at the end a spike in the chart might be an interesting thing to the doctor) – egovconcepts May 18 '18 at 07:35