Reputation: 3
I'm struggeling with the Java FX BarChart.. My own implementation of the chart is a class that extends the Java FX GridPane and holds a BarChart as a member variable. If I initialize the whole thing everything works perfect, but if I change the data dynamically (add one or remove one data) the layout will be destroyed. Speaking in pictures this means: (sorry i can't upload picture at the moment)
So the 1st pictures shows the chart after initalization, the 2nd after one element has been added and after deleting one element the categories aren't shown anymore. (I Ccan't upload a picture of this)
So here's my code:
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import someCompanyThings.IMyBarChart;
import someCompanyThings.LocaleService;
import someCompanyThings.INlsKey;
/**
* A Chart with vertical or horizontal bars. It is assumed that the Bars represent positive integer numbers.
* Data may be added or removed dynamically but on the first intent it should display static.
*/
public class MyBarChart extends GridPane implements IMyBarChart {
/*
* Due to data binding problems with a generic bar chart, we hold the two possible bar charts as member variables.
* Also each of them get's a list of Series<?, ?>
*/
private BarChart<String, Number> _barChartVertical;
private BarChart<Number, String> _barChartHorizontal;
private final ObservableList<Series<String, Number>> _dataVertical = FXCollections.observableArrayList();
private final ObservableList<Series<Number, String>> _dataHorizontal = FXCollections.observableArrayList();
private long _maxValue = 0;
private boolean _numberAxisInPercent = false;
private boolean _horizontal = false;
public MyBarChart(INlsKey pTitle, INlsKey pXLabel, INlsKey pYLabel, boolean pNumberAxisInPercent, boolean pHorizontal) {
super();
CategoryAxis categoryAxis = new CategoryAxis();
categoryAxis.setId("bar-chart-category-axis");
NumberAxis numberAxis = new NumberAxis(0.0, 1.0, 1.0);
numberAxis.setId("bar-chart-number-axis");
// create bar chart
// horizontal means that the x-axis is a number axis and the y-axis is a category axis
if (pHorizontal) {
categoryAxis.setLabel(LocaleService.getMessage(pYLabel));
numberAxis.setLabel(LocaleService.getMessage(pXLabel));
_barChartHorizontal = new BarChart<Number, String>(numberAxis, categoryAxis);
_barChartHorizontal.setData(_dataHorizontal);
_barChartHorizontal.setTitle(LocaleService.getMessage(pTitle));
getChildren().add(_barChartHorizontal);
}
else {
categoryAxis.setLabel(LocaleService.getMessage(pXLabel));
numberAxis.setLabel(LocaleService.getMessage(pYLabel));
_barChartVertical = new BarChart<String, Number>(categoryAxis, numberAxis);
_barChartVertical.setData(_dataVertical);
_barChartVertical.setTitle(LocaleService.getMessage(pTitle));
getChildren().add(_barChartVertical);
}
_numberAxisInPercent = pNumberAxisInPercent;
_horizontal = pHorizontal;
/*
* layout
*/
setHgrow(getChildren().get(0), Priority.ALWAYS);
setVgrow(getChildren().get(0), Priority.ALWAYS);
}
@Override
public IMyBarChart addSeries(INlsKey pSeriesName, ObservableList<Data<String, Number>> pDataSet) {
final Series<String, Number> series = new Series<String, Number>(LocaleService.getMessage(pSeriesName), pDataSet);
_dataVertical.add(series);
// iterate over the whole data segment and add it to the series
for (final Data<String, Number> data : pDataSet) {
Tooltip tooltip = new Tooltip();
tooltip.setText(data.getXValue());
Tooltip.install(data.getNode(), tooltip);
if (data.getYValue().longValue() > _maxValue) {
_maxValue = data.getYValue().longValue();
}
}
setNumberAxisScale();
return this;
}
@Override
public IMyBarChart addSeriesHorizontal(INlsKey pSeriesName, ObservableList<Data<Number, String>> pDataSet) {
final Series<Number, String> series = new Series<Number, String>(LocaleService.getMessage(pSeriesName), pDataSet);
_dataHorizontal.add(series);
// iterate over the whole data segment and add it to the series
for (final Data<Number, String> data : pDataSet) {
Tooltip tooltip = new Tooltip();
tooltip.setText(data.getYValue());
Tooltip.install(data.getNode(), tooltip);
if (data.getXValue().longValue() > _maxValue) {
_maxValue = data.getXValue().longValue();
}
}
setNumberAxisScale();
return this;
}
private void setNumberAxisScale() {
NumberAxis numberAxis = getNumberAxis();
// set the number axis as a percent axis
if (_numberAxisInPercent) {
numberAxis.setUpperBound(100);
numberAxis.setTickUnit(10);
}
else {
numberAxis.setUpperBound(_maxValue + 1);
numberAxis.setTickUnit(1);
}
}
@Override
public void setLegendVisible(boolean pVisible) {
if (_barChartHorizontal != null) {
_barChartHorizontal.setLegendVisible(pVisible);
}
else {
_barChartVertical.setLegendVisible(pVisible);
}
}
@Override
public void setCategories(ObservableList<String> pCategories) {
getCategoryAxis().getCategories().setAll(pCategories);
}
/**
*
* @return the category axis of the used bar chart
*/
private CategoryAxis getCategoryAxis() {
if (_horizontal) {
return (CategoryAxis)_barChartHorizontal.getYAxis();
}
else {
return (CategoryAxis)_barChartVertical.getXAxis();
}
}
/**
*
* @return the number axis of the used bar chart
*/
private NumberAxis getNumberAxis() {
if (_horizontal) {
return (NumberAxis)_barChartHorizontal.getXAxis();
}
else {
return (NumberAxis)_barChartVertical.getYAxis();
}
}
}
The initialization process:
final IMyBarChart tablespacesChart = MyFactory.createBarChart(NlsKeys.tablespacesTitle, NlsKeys.tablespacesXAxis,
NlsKeys.tablespacesYAxis, true, true);
// first bool -> numberAxisInPercent, second bool -> horizontal ortientation
tablespacesChart.setLegendVisible(false);
tablespacesChart.setCategories(model.getListCategories());
tablespacesChart.addSeriesHorizontal(NlsKeys.tablespacesLegendYAxis, model.getListDataUsedMax());
The data changes are realised by another class that just uses
model.getCategories().setAll(MyNewCatList); // or
model.getListDataUsedMax().setAll(MyNewList);
Well, i also tried to implement the chart with just one member variable (like BarChart _barChart) but this didn't work. Now i have those layout issues and i dunno where they come from. So i hope you can give me a hint :-)
Upvotes: 0
Views: 1554
Reputation: 73
Here's my solution: First, create a subclass of bar chart to access the private method updateAxisRange:
class MyBarChart<X, Y> extends BarChart<X, Y> {
public MyBarChart(Axis xAxis, Axis yAxis) {
super(xAxis, yAxis);
}
public void relayout() {
updateAxisRange();
}
}
Next, instantiate your bar chart as MyBarChart:
MyBarChart<String, Number> barChart = new MyBarChart<String, Number>(xAxis, yAxis);
And Lastly, you need to listen to resize events on the parent containing the chart, and when they occur, invoke the relayout of the chart. For example:
BorderPane pane = new BorderPane(barChart);
pane.widthProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2) {
barChart.relayout();
}
});
Upvotes: 1