Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaFX Duplicate children PieChart

Tags:

java

javafx

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)
like image 951
mintas123 Avatar asked Jun 24 '19 20:06

mintas123


1 Answers

Some Background

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.


Issues in Your Code

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.


Solutions

There are at least two solutions.

Disable Animations

One fix is to simply disable animations:

yourPieChart.setAnimated(false);

Copy the 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.

like image 77
Slaw Avatar answered Nov 14 '22 13:11

Slaw