切换多个栏位 javafx

问题描述 投票:0回答:1

我有一个有5个条形图的barchat,现在我试图根据高值将每个条形图从左到右移动(更像是来自fourish的条形图比赛@)。https:/app.fourish.studio。)不一样,但思路是一样的。

为了检查小数对大数,大数对小数,我使用随机整数。

例如,如果barE大于所有的条形图,小于barA,它应该移动到2号,并取代barB。问题是,只有一个转换正在发生,也就是第一个。当随机数字每3秒改变一次时,正确的转换就不会发生。有人知道如何纠正这个问题吗?


import java.util.Calendar;

import java.util.TimeZone;

import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

import javafx.application.Application;

import javafx.application.Platform;

import javafx.beans.property.IntegerProperty;

import javafx.beans.property.SimpleIntegerProperty;

import javafx.beans.value.ChangeListener;

import javafx.geometry.Bounds;

import javafx.scene.Group;

import javafx.scene.Node;

import javafx.scene.Scene;

import javafx.scene.chart.BarChart;

import javafx.scene.chart.CategoryAxis;

import javafx.scene.chart.NumberAxis;

import javafx.scene.chart.XYChart;

import javafx.scene.chart.XYChart.Data;

import javafx.scene.text.Text;

import javafx.stage.Stage;



public class App extends Application {

    private ScheduledExecutorService scheduledExecutorService;

    final static String austria = "Austria",  brazil = "Brazil",  france = "France", england = "England", belgium = "Belgium";

    private IntegerProperty secondA,  secondB , secondC, secondD, secondE;

    private Text secondAText, secondBText , secondCText, secondDText, secondEText;



    @Override

    public void start(Stage primaryStage) throws Exception {

        primaryStage.setTitle("Realtime Bar Chart Demo");



        //defining the axes

        final CategoryAxis xAxis = new CategoryAxis();

        final NumberAxis yAxis = new NumberAxis();

        xAxis.setAnimated(false);

        yAxis.setAnimated(false);



        //creating the bar chart with two axis

        final BarChart<String,Number> bc =  new BarChart<>(xAxis,yAxis);

        bc.setAnimated(false);

        bc.setTitle("Country Summary");

        xAxis.setLabel("Country");

        yAxis.setLabel("Value");



        //defining a series to display data

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

        Data<String, Number> dataA = new XYChart.Data<>(austria,0);

        seriesA.getData().add(dataA);

        seriesA.setName("Austra");

        secondA = new SimpleIntegerProperty(0);

        secondAText = new Text("");

        secondA.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {

            dataA.setYValue(newValue);

            secondAText.setText(String.valueOf(newValue));

        });

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

        Data<String, Number> dataB = new XYChart.Data<>(brazil,0);

        seriesB.getData().add(dataB);

        seriesB.setName("Brazil");

        secondB =  new SimpleIntegerProperty(0);

        secondB.bind(secondA.add(0));

        secondBText = new Text("");

        secondB.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {

            dataB.setYValue(newValue);

            secondBText.setText(String.valueOf(newValue));

        });

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

        Data<String, Number> dataC = new XYChart.Data<>(france,0);

        seriesC.getData().add(dataC);

        seriesC.setName("France");

        secondC =  new SimpleIntegerProperty(0);

        secondC.bind(secondA.add(0));

        secondCText = new Text("");

        secondC.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {

            dataC.setYValue(newValue);

            secondCText.setText(String.valueOf(newValue));

        });



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

        Data<String, Number> dataD = new XYChart.Data<>(england,0);

        seriesD.getData().add(dataD);

        seriesD.setName("England");

        secondD =  new SimpleIntegerProperty(0);

        secondD.bind(secondA.add(0));

        secondDText = new Text("");

        secondD.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {

            dataD.setYValue(newValue);

            secondDText.setText(String.valueOf(newValue));

        });



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

        Data<String, Number> dataE = new XYChart.Data<>(belgium,0);

        seriesE.getData().add(dataE);

        seriesE.setName("Belgium");

        secondE =  new SimpleIntegerProperty(0);

        secondE.bind(secondA.add(0));

        secondEText = new Text("");

        secondE.addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {

            dataE.setYValue(newValue);

            secondEText.setText(String.valueOf(newValue));

        });



        // add series to chart

        bc.getData().add(seriesA);

        bc.getData().add(seriesB);

        bc.getData().add(seriesC);

        bc.getData().add(seriesD);

        bc.getData().add(seriesE);



        displayLabelForData(dataA, secondAText);

        displayLabelForData(dataB, secondBText);

        displayLabelForData(dataC, secondCText);

        displayLabelForData(dataD, secondDText);

        displayLabelForData(dataE, secondEText);

        // setup scene

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

        primaryStage.setScene(scene);

        // show the stage

        primaryStage.show();



        // setup a scheduled executor to periodically put data into the chart

        scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();



        // input data onto graph per second        scheduledExecutorService.scheduleAtFixedRate(() -> {

            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));

double posA = dataA.getNode().localToScene(dataA.getNode().getBoundsInLocal()).getMinX();

double posB = dataB.getNode().localToScene(dataB.getNode().getBoundsInLocal()).getMinX();

double posC = dataC.getNode().localToScene(dataC.getNode().getBoundsInLocal()).getMinX();

double posD = dataD.getNode().localToScene(dataD.getNode().getBoundsInLocal()).getMinX();

double posE = dataE.getNode().localToScene(dataE.getNode().getBoundsInLocal()).getMinX();

TranslateTransition ttA = new TranslateTransition(Duration.millis(2000), dataA.getNode());

TranslateTransition ttB = new TranslateTransition(Duration.millis(2000), dataB.getNode());

TranslateTransition ttC = new TranslateTransition(Duration.millis(2000), dataC.getNode());

TranslateTransition ttD = new TranslateTransition(Duration.millis(2000), dataD.getNode());

TranslateTransition ttE = new TranslateTransition(Duration.millis(2000), dataE.getNode());

//Genarate random numbers

Integer randomB = ThreadLocalRandom.current().nextInt(60);

Integer randomC = ThreadLocalRandom.current().nextInt(60);

Integer randomD = ThreadLocalRandom.current().nextInt(60);

Integer randomE = ThreadLocalRandom.current().nextInt(60);

int intSecondB = secondB.bind(secondA.add(randomB));

int intSecondC = secondC.bind(secondA.add(randomC));

int intSecondD = secondD.bind(secondA.add(randomD));

int intSecondE = secondE.bind(secondA.add(randomE));

我不知道问题是出在下面的if语句还是代码本身。为了避免代码变长,我只包含了if语句,只比较seriesB(barB)和其他条形图。


//using if statement to swich each bar based on value

if (intSecondB >= intSecondA && intSecondB >= intSecondB && intSecondB >= intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {

double diffBA = posB - posA;

ttA.setByX(diffBA);

ttB.setByX(-diffBA);



ttA.setCycleCount(1);

    ttA.setAutoReverse(true);

    ttA.play();

ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

}

if (intSecondB < intSecondA && intSecondB >= intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {

System.out.println("keep seriesB(barB) at its position");

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB >= intSecondD && intSecondB >= intSecondE) {

double diffCB = posC - posB;

ttB.setByX(diffCB);

ttC.setByX(-diffCB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttC.setCycleCount(1);

    ttC.setAutoReverse(true);

    ttC.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB >= intSecondE) {

double diffDB = posD - posB;

ttB.setByX(diffDB);

ttD.setByX(-diffDB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttD.setCycleCount(1);

    ttD.setAutoReverse(true);

    ttD.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffEB = posE - posB;

ttB.setByX(diffEB);

ttE.setByX(-diffEB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttE.setCycleCount(1);

    ttE.setAutoReverse(true);

    ttE.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffFB = posF - posB;

ttB.setByX(diffFB);

ttF.setByX(-diffFB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttF.setCycleCount(1);

    ttF.setAutoReverse(true);

    ttF.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffGB = posG - posB;

ttB.setByX(diffGB);

ttG.setByX(-diffGB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttG.setCycleCount(1);

    ttG.setAutoReverse(true);

    ttG.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffHB = posH - posB;

ttB.setByX(diffHB);

ttH.setByX(-diffHB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttH.setCycleCount(1);

    ttH.setAutoReverse(true);

    ttH.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffIB = posI - posB;

ttB.setByX(diffIB);

ttI.setByX(-diffIB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttI.setCycleCount(1);

    ttI.setAutoReverse(true);

    ttI.play();

}

if (intSecondB < intSecondA && intSecondB < intSecondC && intSecondB < intSecondD && intSecondB < intSecondE) {

double diffJB = posJ - posB;

ttB.setByX(diffJB);

ttJ.setByX(-diffJB);



ttB.setCycleCount(1);

    ttB.setAutoReverse(true);

    ttB.play();

ttJ.setCycleCount(1);

    ttJ.setAutoReverse(true);

    ttJ.play();

}

            // Update the chart

            Platform.runLater(() -> {

                secondA.set( cal.get(Calendar.SECOND));

            });

        }, 0, 3, TimeUnit.SECONDS);

    }

    @Override

    public void stop() throws Exception {

        super.stop();

        scheduledExecutorService.shutdownNow();

    }



    private void displayLabelForData(XYChart.Data<String, Number> data, Text text) {

        final Node node = data.getNode();

        ((Group) node.getParent()).getChildren().add(text);

      node.boundsInParentProperty().addListener((ChangeListener<Bounds>) (ov, oldBounds, bounds) -> {

            text.setLayoutX(

                    Math.round( bounds.getMinX() + bounds.getWidth() / 2 - text.prefWidth(-1) / 2));

            text.setLayoutY(Math.round( bounds.getMinY() - text.prefHeight(-1) * 0.5));

        });

    }

public static void main(String[] args) {

        launch(args);

    }

}

任何帮助将被感激!

javafx bar-chart transition
1个回答
2
投票

这里是一个策略。

  1. 创建一个 ObservableList 对于数据
  2. 创建一个 SortedList 从基础列表中
  3. 注册一个监听者 SortedList当数据发生变化时,创建一个动画。

    a. 对于每根柱子,找到它的当前位置和柱子在索引中对应其新顺序的位置; b.

    b. 使用这些位置来制作动画 translateX 棒子的财产

    c. 动画 yValue 的财产 XYChart.Data 一气呵成

    d. 在动画结束时,将图表数据重置为新的排序数据。

这里有几个小 "小毛病":你需要关闭 autoRanging 在...上 CategoryAxis (否则会忽略条形图顺序的变化),并在更新数据时使用新的顺序重置类别。

下面是一个例子。我创建了一个类,只是为了保存数据,没有任何图表API。

public static class CountryValue {
    private final String country ;
    private final double value ;
    public CountryValue(String country, double value) {
        super();
        this.country = country;
        this.value = value;
    }
    public String getCountry() {
        return country;
    }
    public double getValue() {
        return value;
    }

}

和一个简单的数据模型来保存这些数据的列表。

public static class Model {
    private final ObservableList<CountryValue> values ;

    public Model(CountryValue... countryValues) {
        values = FXCollections.observableArrayList(countryValues) ;
    }

    public ObservableList<CountryValue> getValues() {
        return values ;
    }

}

然后关键部分是这样的:

    Model model = new Model() ;
    SortedList<CountryValue> sortedData = new SortedList<>(
            model.getValues(), 
            Comparator.comparingDouble(CountryValue::getValue).reversed());

    ObservableList<XYChart.Data<String, Number>> chartData = FXCollections.observableArrayList();

    CategoryAxis countryAxis = new CategoryAxis();
    countryAxis.setAutoRanging(false);
    populateChartData(sortedData, chartData, countryAxis);

    sortedData.addListener((Change<? extends CountryValue> c) -> {

        Timeline timeline = new Timeline() ;

        for (int newIndex = 0 ; newIndex < sortedData.size() ; newIndex++) {

            CountryValue cv = sortedData.get(newIndex);
            int currentIndex = indexByCountry(cv.getCountry(), chartData);
            Data<String, Number> data = chartData.get(currentIndex);
            double currentX = data.getNode().getBoundsInParent().getCenterX();
            double targetX = chartData.get(newIndex).getNode().getBoundsInParent().getCenterX();
            DoubleProperty translateXProperty = data.getNode().translateXProperty();
            KeyValue kvx1 = new KeyValue(translateXProperty, 0);
            KeyValue kvx2 = new KeyValue(translateXProperty, targetX - currentX);
            ObjectProperty<Number> yValueProperty = data.YValueProperty();
            KeyValue kvy1 = new KeyValue(yValueProperty, data.getYValue());
            KeyValue kvy2 = new KeyValue(yValueProperty, cv.getValue());
            timeline.getKeyFrames().addAll(
                    new KeyFrame(Duration.ZERO, kvx1),
                    new KeyFrame(Duration.ZERO, kvy1),
                    new KeyFrame(animationDuration, kvx2),
                    new KeyFrame(animationDuration, kvy2)
            );
        }

        timeline.setOnFinished(e -> populateChartData(sortedData, chartData, countryAxis));

        timeline.play();
    });

这个实用程序 populateChartData() 方法同时更新类别轴和数据。

private void populateChartData(ObservableList<CountryValue> source, 
        ObservableList<XYChart.Data<String, Number>> chartData,
        CategoryAxis countryAxis) {

    countryAxis.getCategories().setAll(
        source.stream()
            .map(CountryValue::getCountry)
            .collect(Collectors.toList())
    );

    chartData.setAll(
        source.stream()
            .map(cv -> new XYChart.Data<String, Number>(cv.getCountry(), cv.getValue()))
            .collect(Collectors.toList())
    );
}

这是一个完整的例子。这个动画有点 "抖动";我想是因为y轴的比例变化是不可预知的,你可以通过关闭y轴的自动调整,从新数据中计算出最大的y值,然后对y轴进行动画处理。你可以自己管理这个问题,关闭y轴上的autoranging,从新的数据中计算最大y值,并对y轴范围以及条形图进行动画。另外,请注意,在动画运行时,不要对数据进行更新,这一点很重要(否则,你最终会有多个动画同时运行)。在这里,这只是简单地通过定时来管理,但一个更强大的解决方案将检查这一点,并在开始一个新的动画之前节制更新或直接结束当前的动画。

import java.util.Comparator;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Duration;

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

    private final Duration animationDuration = Duration.millis(250);

    @Override
    public void start(Stage stage) {

        Model model = new Model() ;
        Simulator simulator = new Simulator(model);

        SortedList<CountryValue> sortedData = new SortedList<>(
                model.getValues(), 
                Comparator.comparingDouble(CountryValue::getValue).reversed());

        ObservableList<XYChart.Data<String, Number>> chartData = FXCollections.observableArrayList();

        CategoryAxis countryAxis = new CategoryAxis();
        countryAxis.setAutoRanging(false);
        populateChartData(sortedData, chartData, countryAxis);


        BarChart<String, Number> chart = new BarChart<>(countryAxis, new NumberAxis());
        // turn off default animation:
        chart.setAnimated(false);
        Series<String, Number> series = new Series<>(chartData);
        chart.getData().add(series);

        // when sorted data change, animate bar chart nodes
        // at end of animation, update chart data with new data
        sortedData.addListener((Change<? extends CountryValue> c) -> {

            Timeline timeline = new Timeline() ;

            for (int newIndex = 0 ; newIndex < sortedData.size() ; newIndex++) {

                CountryValue cv = sortedData.get(newIndex);
                int currentIndex = indexByCountry(cv.getCountry(), chartData);
                Data<String, Number> data = chartData.get(currentIndex);
                double currentX = data.getNode().getBoundsInParent().getCenterX();
                double targetX = chartData.get(newIndex).getNode().getBoundsInParent().getCenterX();
                DoubleProperty translateXProperty = data.getNode().translateXProperty();
                KeyValue kvx1 = new KeyValue(translateXProperty, 0);
                KeyValue kvx2 = new KeyValue(translateXProperty, targetX - currentX);
                ObjectProperty<Number> yValueProperty = data.YValueProperty();
                KeyValue kvy1 = new KeyValue(yValueProperty, data.getYValue());
                KeyValue kvy2 = new KeyValue(yValueProperty, cv.getValue());
                timeline.getKeyFrames().addAll(
                        new KeyFrame(Duration.ZERO, kvx1),
                        new KeyFrame(Duration.ZERO, kvy1),
                        new KeyFrame(animationDuration, kvx2),
                        new KeyFrame(animationDuration, kvy2)
                );
            }

            timeline.setOnFinished(e -> populateChartData(sortedData, chartData, countryAxis));

            timeline.play();
        });


        BorderPane root = new BorderPane(chart);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

        new Thread(simulator).start();
    }



    private int indexByCountry(String country, ObservableList<Data<String, Number>> chartData) {
        for (int index = 0 ; index < chartData.size(); index++) {
            if (chartData.get(index).getXValue().equals(country))
                return index ;
        }
        return -1 ;
    }

    private void populateChartData(ObservableList<CountryValue> source, 
            ObservableList<XYChart.Data<String, Number>> chartData,
            CategoryAxis countryAxis) {

        countryAxis.getCategories().setAll(
            source.stream()
                .map(CountryValue::getCountry)
                .collect(Collectors.toList())
        );

        chartData.setAll(
            source.stream()
                .map(cv -> new XYChart.Data<String, Number>(cv.getCountry(), cv.getValue()))
                .collect(Collectors.toList())
        );
    }

    public static class Model {
        private final ObservableList<CountryValue> values ;

        public Model(CountryValue... countryValues) {
            values = FXCollections.observableArrayList(countryValues) ;
        }

        public ObservableList<CountryValue> getValues() {
            return values ;
        }

    }


    // replace with record when they are standard in Java:
    public static class CountryValue {
        private final String country ;
        private final double value ;
        public CountryValue(String country, double value) {
            super();
            this.country = country;
            this.value = value;
        }
        public String getCountry() {
            return country;
        }
        public double getValue() {
            return value;
        }

    }

    // Not really relevant to problem; just simulates changing data in model
    public class Simulator implements Runnable {

        private final Model model ;
        private final Random rng = new Random();

        public Simulator(Model model) {
            this.model = model ;
            createData();
        }

        @Override
        public void run() {
            for (int i = 0 ; i < 10 ; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                Platform.runLater(this::createData);
            }
        }

        private void createData() {
            model.getValues().setAll(
                Stream.of("Austria", "Brazil", "France", "England", "Belgium")
                    .map(country -> new CountryValue(country, 50 * rng.nextDouble() + 50))
                    .collect(Collectors.toList())
            );
        }
    }

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

}
© www.soinside.com 2019 - 2024. All rights reserved.