Search code examples
javafxlayoutjavafx-textarea

JavaFX rotated Text element layout problem / bounding box resizing problem


Summary When rotating a JavaFX Text element to realize vertical text, the Text's bounding box seems not to be resized properly whic causes problems. I would like to have the Text's bounding box as narrow as possible in the horizontal direction.

Details

I am building a simple y-coordinate-axis. Currently, the code looks like this:

public class SimpleYAxis extends HBox {

    public SimpleYAxis(double[] tickValues, String axisLabelText, double tickLength, double rowHeight) {

        this.setAlignment(Pos.CENTER);
        this.setSnapToPixel(true);

        VBox labelContainer = new VBox();
        labelContainer.setAlignment(Pos.CENTER);
        labelContainer.setStyle("-fx-border-color: lime");
        Text axisLabel = new Text(axisLabelText);
        axisLabel.setTextAlignment(TextAlignment.CENTER);
        axisLabel.setTextOrigin(VPos.CENTER);
        axisLabel.setStyle("-fx-font: 18 helvetica;");
        axisLabel.setRotate(270);
        labelContainer.getChildren().add(axisLabel);
        this.getChildren().add(labelContainer);

        GridPane axisPane = new GridPane();

        for (int yIdx = 0; yIdx < tickValues.length; yIdx++) {
            Text tickLabel = new Text(String.format("%.2f", tickValues[yIdx]));
            tickLabel.setTextAlignment(TextAlignment.CENTER);
            tickLabel.setTextOrigin(VPos.TOP);
            axisPane.add(tickLabel, 0, yIdx);
            Line tick = new Line(0, tickLength, 10, tickLength);
            axisPane.add(tick, 1, yIdx);
            axisPane.getRowConstraints().add(new RowConstraints(rowHeight));
        }

        ColumnConstraints axisColConstr = new ColumnConstraints();
        axisColConstr.setFillWidth(false);//
        axisColConstr.setHalignment(HPos.RIGHT);

        axisPane.getColumnConstraints().add(axisColConstr);

        Line yAxis = new Line(0, 0, 0, rowHeight * (tickValues.length - 1));
        yAxis.setTranslateY(yAxis.getBoundsInLocal().getHeight() / 2);
        yAxis.setTranslateX(5);
        axisPane.add(yAxis, 1, 0);

        this.getChildren().add(axisPane);

    }

}

With the following "application":

public class SimpleYAxisSample extends Application {

    public static void main(final String[] args) {
        ProcessingProfiler.setVerboseOutputState(true);
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {

        FlowPane pane = new FlowPane();
        pane.setAlignment(Pos.CENTER);

        SimpleYAxis axis = new SimpleYAxis(dataLoader.xData(), "LABEL", 10, 50);
        pane.getChildren().add(axis);

        final Scene scene = new Scene(pane, 1000, 1000);

        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setOnCloseRequest(evt -> Platform.exit());
    }

}

I get the following result (the green box around the label is only for visualization purpuses):

enter image description here

If I do not turn the Text (omit line "axisLabel.setRotate(270)") I get the following:

enter image description here

Note that the size of the green box is identical.

What I would like to have is that the horizontal size (width) of the box reduces to the text height. This is important, as I have in real life environments quite long label texts which then create a very large gap between the axis label and the axis ticks. They should be close. It seems to me hat the bounding box of the text does not adjust when I apply a rotation. How can avoid this?

Another complication: this axis is integrated into a hierarchy of layout components (in a GridPane in FlowPane in VBox in the center of a BorderPane). The label's box must not "spill" to the left (say by translating the text to the right but keeping the box horizontal size) as the box than on top other elements in the layout hierarchy which will not be accessible to mouse actions any more. So it is really important to have the Text's bounding box as narrow as possible, "translation tricks" are not really an option.


Solution

  • Wrap the rotated label a Group to use its transformed bounds for layout calculation.

    Change:

    labelContainer.getChildren().add(axisLabel);
    

    To:

    labelContainer.getChildren().add(new Group(axisLabel));
    

    For more info, see:


    The output of your app when a Group is used to wrap the rotated label. It can be seen that the width of the layout area required for the label's parent now conforms to the rotated bounds of the label rather than original bounds.

    screenshot