我想在堆叠的条形图中可视化每月的收款与付款金额,但差异也应立即可见。

我正在使用以下示例代码:

public class Statistics extends Application {
    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    final StackedBarChart<String, Number> sbc = new StackedBarChart<>(xAxis, yAxis);
    final XYChart.Series<String, Number> incoming = new XYChart.Series<>();
    final XYChart.Series<String, Number> outgoing = new XYChart.Series<>();

    @Override
    public void start(Stage stage) {
        xAxis.setLabel("Month");
        xAxis.setCategories(FXCollections.observableArrayList(
                Arrays.asList("Jan", "Feb", "Mar")));
        yAxis.setLabel("Value");
        incoming.setName("Incoming");
        incoming.getData().add(new XYChart.Data("Jan", 25601.34));
        incoming.getData().add(new XYChart.Data("Feb2", 20148.82));
        incoming.getData().add(new XYChart.Data("Mar2", 10000));
        outgoing.setName("Outgoing");
        outgoing.getData().add(new XYChart.Data("Jan", -7401.85));
        outgoing.getData().add(new XYChart.Data("Feb2", -1941.19));
        outgoing.getData().add(new XYChart.Data("Mar2", -5263.37));
        Scene scene = new Scene(sbc, 800, 600);
        sbc.getData().addAll(incoming, outgoing);
        stage.setScene(scene);
        stage.show();
    }
}

结果是:

javafx - 如何使堆叠的条形图中的负值从上一个堆叠开始?-LMLPHP

如您所见,负输出值显示在零以下,而不是从正输入值中减去,这使得很难看到两者之间的差异。

相反,我想要的是负值的条形图从正值的条形图的顶部开始,但是由于它们将重叠,所以还沿x轴应用了偏移量。在“Jan”系列的示例中,该外观应类似于:

javafx - 如何使堆叠的条形图中的负值从上一个堆叠开始?-LMLPHP

我在玩getNode().setTranslateX/Y(),但这似乎不是一个好的解决方案,因为翻译单位不是图表中的刻度,而是其他。

如何以一种优雅的方式创建一个“堆积”的条形图,如第二幅图所示?

最佳答案

您的问题真的很酷!

因此,在这里我想使用BarChart而不是StackedBarChart共享我的解决方案。它可能有点笨拙,但这是唯一为我工作的软件。

我想到的第一件事就是只是简单地取下钢筋并更改其Y坐标,但是不幸的是我们无法直接访问钢筋。因此,在研究了XYChart的源代码之后,我发现图表的所有内容都位于Group plotContent中,但该吸气剂是protected。在这种情况下,要做的事情之一就是扩展类并增加方法的范围。因此(最终),代码如下:

public class Statistics extends Application {
    final CategoryAxis xAxis = new CategoryAxis();
    final NumberAxis yAxis = new NumberAxis();
    // here I use the modified version of chart
    final ModifiedBarChart<String, Number> chart = new ModifiedBarChart<>(xAxis, yAxis);
    final XYChart.Series<String, Number> incoming = new XYChart.Series<>();
    final XYChart.Series<String, Number> outgoing = new XYChart.Series<>();

    @Override
    public void start(Stage stage) {
        xAxis.setLabel("Month");
        xAxis.setCategories(FXCollections.observableArrayList(
                Arrays.asList("Jan", "Feb", "Mar")));
        yAxis.setLabel("Value");


        incoming.setName("Incoming");
        incoming.getData().add(new XYChart.Data("Jan", 25601.34));
        incoming.getData().add(new XYChart.Data("Feb", 20148.82));
        incoming.getData().add(new XYChart.Data("Mar", 10000));

        outgoing.setName("Outgoing");
        // To set the min value of yAxis you can either set lowerBound to 0 or don't use negative numbers here
        outgoing.getData().add(new XYChart.Data("Jan", -7401.85));
        outgoing.getData().add(new XYChart.Data("Feb", -1941.19));
        outgoing.getData().add(new XYChart.Data("Mar", -5263.37));

        chart.getData().addAll(incoming, outgoing);

        Scene scene = new Scene(chart, 800, 600);

        stage.setScene(scene);
        stage.show();

        // and here I iterate over all plotChildren elements
        // note, that firstly all positiveBars come, then all negative ones
        int dataSize = incoming.getData().size();
        for (int i = 0; i < dataSize; i++) {
            Node positiveBar = chart.getPlotChildren().get(i);
            // subtract 1 to make two bars y-axis aligned
            chart.getPlotChildren().get(i + dataSize).setLayoutY(positiveBar.getLayoutY() - 1);
        }

    }

    // note, that I extend BarChart here
    private static class ModifiedBarChart<X, Y> extends BarChart<X, Y> {

        public ModifiedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) {
            super(xAxis, yAxis);
        }

        public ModifiedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X, Y>> data) {
            super(xAxis, yAxis, data);
        }

        public ModifiedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis, @NamedArg("data") ObservableList<Series<X, Y>> data, @NamedArg("categoryGap") double categoryGap) {
            super(xAxis, yAxis, data, categoryGap);
        }


        @Override
        public ObservableList<Node> getPlotChildren() {
            return super.getPlotChildren();
        }
    }

结果:
javafx - 如何使堆叠的条形图中的负值从上一个堆叠开始?-LMLPHP

10-08 09:30