wyit
wyit

Reputation: 363

JavaFX 14, Stacked Bar chart does not display negative values

Example:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.StackedBarChart;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;

public class Main extends Application {

    public static void main(String[] args) {
        launch(args);
    }

    // Start the javafx application
    @Override
    public void start(Stage stage) {

        CategoryAxis xAxis = new CategoryAxis();
        NumberAxis yAxis = new NumberAxis();

        XYChart.Series<String, Number> series = new XYChart.Series<String, Number>();
        series.getData().add(new XYChart.Data<String, Number>(0 + "", 5));
        series.getData().add(new XYChart.Data<String, Number>(1 + "", -5));
        series.getData().add(new XYChart.Data<String, Number>(2 + "", 3));
        series.getData().add(new XYChart.Data<String, Number>(3 + "", -2));

        StackedBarChart<String, Number> weightChangeChartBar = new StackedBarChart<String, Number>(xAxis, yAxis);
        weightChangeChartBar.getData().addAll(series);

        Scene scene = new Scene(weightChangeChartBar, 500, 500);
        stage.setScene(scene);
        stage.show();
    }

}

Outcome:

Outcome

None of the previous stack overflow questions on this issue are working in JavaFX14 (for me anyway), does anyone know of a solution?

Previous Questions:

  1. JavaFX:13 Stacked Bar Chart negative values not showing
  2. JavaFx StackedBar Chart Issue
  3. Javafx StackedBarChart bug

Upvotes: 3

Views: 418

Answers (2)

DarkMatterMatt
DarkMatterMatt

Reputation: 576

The patch in wyit's answer didn't work for me, but it led me in the right direction; I had to override a different method in StackedBarChart.

The official implementation (GitHub) uses bar.getStyleClass().setAll(), which removes the negative CSS class from the bar, resulting in incorrect rendering.

The following Java class patches the problematic method in StackedBarChart.

/**
 * Fixes StackedBarChart can't display negative numbers.
 */
public class PatchedStackedBarChart<X, Y> extends StackedBarChart<X, Y> {
    public PatchedStackedBarChart(@NamedArg("xAxis") Axis<X> xAxis, @NamedArg("yAxis") Axis<Y> yAxis) {
        super(xAxis, yAxis);
    }

    /**
     * Override the method that breaks the graph, patch to add missing "negative" CSS class.
     */
    @Override
    protected void dataItemAdded(Series<X, Y> series, int itemIndex, Data<X, Y> item) {
        super.dataItemAdded(series, itemIndex, item);

        Number val = (Number) (item.getYValue() instanceof Number ? item.getYValue() : item.getXValue());
        if (val.doubleValue() < 0) {
            // add missing CSS class
            item.getNode().getStyleClass().add("negative");
        }
    }

    /**
     * Override the method that breaks the graph, patch so it doesn't override styles.
     */
    @Override
    protected void seriesChanged(ListChangeListener.Change<? extends Series> c) {
        for (int i = 0; i < getData().size(); i++) {
            List<Data<X, Y>> items = getData().get(i).getData();
            for (int j = 0; j < items.size(); j++) {
                Node bar = items.get(j).getNode();
                // change .setAll to .addAll to avoid overriding styles
                bar.getStyleClass().removeIf(s -> s.matches("chart-bar|(series|data)\\d+"));
                bar.getStyleClass().addAll("chart-bar", "series" + i, "data" + j);
            }
        }
    }
}

Upvotes: 0

wyit
wyit

Reputation: 363

Solved it: The Bar Charts behavior must be overridden to solve the bug, the series must be added to the Bar Chart, THEN data can be added to the series.

 package example;

    import javafx.application.Application;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.chart.CategoryAxis;
    import javafx.scene.chart.NumberAxis;
    import javafx.scene.chart.StackedBarChart;
    import javafx.scene.chart.XYChart;
    import javafx.stage.Stage;

    public class Main extends Application {

        public static void main(String[] args) {
            launch(args);
        }

        // Start the javafx application
        @Override
        public void start(Stage stage) {

            CategoryAxis xAxis = new CategoryAxis();
            NumberAxis yAxis = new NumberAxis();

            XYChart.Series<String, Number> series = new XYChart.Series<String, Number>();

             // modify behavior to counter bug
             StackedBarChart<String, Number> barChart = new StackedBarChart<String, Number>(xAxis, yAxis) {
                @Override
                protected void dataItemAdded(XYChart.Series<String,Number> series, int itemIndex, XYChart.Data<String,Number> item) {
                    super.dataItemAdded(series, itemIndex, item);

                    Node bar = item.getNode();
                    double barVal = item.getYValue().doubleValue();

                    if (barVal < 0) {
                        bar.getStyleClass().add("negative");
                    }   
                }       
            };

            // add series
            barChart.getData().addAll(series);

            // THEN add data
            series.getData().add(new XYChart.Data<String, Number>(0 + "", 5));
            series.getData().add(new XYChart.Data<String, Number>(1 + "", -5));
            series.getData().add(new XYChart.Data<String, Number>(2 + "", 3));
            series.getData().add(new XYChart.Data<String, Number>(3 + "", -2));


            Scene scene = new Scene(barChart, 500, 500);
            stage.setScene(scene);
            stage.show();
        }

    }

Negative nodes must also have separate CSS from the positive nodes.

/* ====== BAR CHART =========================================================== */
/* TODO flip gradient vertical for negative bars */
.chart-bar {
    -fx-bar-fill: #22bad9;
    -fx-background-color: linear-gradient(derive(-fx-bar-fill,-30%), derive(-fx-bar-fill,-40%)),
                          linear-gradient(derive(-fx-bar-fill,80%), derive(-fx-bar-fill, 0%)),
                          linear-gradient(derive(-fx-bar-fill,30%), derive(-fx-bar-fill,-10%));
    -fx-background-insets: 0,1,2;
    -fx-background-radius: 5 5 0 0, 4 4 0 0, 3 3 0 0;
}

.negative.chart-bar {
   -fx-background-color: linear-gradient(to top, derive(-fx-bar-fill,-30%), derive(-fx-bar-fill,-40%)),
                          linear-gradient(to top, derive(-fx-bar-fill, 80%), derive(-fx-bar-fill,0%)),
                          linear-gradient(to top, derive(-fx-bar-fill,30%), derive(-fx-bar-fill,-10%));
   -fx-background-radius: 0 0 5 5, 0 0 4 4, 0 0 3 3;
}
.bar-chart:horizontal .chart-bar, .stacked-bar-chart:horizontal .chart-bar {
    -fx-background-color: linear-gradient(to left, derive(-fx-bar-fill,-30%), derive(-fx-bar-fill,-40%)),
                          linear-gradient(to left, derive(-fx-bar-fill,80%), derive(-fx-bar-fill, 0%)),
                          linear-gradient(to left, derive(-fx-bar-fill,30%), derive(-fx-bar-fill,-10%));
    -fx-background-radius: 0 5 5 0, 0 4 4 0, 0 3 3 0;
}
.bar-chart:horizontal .negative.chart-bar, .stacked-bar-chart:horizontal .negative.chart-bar {
    -fx-background-color: linear-gradient(to right, derive(-fx-bar-fill,-30%), derive(-fx-bar-fill,-40%)),
                          linear-gradient(to right, derive(-fx-bar-fill, 80%), derive(-fx-bar-fill, 0%)),
                          linear-gradient(to right, derive(-fx-bar-fill,30%), derive(-fx-bar-fill,-10%));
    -fx-background-radius: 5 0 0 5, 4 0 0 4, 3 0 0 3;
}

Upvotes: 1

Related Questions