JavaFX : Data binding et graphique (Line Chart)

Ă€ la fin de cet article, vous devriez avoir le rendu suivant.

Fichier FXML

Vous pouvez nommer le fichier chart-sin.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.layout.VBox?>

<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fr.ronanlefichant.databinding.ChartSinController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
   <children>
      <LineChart fx:id="lineChart" animated="false" createSymbols="false">
        <xAxis>
          <NumberAxis side="BOTTOM" />
        </xAxis>
        <yAxis>
          <NumberAxis autoRanging="false" lowerBound="-3.0" side="LEFT" upperBound="3.0" />
        </yAxis>
      </LineChart>
      <Slider fx:id="slider" blockIncrement="0.25" majorTickUnit="0.25" max="2.5" min="-2.5" minorTickCount="0" snapToTicks="true" />
   </children>
</VBox>

Vous pouvez enlever l'animation avec la propriété animated="false". Je trouvais l'animation dérangeante dans cet exemple. Elle ajoute un délai et donc le graphique ne se rafraîchissait pas instantanément.

createSymbols="false" supprime l'affichage de tous les points et affiche seulement une ligne.

autoRanging="false" permet de figer l'axe des ordonnées à -3.0 et 3.0.

blockIncrement="0.25" et snapToTicks="true" permettent d'incrémenter ou de décrémenter le curseur de 0.25.

Ce fichier FXML devrait produire une vue comme sur l'image suivante :

Le contrĂ´leur

La vue devrait injecter dans le contrĂ´leur lineChart et slider

@FXML
private LineChart<Double, Double> lineChart;

@FXML
private Slider slider;

Notre contrôleur va implémenter Initializable. Il faut donc créer la fonction initialize

@Override
public void initialize(URL location, ResourceBundle resources) {
    /* Ă©coute les changements de valeur du slider et appelle la fonction onSliderValueChanged */
    slider.valueProperty().addListener(this::onSliderValueChanged);

    /* change la valeur du slider */
    slider.setValue(0.25);
}

On ajoute un listener sur la valeur du curseur. Le listener appellera la fonction onSliderValueChanged Ă  chaque changement de valeur.

Vous pouvez changer la valeur du curseur maintenant que le listener est installé. On place la valeur à 0.25 par défaut.

Il faut maintenant créer la fonction onSliderValueChanged. Cette fonction va supprimer puis dessiner le graphique à chaque fois que la valeur du curseur change.

private void onSliderValueChanged(ObservableValue<? extends Number> valueObserver, Number oldValue, Number newValue) {
     /* suppression des données du graphique */
     lineChart.getData().clear();
		
     /* dessine la fonction avec la valeur du slider */
     drawFunction(newValue.doubleValue());
}

Il ne reste plus qu'à créer la fonction qui va dessiner le graphique. Dans cet exemple, j'utilise une fonction sinusoïdale. Le curseur va changer l'amplitude de la fonction.

private void drawFunction(double amplitude) {
    /* création d'une série */
    final XYChart.Series < Double, Double > series = new XYChart.Series < Double, Double > ();

    /* création de la légende */
    String legend = String.format("y = %.2f . sin(1 . (x - 0)) + 0", amplitude);
    series.setName(legend);

    /* pour chaque point tous les 0.01 jusqu'Ă  15 */
    for (double x = 0; x <= 15; x += 0.01) {
        /* ajoute le point (x,y) à la série */
        series.getData().add(new XYChart.Data < Double, Double > (x, amplitude * Math.sin(1 * (x - 0)) + 0));
    }
    /* ajoute la série de point et dessine la fonction */
    lineChart.getData().add(series);

}

Tracer tous les 0.01 sur l'axe des abscisses permet d'avoir une ligne bien arrondie.

Le code complet

J'utilise maven avec l'archetype javafx-archetype-fxml de org.openjfx

App.java

package fr.ronanlefichant.databinding;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

/**
 * JavaFX App
 */
public class App extends Application {

    private static Scene scene;

    @Override
    public void start(Stage stage) throws IOException {
        scene = new Scene(loadFXML("chart-sin"), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    static void setRoot(String fxml) throws IOException {
        scene.setRoot(loadFXML(fxml));
    }

    private static Parent loadFXML(String fxml) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource(fxml + ".fxml"));
        return fxmlLoader.load();
    }

    public static void main(String[] args) {
        launch();
    }

}

ChartSinController.java

package fr.ronanlefichant.databinding;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Slider;

public class ChartSinController implements Initializable {

    @FXML
    private LineChart < Double, Double > lineChart;

    @FXML
    private Slider slider;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        /* Ă©coute les changements de valeur du slider et appel la fonction onSliderValueChanged */
        slider.valueProperty().addListener(this::onSliderValueChanged);
        /* change la valeur du slider */
        slider.setValue(0.25);
    }

    private void onSliderValueChanged(ObservableValue << ? extends Number > valueObserver, Number oldValue, Number newValue) {
        /* suppression des données du graphique */
        lineChart.getData().clear();

        /* dessine la fonction avec la valeur du slider */
        drawFunction(newValue.doubleValue());
    }

    private void drawFunction(double amplitude) {
        /* création d'une série */
        final XYChart.Series < Double, Double > series = new XYChart.Series < Double, Double > ();

        /* création de la légende */
        String legend = String.format("y = %.2f . sin(1 . (x - 0)) + 0", amplitude);
        series.setName(legend);

        /* pour chaque point tous les 0.01 jusqu'Ă  15 */
        for (double x = 0; x <= 15; x += 0.01) {
            /* ajoute le point (x,y) à la série */
            series.getData().add(new XYChart.Data < Double, Double > (x, amplitude * Math.sin(1 * (x - 0)) + 0));
        }
        /* ajoute la série de points et dessine la fonction */
        lineChart.getData().add(series);

    }

}

module-info.java

module fr.ronanlefichant.databinding {
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.base;
    requires transitive javafx.graphics;

    opens fr.ronanlefichant.databinding to javafx.fxml;
    exports fr.ronanlefichant.databinding;
}

Commentaires