在javafx中切换多个条位置

在javafx中切换多个条位置,javafx,bar-chart,transition,Javafx,Bar Chart,Transition,我有一个5条的barchat,现在我正试图根据高值(更像是来自Flow@的条形图比赛)从左到右移动每个条,虽然不一样,但想法是一样的 为了检查小数字和大数字,以及大数字和小数字,我使用了随机整数 例如,如果barE大于所有条,小于barA,那么它应该移到第2位并替换条B。我正在使用“If语句”来尝试实现这一点。问题是,只有一个转变正在发生,那就是第一个转变。当随机数每3秒改变一次时,不会发生正确的转换。有人知道我如何纠正这个问题吗 import java.util.Calendar; imp

我有一个5条的barchat,现在我正试图根据高值(更像是来自Flow@的条形图比赛)从左到右移动每个条,虽然不一样,但想法是一样的

为了检查小数字和大数字,以及大数字和小数字,我使用了随机整数

例如,如果barE大于所有条,小于barA,那么它应该移到第2位并替换条B。我正在使用“If语句”来尝试实现这一点。问题是,只有一个转变正在发生,那就是第一个转变。当随机数每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));


导入java.util.Calendar;
导入java.util.TimeZone;
导入java.util.concurrent.Executors;
导入java.util.concurrent.ScheduledExecutorService;
导入java.util.concurrent.TimeUnit;
导入javafx.application.application;
导入javafx.application.Platform;
导入javafx.beans.property.IntegerProperty;
导入javafx.beans.property.SimpleIntegerProperty;
导入javafx.beans.value.ChangeListener;
导入javafx.geometry.Bounds;
导入javafx.scene.Group;
导入javafx.scene.Node;
导入javafx.scene.scene;
导入javafx.scene.chart.BarChart;
导入javafx.scene.chart.CategoryAxis;
导入javafx.scene.chart.NumberAxis;
导入javafx.scene.chart.XYChart;
导入javafx.scene.chart.XYChart.Data;
导入javafx.scene.text.text;
导入javafx.stage.stage;
公共类应用程序扩展应用程序{
专用ScheduledExecutorService ScheduledExecutorService;
最后一个静态字符串奥地利=“奥地利”,巴西=“巴西”,法国=“法国”,英格兰=“英格兰”,比利时=“比利时”;
私有集成财产secondA、secondB、secondC、secondD、secondE;
私有文本第二文本、第二文本、第二文本、第二文本、第二文本、第二文本;
@凌驾
public void start(Stage primaryStage)引发异常{
setTitle(“实时条形图演示”);
//定义轴
最终CategoryAxis xAxis=新CategoryAxis();
最终数字axis yAxis=新数字axis();
xAxis.setAnimated(false);
yAxis.setAnimated(false);
//创建具有两个轴的条形图
最终条形图bc=新条形图(xAxis,yAxis);
公元前1世纪(假);
bc.setTitle(“国家概要”);
xAxis.setLabel(“国家”);
yAxis.setLabel(“值”);
//定义序列以显示数据
XYChart.Series系列=新的XYChart.Series();
Data dataA=新的XYChart.Data(奥地利,0);
seriesA.getData().add(dataA);
seriesA.setName(“Austra”);
secondA=新的SimpleIntegerProperty(0);
secondAText=新文本(“”);
secondA.addListener((ChangeListener)(可观察、旧值、新值)->{
dataA.setYValue(新值);
secondAText.setText(String.valueOf(newValue));
});
XYChart.Series serieb=新的XYChart.Series();
Data Data=新的XYChart.Data(巴西,0);
serieb.getData().add(dataB);
serieb.setName(“巴西”);
secondB=新的SimpleIntegerProperty(0);
secondB.bind(secondA.add(0));
secondBText=新文本(“”);
secondB.addListener((ChangeListener)(可观察、旧值、新值)->{
数据设置值(新值);
secondBText.setText(String.valueOf(newValue));
});
XYChart.seriesC=新的XYChart.Series();
Data dataC=新的XYChart.Data(法国,0);
seriesC.getData().add(dataC);
seriesC.setName(“法国”);
secondC=新的SimpleIntegerProperty(0);
secondC.bind(secondA.add(0));
secondCText=新文本(“”);
secondC.addListener((ChangeListener)(可观察、旧值、新值)->{
dataC.setYValue(新值);
secondCText.setText(String.valueOf(newValue));
});
XYChart.Series系列=新的XYChart.Series();
Data dataD=新的XYChart.Data(英格兰,0);
seriesD.getData().add(dataD);
seried.setName(“英格兰”);
secondD=新的SimpleIntegerProperty(0);
second.bind(secondA.add(0));
SecondText=新文本(“”);
secondD.addListener((ChangeListener)(可观察、旧值、新值)->{
dataD.setYValue(新值);
secondText.setText(String.valueOf(newValue));
});
XYChart.Series seriesE=新的XYChart.Series();
Data dataE=新的XYChart.Data(比利时,0);
seriesE.getData().add(dataE);
seriesE.setName(“比利时”);
secondE=新的SimpleIntegerProperty(0);
secondE.bind(secondA.add(0));
secondEText=新文本(“”);
secondE.addListener((ChangeListener)(可观察、旧值、新值)->{
dataE.setYValue(新值);
secondEText.setText(String.valueOf(newValue));
});
//将系列添加到图表中
bc.getData().add(seriesA);
bc.getData().add(serieb);
bc.getData().add(seriesC);
bc.getData().add(seriesD);
bc.getData().add(seriesE);
displayLabelForData(数据A,第二文本);
显示LabelForData(数据,第二个B文本);
displayLabelForData(dataC,secondCText);
displayLabelForData(数据数据,第二个数据文本);
显示LabelForData(数据E,第二个文本);
//设置场景
场景=新场景(bc,800600);
初级阶段。场景(场景);
//上台
primaryStage.show();
//设置计划执行器以定期将数据放入图表
scheduledExecutorService=Executors.newSingleThreadScheduledExecutor();
//每秒将数据输入图表scheduledExecutorService.scheduleAtFixedRate(()->{
Calendar cal=Calendar.getInstance(TimeZone.getTimeZone(“GMT”));
double posA=dataA.getNode().localToScene(dataA.getNode().getBoundsInLocal()).getMinX();
double posB=dataB.getNode().localToS

//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);

    }

}

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();
    });
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())
    );
}
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();
    }

}