Issue
I would like to add such kind of bar chart to my application using JavaFX:
Essentially: A (potentially large, i.e. up to 50 entries) table. For each row there are several columns with information. One piece of information are percentages about win/draw/loss ratio, i.e. say three numbers 10%, 50%, 40%. I would like to display these three percentages graphically as a vertical bar, with three different colors. So that a user can get a visual impression of each of these percentages.
I have not found a simple or straight-forward method of doing that with JavaFX. There seems at least no control for that right now. I also could not find a control from ControlsFX that seemd suitable. What I am curently doing is having the information itself, and three columns for the percentages like this:
Option Win Draw Loss
============================
option1 10% 50% 40%
option2 20% 70% 10%
option3 ...
But that's just not so nice. How can I achieve the above mentioned graphical kind of display?
(added an image for better understanding; it's from the lichess.org where they do exactly that in html)
Solution
This uses a combination of trashgod's and James_D's ideas:
a TableView with a custom cell factory and graphic,
The graphic could just be three appropriately-styled labels in a single-row grid pane with column constraints set.
Other than that, it is a standard table view implementation.
Numbers in my example don't always add up to 100% due to rounding, so you may wish to do something about that, if so, I leave that up to you.
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.text.NumberFormat;
import java.util.Arrays;
public class ChartTableApp extends Application {
private final ObservableList<Outcomes> outcomes = FXCollections.observableList(
Arrays.asList(
new Outcomes("Qxd5", 5722, 5722, 3646),
new Outcomes("Kf6", 2727, 2262, 1597),
new Outcomes("c6", 11, 1, 5),
new Outcomes("e6", 0, 1, 1)
)
);
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
stage.setScene(new Scene(createOutcomesTableView()));
stage.show();
}
private TableView<Outcomes> createOutcomesTableView() {
final TableView<Outcomes> outcomesTable = new TableView<>(outcomes);
TableColumn<Outcomes, String> moveCol = new TableColumn<>("Move");
moveCol.setCellValueFactory(o ->
new SimpleStringProperty(o.getValue().move())
);
TableColumn<Outcomes, Integer> totalCol = new TableColumn<>("Total");
totalCol.setCellValueFactory(o ->
new SimpleIntegerProperty(o.getValue().total()).asObject()
);
totalCol.setCellFactory(p ->
new IntegerCell()
);
totalCol.setStyle("-fx-alignment: BASELINE_RIGHT;");
TableColumn<Outcomes, Outcomes> outcomesCol = new TableColumn<>("Outcomes");
outcomesCol.setCellValueFactory(o ->
new SimpleObjectProperty<>(o.getValue())
);
outcomesCol.setCellFactory(p ->
new OutcomesCell()
);
//noinspection unchecked
outcomesTable.getColumns().addAll(
moveCol,
totalCol,
outcomesCol
);
outcomesTable.setPrefSize(450, 150);
return outcomesTable;
}
public record Outcomes(String move, int wins, int draws, int losses) {
public int total() { return wins + draws + losses; }
public double winPercent() { return percent(wins); }
public double drawPercent() { return percent(draws); }
public double lossPercent() { return percent(losses); }
private double percent(int value) { return value * 100.0 / total(); }
}
private static class OutcomesCell extends TableCell<Outcomes, Outcomes> {
OutcomesBar bar = new OutcomesBar();
@Override
protected void updateItem(Outcomes item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
bar.setOutcomes(item);
setGraphic(bar);
}
}
}
private static class OutcomesBar extends GridPane {
private final Label winsLabel = new Label();
private final Label drawsLabel = new Label();
private final Label lossesLabel = new Label();
private final ColumnConstraints winsColConstraints = new ColumnConstraints();
private final ColumnConstraints drawsColConstraints = new ColumnConstraints();
private final ColumnConstraints lossesColConstraints = new ColumnConstraints();
public OutcomesBar() {
winsLabel.setStyle("-fx-background-color : lightgray");
drawsLabel.setStyle("-fx-background-color : darkgray");
lossesLabel.setStyle("-fx-background-color : gray");
winsLabel.setAlignment(Pos.CENTER);
drawsLabel.setAlignment(Pos.CENTER);
lossesLabel.setAlignment(Pos.CENTER);
winsLabel.setMaxWidth(Double.MAX_VALUE);
drawsLabel.setMaxWidth(Double.MAX_VALUE);
lossesLabel.setMaxWidth(Double.MAX_VALUE);
addRow(0, winsLabel, drawsLabel, lossesLabel);
getColumnConstraints().addAll(
winsColConstraints,
drawsColConstraints,
lossesColConstraints
);
}
public void setOutcomes(Outcomes outcomes) {
winsLabel.setText((int) outcomes.winPercent() + "%");
drawsLabel.setText((int) outcomes.drawPercent() + "%");
lossesLabel.setText((int) outcomes.lossPercent() + "%");
winsColConstraints.setPercentWidth(outcomes.winPercent());
drawsColConstraints.setPercentWidth(outcomes.drawPercent());
lossesColConstraints.setPercentWidth(outcomes.lossPercent());
}
}
private static class IntegerCell extends TableCell<Outcomes, Integer> {
@Override
protected void updateItem(Integer item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
setText(
NumberFormat.getNumberInstance().format(
item
)
);
}
}
}
}
Answered By - jewelsea