问题
I have two XYChart
s that I want to display vertically aligned.
The two graphs share the same x axis, but they use different data sets, which values are not of the same order of magnitude. This makes the y axis labels quite different in size. In the end, the two x axes are not aligned anymore.
My objective is to align those x axes.
One proposed solution offers a workaround by rotating the y axis labels, making the label width identical. While this works (and I thank the contributor for his answer), this is not entirely satisfactory: as soon as I have legends that have different sizes, I'm facing the same problem (see this question in R, where several satisfying solutions where found).
I really would like to keep the label orientation unchanged and "play" with the chart components size.
For the sake of reproducibility, here is an example built from Oracle's tutorial:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class Test extends Application {
@Override
public void start(Stage stage) {
stage.setTitle("Line Chart Sample");
final CategoryAxis xAxis1 = new CategoryAxis();
final NumberAxis yAxis1 = new NumberAxis();
xAxis1.setLabel("Month");
final LineChart<String, Number> lineChart1 =
new LineChart<>(xAxis1, yAxis1);
final CategoryAxis xAxis2 = new CategoryAxis();
xAxis2.setLabel("Month");
final NumberAxis yAxis2 = new NumberAxis();
final LineChart<String, Number> lineChart2 =
new LineChart<>(xAxis2, yAxis2);
lineChart1.setTitle("Charts");
lineChart1.setLegendVisible(false);
lineChart2.setLegendVisible(false);
XYChart.Series series1 = new XYChart.Series();
series1.setName("Portfolio 1");
series1.getData().add(new XYChart.Data("Jan", 23));
series1.getData().add(new XYChart.Data("Feb", 14));
series1.getData().add(new XYChart.Data("Mar", 15));
series1.getData().add(new XYChart.Data("Apr", 24));
series1.getData().add(new XYChart.Data("May", 34));
series1.getData().add(new XYChart.Data("Jun", 36));
series1.getData().add(new XYChart.Data("Jul", 22));
series1.getData().add(new XYChart.Data("Aug", 45));
series1.getData().add(new XYChart.Data("Sep", 43));
series1.getData().add(new XYChart.Data("Oct", 17));
series1.getData().add(new XYChart.Data("Nov", 29));
series1.getData().add(new XYChart.Data("Dec", 25));
XYChart.Series series2 = new XYChart.Series();
series2.setName("Portfolio 2");
series2.getData().add(new XYChart.Data("Jan", 330000));
series2.getData().add(new XYChart.Data("Feb", 340000));
series2.getData().add(new XYChart.Data("Mar", 250000));
series2.getData().add(new XYChart.Data("Apr", 440000));
series2.getData().add(new XYChart.Data("May", 390000));
series2.getData().add(new XYChart.Data("Jun", 160000));
series2.getData().add(new XYChart.Data("Jul", 550000));
series2.getData().add(new XYChart.Data("Aug", 540000));
series2.getData().add(new XYChart.Data("Sep", 480000));
series2.getData().add(new XYChart.Data("Oct", 270000));
series2.getData().add(new XYChart.Data("Nov", 370000));
series2.getData().add(new XYChart.Data("Dec", 290000));
lineChart1.getData().addAll(series1);
lineChart2.getData().addAll(series2);
VBox vBox = new VBox();
vBox.addAll(lineChart1, lineChart2);
Scene scene = new Scene(gridPane, 800, 600);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Here is the result:
Is it possible to control the size of the chart component to make the two x axis aligned?
I have tried to change the y axis prefered and minimum width but it does not seem to work.
I'm using Java 8 at the moment. Migrating to Java 9 is possible if necessary.
Edit: As suggested in the answers, I have reduced the scope of this question to the issue of aligning the x axes. The sizing issue is asked here.
回答1:
Get rid of the GridPane
code and replace it with:
VBox vBox = new VBox(lineChart1, lineChart2);
Scene scene = new Scene(vBox, 800, 600);
Full code:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
*
* @author blj0011
*/
public class JavaFXApplication139 extends Application
{
@Override
public void start(Stage primaryStage)
{
primaryStage.setTitle("Line Chart Sample");
final CategoryAxis xAxis1 = new CategoryAxis();
final NumberAxis yAxis1 = new NumberAxis();
xAxis1.setLabel("Month");
final LineChart<String, Number> lineChart1
= new LineChart<>(xAxis1, yAxis1);
final CategoryAxis xAxis2 = new CategoryAxis();
xAxis2.setLabel("Month");
final NumberAxis yAxis2 = new NumberAxis();
final LineChart<String, Number> lineChart2
= new LineChart<>(xAxis2, yAxis2);
lineChart1.setTitle("Charts");
lineChart1.setLegendVisible(false);
lineChart1.getYAxis().setTickLabelRotation(270);
lineChart2.setLegendVisible(false);
lineChart2.getYAxis().setTickLabelRotation(270);
XYChart.Series series1 = new XYChart.Series();
series1.setName("Portfolio 1");
series1.getData().add(new XYChart.Data("Jan", 23));
series1.getData().add(new XYChart.Data("Feb", 14));
series1.getData().add(new XYChart.Data("Mar", 15));
series1.getData().add(new XYChart.Data("Apr", 24));
series1.getData().add(new XYChart.Data("May", 34));
series1.getData().add(new XYChart.Data("Jun", 36));
series1.getData().add(new XYChart.Data("Jul", 22));
series1.getData().add(new XYChart.Data("Aug", 45));
series1.getData().add(new XYChart.Data("Sep", 43));
series1.getData().add(new XYChart.Data("Oct", 17));
series1.getData().add(new XYChart.Data("Nov", 29));
series1.getData().add(new XYChart.Data("Dec", 25));
XYChart.Series series2 = new XYChart.Series();
series2.setName("Portfolio 2");
series2.getData().add(new XYChart.Data("Jan", 330000));
series2.getData().add(new XYChart.Data("Feb", 340000));
series2.getData().add(new XYChart.Data("Mar", 250000));
series2.getData().add(new XYChart.Data("Apr", 440000));
series2.getData().add(new XYChart.Data("May", 390000));
series2.getData().add(new XYChart.Data("Jun", 160000));
series2.getData().add(new XYChart.Data("Jul", 550000));
series2.getData().add(new XYChart.Data("Aug", 540000));
series2.getData().add(new XYChart.Data("Sep", 480000));
series2.getData().add(new XYChart.Data("Oct", 270000));
series2.getData().add(new XYChart.Data("Nov", 370000));
series2.getData().add(new XYChart.Data("Dec", 290000));
lineChart1.getData().addAll(series1);
lineChart2.getData().addAll(series2);
VBox vBox = new VBox(lineChart1, lineChart2);
Scene scene = new Scene(vBox, 800, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
launch(args);
}
}
回答2:
Here's a quick shot on a specialized Layout that
- only cares about children that are of type XYChart
- lays them out vertically with a configurable spacing between them
- sizes the yAxis width to its prefWidth, note that we need to pass-in the actual height of the axis (vs. the typical -1) ... took me a while ..
- aligns each horizontally such that the xAxis are aligned and the yAxis has enough space for its labels
It's not production quality, just for starters on how to do it :)
/**
* Lays out XYCharts vertically such that their x-axis are aligned and
* there's enough space to fully show the labels of all y-axis.
*
* @author Jeanette Winzenburg, Berlin
*/
public class VChartBox extends Pane {
protected void layoutChildren() {
Insets insets = getInsets();
double width = getWidth();
double height = getHeight();
// not entirely certain when to apply all the snaps, this is
// simply copied from vbox
double top = snapSpaceY(insets.getTop());
double left = snapSpaceX(insets.getLeft());
double bottom = snapSpaceY(insets.getBottom());
double right = snapSpaceX(insets.getRight());
double space = snapSpaceY(getSpacing());
double availableWidth = snapSpaceX(width - left - right);
List<XYChart> charts = getCharts();
if (charts.isEmpty()) return;
double heightPerChart = height / charts.size() - space;
OptionalDouble maxYAxisWidth = charts.stream()
.filter(chart -> chart.getYAxis() != null)
.mapToDouble(chart -> chart.getYAxis().prefWidth(heightPerChart))
.max();
double maxYWidth = maxYAxisWidth.orElse(0);
double remainingWidth = availableWidth - maxYWidth;
for (XYChart c : charts) {
Axis axis = c.getYAxis();
double axisWidth = axis != null ? axis.prefWidth(heightPerChart) : 0;
double axisOffset = maxYWidth - axisWidth;
double xOffset = axisOffset + left;
double chartWidth = remainingWidth + axisWidth;
c.resizeRelocate(xOffset, top, chartWidth, heightPerChart);
top += snapSpaceY(c.getHeight() + getSpacing());
}
}
protected List<XYChart> getCharts() {
return getChildren().stream().filter(child -> child instanceof XYChart)
.map(chart -> (XYChart) chart).collect(toList());
}
// properties
/**
* The amount of vertical space between each child in the vbox.
*
* @return the amount of vertical space between each child in the vbox
*/
public final DoubleProperty spacingProperty() {
if (spacing == null) {
spacing = new SimpleDoubleProperty(this, "spacing", 20) {
@Override
public void invalidated() {
requestLayout();
}
};
}
return spacing;
}
private DoubleProperty spacing;
public final void setSpacing(double value) {
spacingProperty().set(value);
}
public final double getSpacing() {
return spacing == null ? 0 : spacing.get();
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(VChartBox.class.getName());
}
来源:https://stackoverflow.com/questions/49139565/aligning-the-x-axes-of-several-xycharts