Issue
Is there a way to set JavaFX Bar Chart column width size?
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.stage.Stage;
public class BarChartSample extends Application {
final static String austria = "Austria";
final static String brazil = "Brazil";
final static String france = "France";
final static String italy = "Italy";
final static String usa = "USA";
@Override public void start(Stage stage) {
stage.setTitle("Bar Chart Sample");
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis();
final BarChart<String,Number> bc =
new BarChart<String,Number>(xAxis,yAxis);
bc.setTitle("Country Summary");
xAxis.setLabel("Country");
yAxis.setLabel("Value");
XYChart.Series series1 = new XYChart.Series();
series1.setName("2003");
series1.getData().add(new XYChart.Data(austria, 25601.34));
series1.getData().add(new XYChart.Data(brazil, 20148.82));
series1.getData().add(new XYChart.Data(france, 10000));
series1.getData().add(new XYChart.Data(italy, 35407.15));
series1.getData().add(new XYChart.Data(usa, 12000));
XYChart.Series series2 = new XYChart.Series();
series2.setName("2004");
series2.getData().add(new XYChart.Data(austria, 57401.85));
series2.getData().add(new XYChart.Data(brazil, 41941.19));
series2.getData().add(new XYChart.Data(france, 45263.37));
series2.getData().add(new XYChart.Data(italy, 117320.16));
series2.getData().add(new XYChart.Data(usa, 14845.27));
XYChart.Series series3 = new XYChart.Series();
series3.setName("2005");
series3.getData().add(new XYChart.Data(austria, 45000.65));
series3.getData().add(new XYChart.Data(brazil, 44835.76));
series3.getData().add(new XYChart.Data(france, 18722.18));
series3.getData().add(new XYChart.Data(italy, 17557.31));
series3.getData().add(new XYChart.Data(usa, 92633.68));
Scene scene = new Scene(bc,800,600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
For example there is a way to setBarGap size but I can't find a way to set the maximum width of a Bar Chart column.
Is there a way to solve this?
Solution
BarChart
works on the basis that both barGap
(gap between bars in the same category) and categoryGap
(gap between bars in separate categories) can be set by the user, so for a given size of chart, the width of the bars is calculated internally every time a layout is requested.
To set a maximum width on every bar, we have to change either barGap
or categoryGap
accordingly. But we can do it programmatically, to respond to any change in the width of the chart.
Let's first calculate the bar width for an initial layout. According to BarChart.layoutPlotChildren()
:
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
double barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
In the example provided by the OP, given the default gap values (barGap=4
, categoryGap=10
), the resulting barWidth
is 37.4.
Let's assume we want to set a limit of 40, and a minimum category gap of 10:
double maxBarWidth=40;
double minCategoryGap=10;
If the chart's width increases when the scene is resized, we could limit barWidth
by increasing categoryGap
:
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
if(barWidth > maxBarWidth){
avilableBarSpace=(maxBarWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
}
} while(barWidth>maxBarWidth);
Note the do-while loop: as we modify categoryGap
a new layout is performed and some initial values change. It takes usually two iterations to get the desired result.
We can repeat this operation as long as the chart's width keeps growing.
But if the chart's width starts decreasing, the gap between categories should be decreased too, while the bar width stays close to its maximum:
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + bc.getBarGap());
barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getBarGap());
avilableBarSpace=(barWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
} while(barWidth < maxBarWidth && bc.getCategoryGap()>minCategoryGap);
Putting everything together, and listening to scene width changes:
...
Scene scene = new Scene(bc,800,600);
bc.getData().addAll(series1, series2, series3);
stage.setScene(scene);
stage.show();
double maxBarWidth=40;
double minCategoryGap=10;
scene.widthProperty().addListener((obs,n,n1)->{
if(bc.getData().size()==0) return;
if(n!=null && (n1.doubleValue()>n.doubleValue())){
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (bc.getCategoryGap() + bc.getBarGap());
barWidth = (avilableBarSpace / bc.getData().size()) - bc.getBarGap();
if (barWidth >maxBarWidth){
avilableBarSpace=(maxBarWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
}
} while(barWidth>maxBarWidth);
}
if(n!=null && (n1.doubleValue()<n.doubleValue()) && bc.getCategoryGap()>minCategoryGap){
double barWidth=0;
do{
double catSpace = xAxis.getCategorySpacing();
double avilableBarSpace = catSpace - (minCategoryGap + bc.getBarGap());
barWidth = Math.min(maxBarWidth, (avilableBarSpace / bc.getData().size()) - bc.getBarGap());
avilableBarSpace=(barWidth + bc.getBarGap())* bc.getData().size();
bc.setCategoryGap(catSpace-avilableBarSpace-bc.getBarGap());
} while(barWidth < maxBarWidth && bc.getCategoryGap()>minCategoryGap);
}
});
Note that in the rest of the cases, barWidth
will be calculated internally.
Answered By - José Pereda