I'm writing a program in which on button click data in a pie chart rotates (slice on 10-12 o'clock moves to 12-2 etc). Code below (kinda) works, it rotates, but eats the temp slice and creates whole paragraph of errors. It is my first time trying JavaFX and I'm not really sure how to manage that.
private BorderPane layout;
private Scene scene;
ObservableList<PieChart.Data> pieChartData =
FXCollections.observableArrayList(
new PieChart.Data("Post-production age", 424236),
new PieChart.Data("Production age", 1030060),
new PieChart.Data("Production age2", 1030060),
new PieChart.Data("Production age3", 1030060),
new PieChart.Data("Pre-production age", 310319));
PieChart chart = new PieChart(pieChartData);
@Override public void start(Stage stage) {
layout = new BorderPane();
scene = new Scene(layout,720,480);
stage.setTitle("People");
stage.setWidth(500);
stage.setHeight(500);
Button button = new Button();
button.setText("rotate");
layout.setBottom(button);
layout.setCenter(chart);
button.setOnAction(e -> {
rotate();
});
chart.setStartAngle(90);
chart.setTitle("Economical age groups");
stage.setScene(scene);
stage.show();
}
public void rotate(){
ObservableList<PieChart.Data> pieChartDataTemp = pieChartData;
int sizeOne = pieChartDataTemp.size();
PieChart.Data tempData = pieChartDataTemp.get(sizeOne-1);
pieChartDataTemp.add(0,tempData);
if(pieChartDataTemp.size()>sizeOne) pieChartDataTemp.remove(pieChartDataTemp.size()-1 );
PieChart chartTemp = new PieChart(pieChartDataTemp);
layout.setCenter(chartTemp);
chartTemp.setStartAngle(90);
}
Here's the stack trace:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Children: duplicate children added: parent = Chart$1@5cfeee33[styleClass=chart-content]
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:558)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.add(VetoableListDecorator.java:206)
at javafx.controls/javafx.scene.chart.PieChart.dataItemAdded(PieChart.java:417)
at javafx.controls/javafx.scene.chart.PieChart.lambda$new$0(PieChart.java:168)
at javafx.base/com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at javafx.base/com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.base/javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.base/javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.base/javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.base/javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.base/javafx.collections.ModifiableObservableListBase.add(ModifiableObservableListBase.java:155)
at task.Main.rotate(Main.java:54)
at task.Main.lambda$start$0(Main.java:39)
(...and so on)
JavaFX charts support animation when data is added, removed, or updated. However, when animation is enabled, the nodes used to actually display the data aren't removed from the chart until the animation completes—which is not something publicly observable. This means you can't just remove a PieChart.Data
and then immediately re-add it, as you're currently doing. Attempting to do that results in trying to add a Node
to a Parent
when said Node
is still currently a child of said Parent
. As the IllegalArgumentException
you get says, duplicate children are not allowed.
You have this code (from #rotate()
):
ObservableList<PieChart.Data> pieChartDataTemp = pieChartData;
int sizeOne = pieChartDataTemp.size();
PieChart.Data tempData = pieChartDataTemp.get(sizeOne - 1);
pieChartDataTemp.add(0,tempData);
if (pieChartDataTemp.size() > sizeOne) {
pieChartDataTemp.remove(pieChartDataTemp.size() - 1);
}
PieChart chartTemp = new PieChart(pieChartDataTemp);
layout.setCenter(chartTemp);
chartTemp.setStartAngle(90);
Both pieChartDataTemp
and pieChartData
refer to the same ObservableList
. So when you add tempData
at the 0
index you're actually adding the element to the current PieChart
while it's still present at the size() - 1
index. I tried to fix this by swapping the add
and remove
calls but that didn't fix the problem—that's how I found out the animations were getting in the way.
You also create a new PieChart
and replace the old one. This is not necessary, assuming I properly understand what you're trying to do. It may also cause problems since you're using the same ObservableList
for each PieChart
.
There are at least two solutions.
One fix is to simply disable animations:
yourPieChart.setAnimated(false);
PieChart.Data
The other option is to create a new PieChart.Data
from the old one. Here's an example:
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class Main extends Application {
private ObservableList<PieChart.Data> createChartData() {
return FXCollections.observableArrayList(
new PieChart.Data("Post-production age", 424236),
new PieChart.Data("Production age", 1030060),
new PieChart.Data("Production age2", 1030060),
new PieChart.Data("Production age3", 1030060),
new PieChart.Data("Pre-production age", 310319)
);
}
@Override
public void start(Stage primaryStage) {
PieChart chart = new PieChart(createChartData());
chart.setStartAngle(90.0);
Button rotateBtn = new Button("Rotate");
rotateBtn.setOnAction(event -> {
event.consume();
PieChart.Data removed = chart.getData().remove(chart.getData().size() - 1);
chart.getData().add(0, new PieChart.Data(removed.getName(), removed.getPieValue()));
});
VBox root = new VBox(10, rotateBtn, new Separator(), chart);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
The rotate code is in the onAction
handler of the button:
rotateBtn.setOnAction(event -> {
event.consume();
PieChart.Data removed = chart.getData().remove(chart.getData().size() - 1);
chart.getData().add(0, new PieChart.Data(removed.getName(), removed.getPieValue()));
});
Note I don't create another PieChart
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With