override fun doLayout()

in plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/axis/label/HorizontalRotatedLabelsLayout.kt [32:159]


    override fun doLayout(
        axisDomain: DoubleSpan,
        axisLength: Double,
    ): AxisLabelsLayoutInfo {
        if (breaks.isEmpty) {
            return noLabelsLayoutInfo(axisLength, orientation)
        }

        if (!theme.showLabels()) {
            return noLabelsLayoutInfo(axisLength, orientation)
        }

        val ticks = breaks.projectOnAxis(axisDomain, axisLength, isHorizontal = true)
        val labelBoundsList = labelBoundsList(ticks, breaks.labels, HORIZONTAL_TICK_LOCATION)

        var overlap = false
        val bounds = labelBoundsList.fold(null) { acc: DoubleRectangle?, b ->
            overlap = overlap || acc != null && acc.xRange().connected(
                b.xRange().expanded(MIN_TICK_LABEL_DISTANCE / 2)
            )
            GeometryUtil.union(b, acc)
        }!! // labels are not empty so bounds can't be null

        val maxLabelHeight = labelBoundsList.maxOf { it.height }

        val orientationSign = when (orientation) {
            Orientation.TOP -> -1.0
            Orientation.BOTTOM -> 1.0
            else -> throw IllegalStateException("Unsupported orientation $orientation")
        }

        val radAngle = toRadians(myRotationAngle)
        val sinA = sin(radAngle)
        val cosA = cos(radAngle)
        val isVertical = abs(cosA) < 1e-6
        val isUpsideDown = cosA < 0
        val isHorizontal = abs(sinA) < 1e-6 && !isUpsideDown
        val isLabelDirectedFromTick = when (orientation) {
            Orientation.TOP -> sinA > 0
            Orientation.BOTTOM -> sinA < 0
            else -> throw IllegalStateException("Unsupported orientation $orientation")
        }

        val vJust = if (theme.labelVJust().isNaN()) {
            if (orientation == Orientation.BOTTOM) 1.0 else 0.0
        } else {
            theme.labelVJust()
        }

        val hJust = if (theme.labelHJust().isNaN()) {
            when {
                isHorizontal || isVertical -> 0.5
                isLabelDirectedFromTick -> 0.0
                else -> 1.0
            }
        } else {
            theme.labelHJust()
        }

        val horizontalAnchor = when {
            isVertical -> hAnchorForVerticalLabels(vJust)
            isUpsideDown -> Text.HorizontalAnchor.MIDDLE
            hJust == 0.0 && (isHorizontal || isLabelDirectedFromTick) -> Text.HorizontalAnchor.LEFT
            hJust == 1.0 && (isHorizontal || !isLabelDirectedFromTick) -> Text.HorizontalAnchor.RIGHT
            else -> Text.HorizontalAnchor.MIDDLE
        }

        val isCornerCase = !isHorizontal && horizontalAnchor != Text.HorizontalAnchor.MIDDLE

        val yBBoxOffset: (DoubleRectangle) -> Double = { rect: DoubleRectangle ->
            orientationSign * maxLabelHeight / 2 + (maxLabelHeight - rect.height) * (0.5 - vJust)
        }

        val xBBoxOffset: (DoubleRectangle) -> Double = { rect: DoubleRectangle ->
            rect.width * (0.5 - hJust)
        }

        val verticalAnchor = when {
            isVertical -> vAnchorForVerticalLabels(hJust)
            isHorizontal && vJust == 0.0 -> Text.VerticalAnchor.BOTTOM
            isHorizontal && vJust == 1.0 -> Text.VerticalAnchor.TOP
            isCornerCase && orientation == Orientation.BOTTOM -> Text.VerticalAnchor.TOP
            isCornerCase && orientation == Orientation.TOP -> Text.VerticalAnchor.BOTTOM
            else -> Text.VerticalAnchor.CENTER
        }

        val yOffsetSpecial =
            orientationSign * maxLabelHeight * yOffsetCoefficient(isLabelDirectedFromTick, horizontalAnchor)

        val yOffset: (DoubleRectangle) -> Double = { rect: DoubleRectangle ->
            when {
                isVertical && horizontalAnchor != Text.HorizontalAnchor.MIDDLE -> yOffsetSpecial
                isCornerCase -> (maxLabelHeight - rect.height) * ((orientationSign + 1) / 2 - vJust)
                isHorizontal && verticalAnchor == Text.VerticalAnchor.TOP -> yBBoxOffset(rect) - rect.height / 2
                isHorizontal && verticalAnchor == Text.VerticalAnchor.BOTTOM -> yBBoxOffset(rect) + rect.height / 2
                else -> yBBoxOffset(rect)
            }
        }

        val xOffset: (DoubleRectangle) -> Double = { rect: DoubleRectangle ->
            when {
                isVertical -> 0.0
                horizontalAnchor != Text.HorizontalAnchor.MIDDLE -> 0.0
                else -> xBBoxOffset(rect)
            }
        }

        val labelAdditionalOffsets = labelBoundsList.map {
            DoubleVector(xOffset(it), yOffset(it))
        }

        val adjustedLabelBoundsList = labelBoundsList.map {
            val origin =
                DoubleVector(
                    xBBoxOffset(it) + it.origin.x,
                    yBBoxOffset(it) + it.origin.y - orientationSign * it.height / 2
                )
            DoubleRectangle(origin, it.dimension)
        }

        return createAxisLabelsLayoutInfoBuilder(bounds, overlap)
            .labelHorizontalAnchor(horizontalAnchor)
            .labelVerticalAnchor(verticalAnchor)
            .labelRotationAngle(-myRotationAngle)
            .labelAdditionalOffsets(labelAdditionalOffsets)
            .labelBoundsList(adjustedLabelBoundsList.map(::alignToLabelMargin)) // for debug drawing
            .build()
    }