Reputation: 121
I am building a Java desktop app for which I need to generate charts. One of these needs to be a bar chart that has a line chart layered over it.
I used a StackPane for overlaying the two graphs and populated them individually, but as you can see in the picture they don't overlay correctly, the line graph starts to the left of the y axis. Also, this changes when the left y axis doesn't have a label (???), in the sense that the line shows correctly but the bars get messed up.
I also have two different Y axis for each chart, but the right one doesn't get displayed.
How can I make everything overlay nicely and each graph get positioned correctly? What am I doing wrong?
public class Graphs implements Initializable {
@FXML StackPane stackPane;
@FXML BarChart barChart;
@FXML CategoryAxis xAxis;
@FXML NumberAxis yAxis;
@FXML NumberAxis yAxisRight;
@FXML LineChart lineChartEffective;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
xAxis.setCategories(FXCollections.<String>observableArrayList("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"));
yAxis.setLowerBound(0);
yAxis.setUpperBound(100);
yAxis.setTickUnit(10);
yAxisRight.setSide(Side.RIGHT);
yAxisRight.setLowerBound(0);
yAxisRight.setUpperBound(50);
yAxisRight.setTickUnit(1);
barChart.setLegendVisible(false);
barChart.setAnimated(false);
barChart.setLegendVisible(false);
barChart.setAnimated(false);
barChart.setAlternativeRowFillVisible(false);
barChart.setAlternativeColumnFillVisible(false);
barChart.setHorizontalGridLinesVisible(false);
barChart.setVerticalGridLinesVisible(false);
barChart.getXAxis().setVisible(true);
barChart.getYAxis().setVisible(true);
barChart.getData().add(getAverageLoadPerFTE());
xAxis.setLabel("Month");
yAxis.setLabel("Level (%)");
yAxisRight.setLabel("Level (u)");
lineChartEffective.setLegendVisible(false);
lineChartEffective.setAnimated(false);
lineChartEffective.setCreateSymbols(true);
lineChartEffective.setAlternativeRowFillVisible(false);
lineChartEffective.setAlternativeColumnFillVisible(false);
lineChartEffective.setHorizontalGridLinesVisible(false);
lineChartEffective.setVerticalGridLinesVisible(false);
lineChartEffective.getXAxis().setVisible(false);
lineChartEffective.getYAxis().setVisible(true);
lineChartEffective.getStylesheets().addAll(getClass().getResource("chartEffective.css").toExternalForm());
lineChartEffective.getData().add( getEffectiveCustomerSupport());
}
private XYChart.Series<String,Number> getAverageLoadPerFTE() {
XYChart.Series<String, Number> series = new XYChart.Series<String, Number>();
series.getData().add(new XYChart.Data<String,Number>("Jan", 58 ));
series.getData().add(new XYChart.Data<String,Number>("Feb", 54 ));
series.getData().add(new XYChart.Data<String,Number>("Mar", 47 ));
series.getData().add(new XYChart.Data<String,Number>("Apr", 34 ));
series.getData().add(new XYChart.Data<String,Number>("May", 33 ));
series.getData().add(new XYChart.Data<String,Number>("Jun", 32 ));
series.getData().add(new XYChart.Data<String,Number>("Jul", 30 ));
series.getData().add(new XYChart.Data<String,Number>("Aug", 36 ));
series.getData().add(new XYChart.Data<String,Number>("Sep", 29 ));
series.getData().add(new XYChart.Data<String,Number>("Oct", 33 ));
series.getData().add(new XYChart.Data<String,Number>("Nov", 0 ));
series.getData().add(new XYChart.Data<String,Number>("Dec", 0 ));
series.setName("Average load per FTE");
return series;
}
private XYChart.Series<String,Number> getEffectiveCustomerSupport() {
XYChart.Series<String, Number> series = new XYChart.Series<String, Number>();
series.getData().add(new XYChart.Data<String,Number>("Jan", 2.9 ));
series.getData().add(new XYChart.Data<String,Number>("Feb", 2.7 ));
series.getData().add(new XYChart.Data<String,Number>("Mar", 3.3 ));
series.getData().add(new XYChart.Data<String,Number>("Apr", 3.4 ));
series.getData().add(new XYChart.Data<String,Number>("May", 3.3 ));
series.getData().add(new XYChart.Data<String,Number>("Jun", 3.2 ));
series.getData().add(new XYChart.Data<String,Number>("Jul", 3.3 ));
series.getData().add(new XYChart.Data<String,Number>("Aug", 4 ));
series.getData().add(new XYChart.Data<String,Number>("Sep", 3.8 ));
series.getData().add(new XYChart.Data<String,Number>("Oct", 4.1 ));
series.getData().add(new XYChart.Data<String,Number>("Nov", 0 ));
series.getData().add(new XYChart.Data<String,Number>("Dec", 0 ));
series.setName("Effective customer support");
return series;
}
}
Graphs.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.chart.LineChart?>
<?import javafx.scene.chart.CategoryAxis?>
<?import javafx.scene.chart.NumberAxis?>
<?import javafx.scene.chart.BarChart?>
<AnchorPane prefHeight="731.0" prefWidth="878.0" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Graphs">
<children>
<StackPane fx:id="stackPane" layoutX="26.0" layoutY="32.0" prefHeight="523.0" prefWidth="708.0">
<children>
<BarChart fx:id="barChart" >
<xAxis>
<CategoryAxis side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis fx:id = "yAxis" side="LEFT" />
</yAxis>
</BarChart>
<LineChart fx:id="lineChartEffective" stylesheets="@chartEffective.css">
<xAxis>
<CategoryAxis fx:id = "xAxis" side="BOTTOM" />
</xAxis>
<yAxis>
<NumberAxis fx:id="yAxisRight" side="RIGHT"/>
</yAxis>
</LineChart>
</children>
</StackPane>
</children>
</AnchorPane>
chartsEffective.css:
.chart-plot-background {
-fx-background-color: transparent;
}
.default-color0.chart-series-line {
-fx-stroke: #4b0082;
}
.default-color0.chart-line-symbol {
-fx-background-color: #4b0082, white;
}
Upvotes: 5
Views: 1255
Reputation: 1
You can use HBox
with Pane
fillers to offset the width differences between the charts. This can also be done with translateX
or StackPane
alignment options, but those solutions can glitch out when you dynamically minimize/maximize/resize the window.
private static <X, Y> HBox wrapChart(XYChart<X, Y> chart1, XYChart<X, Y> chart2, boolean left)
{
Pane filler = new Pane();
filler.minWidthProperty().bind(chart2.getYAxis().widthProperty());
filler.maxWidthProperty().bind(chart2.getYAxis().widthProperty());
HBox.setHgrow(chart1, Priority.ALWAYS);
return left ? new HBox(chart1, filler) : new HBox(filler, chart1);
}
stackPane.getChildren().addAll
(
wrapChart(leftChart, rightChart, true),
wrapChart(rightChart, leftChart, false)
)
But there are more problems to it. When you change the axis side
property, the title and legend position will change as well. You need to decide how to deal with these problems. You can just remove the conflicting elements or perfectly align them too.
The following sections describe how you can align secondary elements with the default side(BOTTOM
). This also works for TOP
side.
for(Node n : leftChart.lookupAll(".chart-title"))
n.translateXProperty().bind(rightChart.getYAxis().widthProperty().divide(2));
for(Node n : rightChart.lookupAll(".chart-title"))
n.translateXProperty().bind(leftChart.getYAxis().widthProperty().multiply(-1).divide(2));
If legends are equal, then you can overlap them or simply hide one of them.
for(Node n : leftChart.lookupAll(".chart-legend"))
n.translateXProperty().bind(rightChart.getYAxis().widthProperty().divide(2));
for(Node n : rightChart.lookupAll(".chart-legend"))
n.translateXProperty().bind(leftChart.getYAxis().widthProperty().divide(2).multiply(-1));
If legends are different, then you can move them closer to their axes
for(Node n : leftChart.lookupAll(".chart-legend"))
n.translateXProperty().bind(n.layoutXProperty().divide(2).multiply(-1));
for(Node n : rightChart.lookupAll(".chart-legend"))
n.translateXProperty().bind(n.layoutXProperty().divide(2));
You can also use DoubleXYChart that has of all the above code and more.
Upvotes: 0