Aligning the X axes of several XYCharts

谁都会走 提交于 2021-02-16 18:31:51

问题


I have two XYCharts 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!