Search code examples
javajavafxtreeviewtreetableview

Creating hierarchical data and adding it to a TreeTableView in JavaFX


I just got into programming and I am writing a program in java for counting votes using Single Transferable Vote. I have my input data in an Excel file looking like this:

this

where the name of the candidate is mapped to the number under it in a HashTable and every row below row 2 represents one voters preferences from left to right.

I want to add the names and votes in a TreeTableView looking something like this:

this.

Where each candidate is a root for their second preferences, each second preference is a root for the third preferences, each third preference is a root for the fourth preferences and so on.

I am using the apache.poi library to use the Excel files. I am looking for tips on how to represent the hierarchical data from the sheet and how to add it to the TreeTabeView. How could I do this?

Sorry if this is a noob question. I am not very familiar with how to use hierarchical data. Thanks in advance!


Solution

  • Use a item class with 2 properties

    • candidate
    • vote count

    would do the trick (=one property per column). E.g.

    public class VoteEntry {
    
        // candidate INDEX
        private final IntegerProperty candidate;
    
        private final IntegerProperty voteCount;
    
        public VoteEntry(int candidate, int count) {
            this.voteCount = new SimpleIntegerProperty(count);
            this.candidate = new SimpleIntegerProperty(candidate);
        }
    
        public final int getCandidate() {
            return this.candidate.get();
        }
    
        public final void setCandidate(int value) {
            this.candidate.set(value);
        }
    
        public final IntegerProperty candidateProperty() {
            return this.candidate;
        }
    
        public final int getVoteCount() {
            return this.voteCount.get();
        }
    
        public final void setVoteCount(int value) {
            this.voteCount.set(value);
        }
    
        public final IntegerProperty voteCountProperty() {
            return this.voteCount;
        }
    
    }
    

    You could group the votes to smaller and smaller subgroups to create the TreeItem hierarchy:

    private final List<int[]> votes = new ArrayList<>();
    
    private void addVote(int... preferences) {
        // convert to array of candidate indices sorted descendingly by preference
        int[] votes = new int[preferences.length];
        for (int i = 0; i < preferences.length; i++) {
            votes[preferences[i] - 1] = i;
        }
        this.votes.add(votes);
    }
    
    private static void createHierarchy(TreeItem<VoteEntry> parent, List<int[]> votes, int index) {
        int max = votes.stream().mapToInt(a -> a.length).max().getAsInt();
        if (max > index) {
            // group by candidate
            Map<Integer, List<int[]>> groups = votes.stream().collect(Collectors.groupingBy(a -> a.length > index ? a[index] : -1));
            groups.forEach((candidate, vts) -> {
                if (candidate != -1) {
                    VoteEntry entry = new VoteEntry(candidate, vts.size());
                    TreeItem<VoteEntry> item = new TreeItem<>(entry);
                    parent.getChildren().add(item);
                    createHierarchy(item, vts, index + 1);
                }
            });
    
            // sort by candidate
            parent.getChildren().sort(Comparator.comparingInt(ti -> ti.getValue().getCandidate()));
        }
    }
    
    @Override
    public void start(Stage primaryStage) {
        addVote(1, 2, 3, 4);
        addVote(4, 3, 2, 1);
        addVote(1, 3, 2, 4);
        addVote(2, 1, 4, 3);
        addVote(2, 4, 3, 1);
        addVote(2, 1, 3, 4);
        // ...
    
        ObservableList<String> candidateNames = FXCollections.observableArrayList(
                "Candidate 1",
                "Candidate 2",
                "Candidate 3",
                "Candidate 4"
                );
    
        TreeItem<VoteEntry> root = new TreeItem<>();
        createHierarchy(root, votes, 0);
    
        TreeTableView<VoteEntry> view = new TreeTableView<>(root);
        view.setShowRoot(false);
    
        TreeTableColumn<VoteEntry, String> candidateColumn = new TreeTableColumn<>("candidate");
        candidateColumn.setCellValueFactory(data -> Bindings.valueAt(candidateNames, data.getValue().getValue().candidateProperty()));
    
        TreeTableColumn<VoteEntry, Integer> votesColumn = new TreeTableColumn<>("votes");
        votesColumn.setCellValueFactory(new TreeItemPropertyValueFactory<>("voteCount"));
    
        view.getColumns().addAll(candidateColumn, votesColumn);
    
        Scene scene = new Scene(view);
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    

    Note that the recursive approach only works for a limited amount of candidates (stackoverflows). But it should work for reasonable candidate numbers (a user wouldn't want to extend hundreds of items...).