我有一个有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);
}
}
任何帮助将被感激!
这里是一个策略。
ObservableList
对于数据SortedList
从基础列表中注册一个监听者 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();
}
}