All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.jetbrains.datalore.plot.builder.layout.XYPlotTileLayout.kt Maven / Gradle / Ivy

There is a newer version: 4.5.3-alpha1
Show newest version
/*
 * Copyright (c) 2020. JetBrains s.r.o.
 * Use of this source code is governed by the MIT license that can be found in the LICENSE file.
 */

package jetbrains.datalore.plot.builder.layout

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.guide.Orientation
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.GEOM_MARGIN
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.GEOM_MIN_SIZE
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.clipBounds
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.geomBounds
import jetbrains.datalore.plot.builder.layout.XYPlotLayoutUtil.maxTickLabelsBounds

internal class XYPlotTileLayout(
    private val xAxisLayout: AxisLayout,
    private val yAxisLayout: AxisLayout
) : TileLayout {

    override fun doLayout(preferredSize: DoubleVector): TileLayoutInfo {

        var (xAxisInfo, yAxisInfo) = computeAxisInfos(
            xAxisLayout,
            yAxisLayout,
            preferredSize
        )

        var geomBounds = geomBounds(
            xAxisThickness = xAxisInfo.axisBounds().dimension.y,
            yAxisThickness = yAxisInfo.axisBounds().dimension.x,
            preferredSize
        )

        // X-axis labels bounds may exceed axis length - adjust
        run {
            val maxTickLabelsBounds = maxTickLabelsBounds(
                Orientation.BOTTOM,
                0.0,
                geomBounds,
                preferredSize
            )
            val tickLabelsBounds = xAxisInfo.tickLabelsBounds
            val leftOverflow = maxTickLabelsBounds.left - tickLabelsBounds!!.origin.x
            val rightOverflow = tickLabelsBounds.origin.x + tickLabelsBounds.dimension.x - maxTickLabelsBounds.right
            if (leftOverflow > 0) {
                geomBounds = DoubleRectangle(
                    geomBounds.origin.x + leftOverflow,
                    geomBounds.origin.y,
                    geomBounds.dimension.x - leftOverflow,
                    geomBounds.dimension.y
                )
            }
            if (rightOverflow > 0) {
                geomBounds = DoubleRectangle(
                    geomBounds.origin.x,
                    geomBounds.origin.y,
                    geomBounds.dimension.x - rightOverflow,
                    geomBounds.dimension.y
                )
            }
        }

        geomBounds = geomBounds.union(
            DoubleRectangle(geomBounds.origin, GEOM_MIN_SIZE)
        )

        // Combine geom area and x/y axis
        val geomWithAxisBounds =
            tileBounds(
                xAxisInfo.axisBounds(),
                yAxisInfo.axisBounds(),
                geomBounds
            )

        // sync axis info with new (may be) geom area size
        xAxisInfo = xAxisInfo.withAxisLength(geomBounds.width).build()
        yAxisInfo = yAxisInfo.withAxisLength(geomBounds.height).build()

        return TileLayoutInfo(
            geomWithAxisBounds,
            geomBounds,
            clipBounds(geomBounds),
            xAxisInfo,
            yAxisInfo,
            trueIndex = 0
        )
    }

    companion object {
        private const val AXIS_STRETCH_RATIO = 0.1  // allow 10% axis flexibility (on each end)

        private fun tileBounds(
            xAxisBounds: DoubleRectangle,
            yAxisBounds: DoubleRectangle,
            geomBounds: DoubleRectangle
        ): DoubleRectangle {
            // Can't just union bounds because
            // x-axis has zero origin
            // y-axis has negative origin
            val leftTop = DoubleVector(
                geomBounds.left - yAxisBounds.width,
                geomBounds.top - GEOM_MARGIN
            )
            val rightBottom = DoubleVector(
                geomBounds.right + GEOM_MARGIN,
                geomBounds.bottom + xAxisBounds.height
            )
            return DoubleRectangle(leftTop, rightBottom.subtract(leftTop))
        }

        private fun computeAxisInfos(
            xAxisLayout: AxisLayout,
            yAxisLayout: AxisLayout,
            plotSize: DoubleVector
        ): Pair {
            val xAxisThickness = xAxisLayout.initialThickness()
            var yAxisInfo = computeYAxisInfo(
                yAxisLayout,
                geomBounds(
                    xAxisThickness,
                    yAxisLayout.initialThickness(),
                    plotSize
                )
            )

            val yAxisThickness = yAxisInfo.axisBounds().dimension.x
            var xAxisInfo = computeXAxisInfo(
                xAxisLayout,
                plotSize, geomBounds(
                    xAxisThickness,
                    yAxisThickness,
                    plotSize
                )
            )

            if (xAxisInfo.axisBounds().dimension.y > xAxisThickness) {
                // Re-layout y-axis if x-axis became thicker than its 'original thickness'.
                yAxisInfo = computeYAxisInfo(
                    yAxisLayout,
                    geomBounds(
                        xAxisInfo.axisBounds().dimension.y,
                        yAxisThickness,
                        plotSize
                    )
                )
            }

            return Pair(xAxisInfo, yAxisInfo)
        }

        private fun computeXAxisInfo(
            axisLayout: AxisLayout,
            plotSize: DoubleVector,
            geomBounds: DoubleRectangle
        ): AxisLayoutInfo {
            val axisLength = geomBounds.dimension.x
            val stretch = axisLength * AXIS_STRETCH_RATIO
            val maxTickLabelsBounds = maxTickLabelsBounds(
                Orientation.BOTTOM,
                stretch,
                geomBounds,
                plotSize
            )
            return axisLayout.doLayout(geomBounds.dimension, maxTickLabelsBounds)
        }

        private fun computeYAxisInfo(
            axisLayout: AxisLayout,
            geomBounds: DoubleRectangle
        ): AxisLayoutInfo {
            return axisLayout.doLayout(geomBounds.dimension, null)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy