Peter Penzov
Peter Penzov

Reputation: 1588

Limit width size of Stacked Bar chart

I tried to implement this solution for StackedBar Chart but it turns out that there is no Java method getBarGap() in StackedBar chart. Is there any solution into the latest JavaFX version for this problem?

Basic example:

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
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.scene.layout.HBox;
import javafx.stage.Stage;

public class MainApp extends Application
{

    private StackedBarChart<String, Number> stackedChart;
    private List<EventsObj> eventsObj;

    @Override
    public void start(Stage stage) throws Exception
    {
        createStackedChart();
        List<EventsObj> testData = generateTestData();

        addStackedChartData(testData);

        HBox hb = new HBox();
        hb.getChildren().add(stackedChart);

        Scene scene = new Scene(hb);
        stage.setTitle("JavaFX and Maven");
        stage.setScene(scene);
        stage.show();
    }

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

    private void createStackedChart()
    {
        CategoryAxis xAxis = new CategoryAxis();
        xAxis.setLabel("Days");
        NumberAxis yAxis = new NumberAxis();

        stackedChart = new StackedBarChart<>(xAxis, yAxis);
        stackedChart.setCategoryGap(20);

        stackedChart.widthProperty().addListener((obs, b, b1) ->
        {
            // Chart Bar column is not automatically resized. We need to wait for next JavaFX releases to fix this.
            Platform.runLater(() -> setMaxBarWidth(stackedChart, xAxis, 40, 10));
        });
    }

    private List<EventsObj> generateTestData()
    {
        eventsObj = new ArrayList<>();

        for (int i = 0; i < 5; i++)
        {
            eventsObj.add(new EventsObj(String.valueOf(randomDate()), random(2, 60), random(2, 60), random(2, 60), random(2, 60)));
        }

        return eventsObj;
    }

    public static int random(int lowerBound, int upperBound)
    {
        return (lowerBound + (int) Math.round(Math.random() * (upperBound - lowerBound)));
    }

    private LocalDate randomDate()
    {
        Random random = new Random();
        int minDay = (int) LocalDate.of(1900, 1, 1).toEpochDay();
        int maxDay = (int) LocalDate.of(2015, 1, 1).toEpochDay();
        long randomDay = minDay + random.nextInt(maxDay - minDay);

        LocalDate randomBirthDate = LocalDate.ofEpochDay(randomDay);

        return randomBirthDate;
    }

    private void addStackedChartData(List<EventsObj> data)
    {
        List<XYChart.Series<String, Number>> dataSeries = new ArrayList<>(data.size());

        for (EventsObj data1 : data)
        {
            final XYChart.Series<String, Number> series1 = new XYChart.Series<>();
            series1.setName(data1.getDate());
            series1.getData().setAll(
                new XYChart.Data<>("Info", data1.getInfo()));
            dataSeries.add(series1);
        }

        stackedChart.getData().setAll(dataSeries);
    }

    private void setMaxBarWidth(StackedBarChart<String, Number> bc, CategoryAxis xAxis, double maxBarWidth, double minCategoryGap)
    {
        double barWidth = 0;
        do
        {
            double catSpace = xAxis.getCategorySpacing();
            double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getCategoryGap());
            barWidth = (avilableBarSpace / bc.getData().size()) - bc.getCategoryGap();
            if (barWidth > maxBarWidth)
            {
                avilableBarSpace = (maxBarWidth + bc.getCategoryGap()) * bc.getData().size();
                bc.setCategoryGap(catSpace - avilableBarSpace - bc.getCategoryGap());
            }
        }
        while (barWidth > maxBarWidth);

        do
        {
            double catSpace = xAxis.getCategorySpacing();
            double avilableBarSpace = catSpace - (minCategoryGap + bc.getCategoryGap());
            barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getCategoryGap());
            avilableBarSpace = (barWidth + bc.getCategoryGap()) * bc.getData().size();
            bc.setCategoryGap(catSpace - avilableBarSpace - bc.getCategoryGap());
        }
        while (barWidth < maxBarWidth && bc.getCategoryGap() > minCategoryGap);
    }
}

Upvotes: 2

Views: 1568

Answers (1)

Jos&#233; Pereda
Jos&#233; Pereda

Reputation: 45456

In a BarChart chart, the different series added are plotted in the different columns from the same category with a barGap, some spacing in between.

But in a StackBarChart chart, the different series are plotted stacked in the same column, hence in this case there is no bar gap.

If you take the setMaxBarWidth() method from this answer, all you need to do is set bc.getBarGap() to 0, and take sbc.getData().size() as 1. In this case there is no need for iterations.

This will be the new method, really simplified:

private void setMaxCategoryWidth(double maxCategoryWidth, double minCategoryGap){
    double catSpace = xAxis.getCategorySpacing();
    sbc.setCategoryGap(catSpace - Math.min(maxCategoryWidth, catSpace - minCategoryGap));
}

Basically, all you have to do is subtract the desired width of your category from the initial width of the category and put this extra space into the categoryGap.

Now, you can create your chart:

StackedBarChart<String, Number> sbc =  new StackedBarChart<>(xAxis, yAxis);

And set your desired size for the categories:

setMaxCategoryWidth(40, 10);
sbc.widthProperty().addListener((obs, b, b1) -> {
    Platform.runLater(() -> setMaxCategoryWidth(40, 10));
});

This pic is generated based on the sample 7.4 taken from here.

Size 40

Upvotes: 3

Related Questions