简体   繁体   中英

How do you create an animated LineChart in JavaFX?

I have created a LineChart in SceneBuilder and am trying to workout how to data to it, so that it will be drawn live. In my Controller class, I have:

    public class Controller {    
        ...    
        @FXML private LineChart<Number, Number> chart;
        @FXML private NumberAxis xAxis, yAxis;
        private XYChart.Series<Number, Number> series;
        private boolean run = true;
        ...

        public init() {
            xAxis.setAutoRanging(false);
            xAxis.setTickLabelsVisible(false);
            xAxis.setTickMarkVisible(false);
            xAxis.setMinorTickVisible(false);

            series = new XYChart.Series<Number, Number>();
            series.setName("Data plot"); 

            chart.setAnimated(false);
            chart.getData().addAll(series);           
        }  

        ...
        //When a button is clicked, a signal is sent to my server that starts a 
        //tracking application. Then it enters the while loop. This code
        //then makes requests to the server for data (getData()).
        //This is the data that I would like to display.
        @FXML
        private track() {
            connection.startTracking();

            Platform.runLater(new Runnable() {
                @Override
                public void run() {

                while(run) {
                    int[] data = connection.getData() //from somewhere else
                    dataManager.dataToSeries(data, series);

                    if(!series.getData.isEmpty()) {
                        chart.getData().retainAll();
                        chart.getData.addAll(series);
                    }
                }
            }
       }
  }    

And here is the dataToSeries() method from dataController

...
private void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
    int yValue = 0;
    int len = data.length;

    for (int i = 0; i < len; i++) {
        series.getData().add(new XYChart.Data<Number, Number>(data[i], yValue);  
        yValue++;
    }
 }

If I print out the series in Controller , I get the data that i expect, however it is in the form:

Series: [Data[11,1,null], Data[16,2,null], Data[21,3,null]

Can anyone explain why this data isn't being displayed on my LineChart ?

You're blocking the UI thread with the Runnable you post on the application thread using Platform.runLater .

Since the track() method is annotated with @FXML I suspect this is used as a EventHandler in the fxml file and therefore runs on the application thread anyways which removes the necessity of using Platform.runLater to run the code on the application thread.

To not block the UI thread the loop should run on a non-application thread and only the UI updates should be done on the application thread. Furthermore a break small break between updates may not be wrong. In this case a ScheduledExecutorService could provide suitable scheduling:

Example Application code:

private final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture future;

private Random random = new Random();

private int[] getData() {
    int[] result = new int[10];
    for (int i = 0; i < result.length; i++) {
        result[i] = random.nextInt(10);
    }
    return result;
}

private static void dataToSeries(int[] data, XYChart.Series<Number, Number> series) {
    int len = data.length;

    int size = series.getData().size();
    if (size > len) {
        series.getData().subList(len, series.getData().size()).clear();
    } else if (size < len) {
        for (; size < len; size++) {
            series.getData().add(new XYChart.Data<>(0, size));
        }
    }

    for (int i = 0; i < len; i++) {
        series.getData().get(i).setXValue(data[i]);
    }
}

@Override
public void start(Stage primaryStage) {
    ToggleButton btn = new ToggleButton("updating");
    btn.setSelected(false);

    XYChart.Series<Number, Number> series = new XYChart.Series<>();

    LineChart<Number, Number> chart = new LineChart<>(new NumberAxis(), new NumberAxis(), FXCollections.observableArrayList(series));
    chart.setAnimated(false);

    Runnable dataGetter = () -> {
        try {
            // simulate some delay caused by the io operation
            Thread.sleep(100);
        } catch (InterruptedException ex) {
        }
        int[] data = getData();
        Platform.runLater(() -> {
            // update ui
            dataToSeries(data, series);
        });
    };

    btn.selectedProperty().addListener((a, b, newValue) -> {
        if (newValue) {
            // update every second
            future = service.scheduleWithFixedDelay(dataGetter, 0, 1, TimeUnit.SECONDS);
        } else {
            // stop updates
            future.cancel(true);
            future = null;
        }
    });

    VBox root = new VBox(10, chart, btn);

    Scene scene = new Scene(root);

    primaryStage.setScene(scene);
    primaryStage.show();
}

@Override
public void stop() throws Exception {
    service.shutdownNow();
}

Based Java SE Development Demos and Samples Downloads in rootFoder -> jdk.xxx -> demo -> javafx_samples execute Ensemble8.jar.

Within the application go to the section charts and search for LineChart

You should see something like that Image from Ensembre Application

Then click in View Source

Here is an example of an implementation.

@FXML
private LineChart<Number, Number> lineChart;

@FXML
private NumberAxis xAxis;

@FXML
private NumberAxis yAxis;

@Override
public void initialize(URL location, ResourceBundle resources) {
    lineChart.setTitle("Stock Monitoring, 2010");
    ObservableList<XYChart.Series<Number,Number>> dataChart =
            FXCollections.observableArrayList(
                    new LineChart.Series("Serie 1",FXCollections.observableArrayList(
                            new XYChart.Data<>(1,2),
                            new XYChart.Data<>(5,3),
                            new XYChart.Data<>(9,3),
                            new XYChart.Data<>(2,7),
                            new XYChart.Data<>(6,9),
                            new XYChart.Data<>(1,2)
                    )),
                    new LineChart.Series<>("Series 2",FXCollections.observableArrayList(
                            new XYChart.Data<>(5,7),
                            new XYChart.Data<>(3,4),
                            new XYChart.Data<>(8,2),
                            new XYChart.Data<>(6,9),
                            new XYChart.Data<>(1,3),
                            new XYChart.Data<>(9,7)
                    )));

    lineChart.setData(dataChart);

For example to change the "y" axis of the second point of the first list Serie 1 whose current value is (x: 5, y: 3) would look something like this

dataChart.get(0).getData().get(1).setYValue(9);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM