条形图作为 JavaFX 中的 TableRow

Barchart as TableRow in JavaFX

我想使用 JavaFX 将这种条形图添加到我的应用程序中:

本质上:A(可能很大,即最多 50 个条目)table。对于每一行,都有几列包含信息。一条信息是关于 win/draw/loss 比率的百分比,即三个数字 10%、50%、40%。我想用三种不同的颜色以图形方式将这三个百分比显示为垂直条。以便用户可以直观地了解这些百分比。

我还没有找到使用 JavaFX 进行此操作的简单或直接的方法。至少现在似乎无法控制。我也无法从 ControlsFX 中找到似乎 suitable 的控件。我目前正在做的是获取信息本身,以及三列百分比,如下所示:

Option    Win   Draw    Loss
============================
option1   10%   50%     40%
option2   20%   70%     10%
option3   ...

但这不是很好。如何实现上述图形显示?

(添加了一张图片以便更好地理解;它来自 lichess.org 他们在 html 中正是这样做的)

这结合了 trashgod 和 James_D 的想法:

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.

除此之外,它是一个标准的 table 视图实现。

由于四舍五入,我示例中的数字并不总是相加为 100%,因此您可能希望对此做些事情,如果是这样,我将其留给您。

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
                        )
                );
            }
        }
    }
}