Graphique JavaFX Mise à l'échelle automatique Incorrecte avec des nombres faibles


J'utilise JavaFX pour construire un StackedBarChart. Le graphique changera que de nouvelles données. Je simule cela avec un bouton qui met à jour le graphique.

Cela fonctionne généralement bien, mais j'ai remarqué qu'avec des valeurs faibles (valeurs inférieures à ~100), les étiquettes de l'axe Y semblent un peu décalées lorsque je mets à jour le graphique:

Graphique JavaFX avec mise à l'échelle automatique correcte sur l'axe Y

Plus étrange encore, si je mets à jour le graphique une deuxième fois (ou troisième, quatrième...), la mise à l'échelle automatique de l'axe des Y est très éloignée:

JavaFX StackedBarChart avec une mauvaise mise à l'échelle automatique sur l'axe Y

Si j'utilise des valeurs plus grandes (valeurs > ~1000), puis la mise à l'échelle automatique fonctionne correctement. Si je désactive l'animation du graphique, la mise à l'échelle automatique fonctionne correctement. La mise à l'échelle automatique fonctionne bien la première fois que je mets à jour le graphique, mais pas après cela.

Voici le code que j'utilise, qui est à peu près identique au code de ce tutoriel JavaFX.

import java.util.Arrays;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!


        Button button = new Button("update");
        button.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                updateChart();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, button);

        Scene scene = new Scene(contentPane, 800, 600);

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


    private void updateChart(){

        int m = 1; //change this to 100 and autoscaling works!


        xAxis.setCategories(FXCollections.<String>observableArrayList());
        sbc.getData().clear();

        final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
        final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));


        xAxis.setCategories(FXCollections.<String>observableArrayList(Arrays.asList("one", "two", "three")));
        sbc.getData().addAll(series1, series2);
    }

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

Question bonus: l'animation, même lorsqu'elle fonctionne, affiche 2 sections supplémentaires de chaque barre empilée, qui disparaissent lorsque l'animation est terminée. Existe-t-il un moyen de se débarrasser de ces sections supplémentaires pendant l'animation?

Author: Kevin Workman, 2015-03-18

2 answers

Vous utilisez le graphique, l'animation et la modification de la liste observable d'une manière qui semble ne pas être faite. Eh bien, j'en faisais au départ. J'ai rencontré des problèmes comme celui-ci, parmi lesquels ArrayIndexOutOfBounds Exceptions. En bref: l'animation des graphiques JavaFX est très cassée, ou alors elle apparaît. Il semble être peu fiable et donc inutilisable lorsque vous modifiez directement la liste.

J'ai arrêté d'utiliser l'animation ou la modification de la liste et j'ai plutôt défini la liste sur chaque changement. Une de ces méthodes a résolu le problème.

Modification de l'exemple en utilisant sbc.setData () au lieu de sbc.getData().effacer() et sbc.getData().addAll (). Cela fonctionne, c'est-à-dire que l'animation est toujours activée:

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    Random rnd = new Random();

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!

        Button button = new Button("update");
        button.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                updateChart();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, button);

        Scene scene = new Scene(contentPane, 800, 600);

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


    private void updateChart(){

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
        final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        sbc.setData( FXCollections.observableArrayList(series1, series2));
    }

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

J'ai ajouté un caractère aléatoire pour votre m afin de voir les changements dans les barres.

, Mais le vrai problème est la façon dont vous utilisez le graphique. Vous ne devez pas modifier la liste, mais plutôt modifier les données des éléments de la liste. Ensuite, l'animation du graphique fonctionne comme attendu, les barres montent et descendent avec les données.

Exemple avec un bouton" créer "(crée une nouvelle liste de graphiques) et" modifier " (modifie les données de la liste, mais ne crée pas de nouvelle liste):

public class StackedBarChartSample extends Application {

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    Random rnd = new Random();

    final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
    final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

    @Override
    public void start(Stage stage) {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true); //change this to false and autoscaling works!

        Button createButton = new Button("Create");
        createButton.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                createChartData();
            }

        });

        Button modifyButton = new Button("Modify");
        modifyButton.setOnAction(new EventHandler<ActionEvent>(){
            @Override
            public void handle(ActionEvent arg0) {
                modifyChartData();
            }

        });

        VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, createButton, modifyButton);

        Scene scene = new Scene(contentPane, 800, 600);

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


    private void createChartData(){

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25*m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20*m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10*m));

        sbc.setData( FXCollections.observableArrayList(series1, series2));
    }

    private void modifyChartData() {

        int m = 1; //change this to 100 and autoscaling works!

        m = rnd.nextInt(100) + 1; // just some random value to see changes in the chart

        System.out.println( "m = " + m);

        for( XYChart.Data<String,Number> data: series1.getData()) {
            data.setYValue(rnd.nextInt(30) * m);
        }

        for( XYChart.Data<String,Number> data: series2.getData()) {
            data.setYValue(rnd.nextInt(30) * m);
        }


    }

    public static void main(String[] args) {
        launch(args);
    }
}
 5
Author: Roland, 2015-03-29 06:33:04

, Le problème vient de deux choses.

  1. Ajout et suppression de données en même temps (avant le début des animations)
  2. La mise à l'échelle automatique de l'axe Y pour s'adapter aux données animées

En supprimant les anciennes données sbc.getData().clear() et l'ajout de nouvelles données sbc.getData().addAll(series1, series2); dans le même temps vous entraîner deux séries d'animations pour démarrer en même temps.

J'ai modifié votre code (à la fin du post) en plongeant la fonction de mise à jour dans les fonctions clear et update et en ajoutant une variété de boutons pour eux.

  • Effacer
  • Mise à jour
  • Effacer et mettre à jour
  • Effacer et mettre à jour plus tard
  • Effacer ( pas anim) et mettre à jour

Exécutez le programme deux fois côte à côte, une avec de petites valeurs et une avec de grandes valeurs.

Si vous appuyez sur Mettre à jour, vous voyez les données tomber pour les deux. Si vous effacez ces données, vous voyez l'animation supprimer mais remarquez l'échelle de l'axe Y. C'est la même chose pour les deux ensembles de données. Il reste le même, même si vous utilisez les valeurs million.

Maintenant, si vous faites Clear & Update, le yAxis s'adaptera aux plus grandes valeurs animées. En utilisant de petites valeurs, les anciennes données dominent car elles grandissent et restent plus grandes que les nouvelles données pendant leur animation. Avec de grandes valeurs, les nouvelles données dominent car elles sont plus grandes que le point final final des anciennes données.

Ensuite, à la fin de l'animation de suppression, les anciennes données sont effacées mais l'axe Y est laissé seul et dans le cas de petites valeurs, il semble complètement FUBARd.

Personnellement, je pense que c'est un bug avec JavaFX. On a presque l'impression que les animations commencent et s'arrêtent avec y à 650. Je dis cela parce que lors de l'ajout de données avec de petites valeurs les barres semblent tomber en place, mais avec de grandes valeurs, ils semblent grandir en place. Je ne peux pas le dire avec certitude puisque je n'ai pas fait de plongée de code.

La première solution à laquelle je pense est de simplement dire à l'axe des ordonnées de redimensionner automatiquement une fois toute l'animation terminée. Cependant je ne pense pas qu'il y en ait actuellement un moyen d'être averti lorsque le graphique est fait l'animation. Cela peut être faux, mais je ne pense pas que cela compte beaucoup de toute façon. Les barres évolueraient à de très petites tailles, puis redeviendraient grandes. Le résultat final est bien mais une expérience utilisateur étrange et retardée.

Modifier: Comme Roland l'a commenté ci-dessous, la méthode du paragraphe suivant a une odeur de code. Je le laisse ici parce que cela fonctionne mais ne l'utilise pas. Soit utiliser ma dernière suggestion ou la façon dont Roland fournit dans son réponse.

Une solution consiste à ne pas démarrer les deux animations en même temps. La dernière animation en cours d'exécution doit être celle de l'ajout de données. Cela peut être vu dans le code du bouton Effacer et mettre à jour plus tard. Cependant, cela me semble toujours un peu bizarre.

La façon dont je préfère visuellement qui, je pense, résout également la question bonus; Ne pas animer la suppression de données. Si vous l'éteignez juste avant et le réactivez par la suite, vous obtenez de nouvelles données intéressantes qui s'animent et les anciennes données vont juste poof. Vu avec le bouton Clear ( no anim) & Update.

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class StackedBarChartSample extends Application
{

    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<String, Number>(xAxis, yAxis);

    @Override
    public void start(final Stage stage)
    {

        stage.setTitle("Bar Chart Sample");

        sbc.setAnimated(true);

        final Button updateButton = new Button("update");
        updateButton.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(final ActionEvent arg0)
            {
                updateChart();
            }

        });

        final Button clearButton = new Button("Clear");
        clearButton.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(final ActionEvent arg0)
            {
                clearChart(true);
            }

        });

        final Button clearAndUpdateButton = new Button("Clear & Update");
        clearAndUpdateButton.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(final ActionEvent evt)
            {
                clearChart(true);
                updateChart();
            }

        });

        final Button clearAndUpdateLaterButton = new Button("Clear & Update Later");
        clearAndUpdateLaterButton.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(final ActionEvent evt)
            {
                clearChart(true);
                new Timer(true).schedule(new TimerTask()
                {
                    @Override
                    public void run()
                    {
                        Platform.runLater(() -> updateChart());
                    }
                }, 1000);
            }

        });

        final Button clearNoAnimAndUpdateButton = new Button("Clear (no anim) & Update");
        clearNoAnimAndUpdateButton.setOnAction(new EventHandler<ActionEvent>()
        {
            @Override
            public void handle(final ActionEvent evt)
            {
                clearChart(false);
                updateChart();
            }

        });

        final VBox contentPane = new VBox();
        contentPane.getChildren().addAll(sbc, clearButton, updateButton, clearAndUpdateButton, clearAndUpdateLaterButton,
                clearNoAnimAndUpdateButton);

        final Scene scene = new Scene(contentPane, 800, 600);

        stage.setScene(scene);
        stage.show();

        xAxis.setCategories(FXCollections.<String> observableArrayList(Arrays.asList("one", "two", "three")));
    }

    private void clearChart(final boolean animate)
    {
        System.out.println("Clear");
        final boolean wasAnimated = sbc.getAnimated();
        if (!animate) {
            sbc.setAnimated(false);
        }

        sbc.getData().clear();

        if (!animate) {
            sbc.setAnimated(wasAnimated);
        }
    }

    private void updateChart()
    {
        System.out.println("Update");
        final int m = 1; // change this to 100 and autoscaling works!

        final XYChart.Series<String, Number> series1 = new XYChart.Series<String, Number>();
        final XYChart.Series<String, Number> series2 = new XYChart.Series<String, Number>();

        series1.setName("ABC");
        series1.getData().add(new XYChart.Data<String, Number>("one", 25 * m));
        series1.getData().add(new XYChart.Data<String, Number>("two", 20 * m));
        series1.getData().add(new XYChart.Data<String, Number>("three", 10 * m));

        series2.setName("XYZ");
        series2.getData().add(new XYChart.Data<String, Number>("one", 25 * m));
        series2.getData().add(new XYChart.Data<String, Number>("two", 20 * m));
        series2.getData().add(new XYChart.Data<String, Number>("three", 10 * m));

        sbc.getData().addAll(series1, series2);
    }

    public static void main(final String[] args)
    {
        launch(args);
    }
}
 2
Author: Appak, 2015-03-30 16:35:11