Tryphon
Tryphon

Reputation: 161

Update JavaFX BarChart dynamically

As the title says. Basically I have an ArrayList where DiceRoll is a class that is used successfully in a TableView to update the values as they are changed.

public class DiceRoll {
private final SimpleIntegerProperty rollNumber = new SimpleIntegerProperty();
private final SimpleIntegerProperty cubeRoll = new SimpleIntegerProperty(0);
private final SimpleIntegerProperty icosahedron = new SimpleIntegerProperty(0);
private final SimpleIntegerProperty dodecahedron = new SimpleIntegerProperty(0);

public DiceRoll(int rollNumber) {
    this.rollNumber.set(rollNumber);
}

public void incrementRoll(DiceTypes type){
    switch(type){
        case CUBE:
            this.cubeRoll.set(this.cubeRoll.getValue() + 1);
            break;
        case DODECAHEDRON:
            this.dodecahedron.set(this.dodecahedron.getValue() + 1);
            break;
        case ICOSAHEDRON:
            this.icosahedron.set(this.icosahedron.getValue() + 1);
            break;
    }
}

public int getRollNumber() {
    return rollNumber.get();
}

public SimpleIntegerProperty rollNumberProperty() {
    return rollNumber;
}

public int getCubeRoll() {
    return cubeRoll.get();
}

public SimpleIntegerProperty cubeRollProperty() {
    return cubeRoll;
}

public int getIcosahedron() {
    return icosahedron.get();
}

public SimpleIntegerProperty icosahedronProperty() {
    return icosahedron;
}

public int getDodecahedron() {
    return dodecahedron.get();
}

public SimpleIntegerProperty dodecahedronProperty() {
    return dodecahedron;
}

public void reset(){
    cubeRoll.set(0);
    icosahedron.set(0);
    dodecahedron.set(0);
}

}

Like so:

rollNumberColumn.setCellValueFactory(new PropertyValueFactory<>("rollNumber"));

This all works, although I would prefer a non-factory approach. However I can't figure out how to do the same kind of linking with a BarChart.

My BarChart has a CategoryAxis as the X axis and a NumberAxis as the Y axis. I can successfully add data with XYGraph.Series and XYGraph.Data methods but these won't update automagically. How would I do this?

edit:

    TableColumn<DiceRoll, Number> rollNumberColumn = new TableColumn<>("Roll Number");        
    rollNumberColumn.setCellValueFactory(new PropertyValueFactory<>("rollNumber"));        
    tableView.setItems(FXCollections.observableArrayList(model.getDiceRolls())); //model.getDiceRolls() returns an ArrayList<DiceRoll>
    tableView.getColumns().setAll(rollNumberColumn, cubeRollNumberColumn, dodecahedronRollNumberColumn, icosahedronRollNumberColumn);

A DiceRoll is basically initialized as a number that you can roll. I've added this in my table x20 for all 20 possible rolls and just add to it if it gets rolled. In graph form this would mean 3 graphs with on the X axis the roll number and the Y axis the amount of times that it rolled.

The factory comment is kind of irrelevant to my question but I don't really like using the factory pattern which is used here.

Edit2:

ObservableList<DiceRoll> testing = FXCollections.observableArrayList(model.getDiceRolls());
    testing.addListener((ListChangeListener)c -> System.out.println("I'm working!"));

I tried doing this as suggested by Chris but the program doesn't print anything. It could be that observableArrayList() returns a new list instead of wrapping existing references but that would mean my table wouldn't work either. What could be wrong?

Edit3:

What was wrong is that observables "change" when the value of the reference changes. And the values of an observablelist are other references which in my case get added once and get never touched again. This means that it NEVER actually changes in the lifetime of the program after startup. I figured out that I can attach listeners to the IntegerProperties in DiceRoll to do whatever the hell I want when one of them changes which is awesome and gives me much needed control.

edit4

this.model.getDiceRolls().parallelStream().forEach(diceRoll -> {
        diceRoll.cubeRollProperty().addListener((observable, oldValue, newValue) ->
                cubeSeries.getData().get(diceRoll.getRollNumber() - 1).setYValue(newValue)
        );
        diceRoll.dodecahedronRollProperty().addListener((observable, oldValue, newValue) ->
                dodecahedronSeries.getData().get(diceRoll.getRollNumber() - 1).setYValue(newValue)
        );
        diceRoll.icosahedronRollProperty().addListener((observable, oldValue, newValue) ->
                icosahedronSeries.getData().get(diceRoll.getRollNumber() - 1).setYValue(newValue)
        );
    });

This is what I used at the end to monitor changes. All it requires is that your class uses a so called *Property variable which supports listeners out of the box. The moment it changes you can run a piece of code to update your chart for example.

Upvotes: 1

Views: 1588

Answers (1)

Chris
Chris

Reputation: 2465

If you are using an ObservableList you can add a ListChangeListener and call your chart update function when the list changes.

ObservableList<DiceRoll> myList = FxCollections.observableArrayList();
myList.addListener((ListChangeListener)(c -> { updateMyChart() }));

Then in updateMyChart() you would iterate the list:

private void updateMyChart() {

    /* Clear series */

    for(int i = 0; i < myList.size(); i++ {
        /* Create new Series */
    }

    /* Add Series */

}

And you would just simply add your DiceRoll objects to the list.

myList.add(new DiceRoll(1));

P.S - I left out the steps for creating / deleting a series as you mention you already can do that, if you need to see that too I can add it in.

Upvotes: 1

Related Questions