override fun doLayout()

in plot-builder/src/commonMain/kotlin/org/jetbrains/letsPlot/core/plot/builder/layout/FacetedPlotLayout.kt [42:271]


    override fun doLayout(preferredSize: DoubleVector, coordProvider: CoordProvider): PlotLayoutInfo {
        val plotLayoutMargins = plotTheme.layoutMargins()
        var tilesAreaSize = DoubleVector(
            preferredSize.x - (insets.left + insets.right),
            preferredSize.y - (insets.top + insets.bottom)
        )
            .subtract(DoubleVector(plotLayoutMargins.width, plotLayoutMargins.height))

        val facetTiles = facets.tileInfos()

        // Calc sizes of facet tabs
        val facetColTabHeights: Map<Int, List<Double>> // facet tab heights for column labels
        val facetRowTabWidth: Double                   // max width of row labels
        val totalAddedHeight: Double                   // summary height of horizontal facet tabs
        val totalAddedWidth: Double                    // width with padding of facet vertical tabs

        // facet tab heights for column labels
        if (facetsTheme.horizontalFacetStrip().showStrip()) {
            facetColTabHeights = facetTiles
                .groupBy({ it.row }, { it.colLabs })
                .mapValues {
                    val subColLabels = HashMap<Int, Double>()
                    it.value.forEach { colLabs ->
                        colLabs.forEachIndexed { index, colLab ->
                            val labHeight = facetLabelSize(
                                colLab,
                                facetsTheme.horizontalFacetStrip(),
                                marginSize = Thickness::height
                            )
                            subColLabels[index] = max(labHeight, subColLabels[index] ?: 0.0)
                        }
                    }
                    subColLabels.values.toList()
                }
            totalAddedHeight = facetColTabHeights.values.sumOf { heights ->
                facetColHeadTotalHeight(heights, facetsTheme.horizontalFacetStrip().stripSpacing().y)
            }
        } else {
            facetColTabHeights = emptyMap()
            totalAddedHeight = 0.0
        }

        // max width of row labels
        if (facetsTheme.verticalFacetStrip().showStrip()) {
            facetRowTabWidth = facetTiles
                .mapNotNull { it.rowLab }
                .maxOfOrNull { facetLabelSize(it, facetsTheme.verticalFacetStrip(), marginSize = Thickness::width) }
                ?: 0.0
            totalAddedWidth = facetRowTabWidth + facetsTheme.verticalFacetStrip().stripSpacing().x
        } else {
            facetRowTabWidth = 0.0
            totalAddedWidth = 0.0
        }
        val facetColHeadHeightGetter = { facetTile: PlotFacets.FacetTileInfo ->
            facetColTabHeights[facetTile.row]?.let { heights ->
                facetColHeadTotalHeight(heights, facetsTheme.horizontalFacetStrip().stripSpacing().y)
            } ?: facetColHeadTotalHeight(facetTile.colLabs, facetsTheme.horizontalFacetStrip())
        }

        val labsTotalDim = DoubleVector(totalAddedWidth, totalAddedHeight)

        tilesAreaSize = tilesAreaSize.subtract(labsTotalDim)

        val layoutInfos: List<TileLayoutInfo> = if (facets.freeHScale || facets.freeVScale) {
            FreeScalesTilesLayouter.createTileLayoutInfos(
                tilesAreaSize,
                facets,
                layoutProviderByTile,
                totalAddedHSize,
                totalAddedVSize,
                coordProvider,
                hAxisTheme = hAxisTheme,
                vAxisTheme = vAxisTheme,
            )
        } else {
            FixedScalesTilesLayouter.createTileLayoutInfos(
                tilesAreaSize,
                facets,
                layoutProviderByTile,
                totalAddedHSize,
                totalAddedVSize,
                coordProvider,
                hAxisTheme = hAxisTheme,
                vAxisTheme = vAxisTheme,
            )
        }

        // Create final plot tiles layout infos.


        // Align geom areas of tiles.

        // absolute offsets of tile geom areas.
        val geomOffsetByCol = geomOffsetsByCol(
            layoutInfos, facetTiles,
            facetsTheme.panelSpacing().x, facets.colCount
        )

        val geomOffsetByRow = geomOffsetsByRow(
            layoutInfos, facetTiles,
            facetsTheme.horizontalFacetStrip().showStrip(),
            facetsTheme.panelSpacing().y, facets.rowCount,
            facetColHeadHeightGetter
        )

        val tileBoundsList = ArrayList<DoubleRectangle>()
        val geomOuterBoundsList = ArrayList<DoubleRectangle>()
        for ((index, facetTile) in facetTiles.withIndex()) {
            val layoutInfo = layoutInfos[index]

            val col = facetTile.col
            val row = facetTile.row
            val geomX = geomOffsetByCol[col]
            val geomY = geomOffsetByRow[row]
            val outerGeomSize = layoutInfo.geomOuterBounds.dimension

            // Tile width
            val tileLabelWidth = if (facetTile.rowLab != null && facetsTheme.verticalFacetStrip().showStrip()) {
                facetRowTabWidth + facetsTheme.verticalFacetStrip().stripSpacing().x // one label on the left side
            } else {
                0.0
            }

            val axisWidth = if (facetTile.hasVAxis) {
                layoutInfo.axisThicknessY()
            } else {
                0.0
            }

            val tileX = geomX - axisWidth
            val tileWidth = outerGeomSize.x + axisWidth + tileLabelWidth

            // Tile height
            val tileLabelHeight = if (facetsTheme.horizontalFacetStrip().showStrip()) {
                facetColHeadHeightGetter(facetTile)
            } else {
                0.0
            }

            val axisHeight = if (facetTile.hasHAxis) {
                layoutInfo.axisThicknessX()
            } else {
                0.0
            }

            val tileY = geomY - tileLabelHeight
            val tileHeight = outerGeomSize.y + tileLabelHeight + axisHeight

//            if (col == 0) {
//                println("[$row][$tileY] $tileHeight = ${geomSize.y} + $tileLabelHeight + $axisHeight")
//            }

            // Absolute bounds...
            val tileBounds = DoubleRectangle(
                DoubleVector(tileX, tileY),
                DoubleVector(tileWidth, tileHeight)
            )
            val geomOuterBounds = DoubleRectangle(
                DoubleVector(geomX, geomY),
                outerGeomSize
            )

            tileBoundsList.add(tileBounds)
            geomOuterBoundsList.add(geomOuterBounds)
        }

        val tilesAreaOrigin = tileBoundsList
            .reduce { b0, b1 -> b0.union(b1) }
            .origin

        // Normalize origin of tilesAreaBounds.
        val originDelta = tilesAreaOrigin.negate()
        val tilesPaddingLeftTop = insets.leftTop

        val finalLayoutInfos = ArrayList<TileLayoutInfo>()
        for ((index, facetTile) in facetTiles.withIndex()) {
            val layoutInfo = layoutInfos[index]
            val geomInnerBoundsOffset = layoutInfo.geomInnerBounds.origin
                .subtract(layoutInfo.geomOuterBounds.origin)
            val geomContentBoundsOffset = layoutInfo.geomContentBounds.origin
                .subtract(layoutInfo.geomOuterBounds.origin)

            val tileBounds = tileBoundsList[index]
            val geomOuterBounds = geomOuterBoundsList[index]
            val geomInnerBounds = DoubleRectangle(
                geomOuterBounds.origin.add(geomInnerBoundsOffset),
                layoutInfo.geomInnerBounds.dimension
            )
            val geomContentBounds = DoubleRectangle(
                geomOuterBounds.origin.add(geomContentBoundsOffset),
                layoutInfo.geomContentBounds.dimension
            )

            val xLabels = when (facetsTheme.horizontalFacetStrip().showStrip()) {
                true -> {
                    val colHeights = facetColTabHeights[facetTile.row] ?: facetTile.colLabs.map {
                        facetLabelSize(
                            it,
                            facetsTheme.horizontalFacetStrip(),
                            marginSize = Thickness::height
                        )
                    }
                    facetTile.colLabs.zip(colHeights)
                }

                false -> emptyList()
            }
            val yLabel = when (facetsTheme.verticalFacetStrip().showStrip()) {
                true -> facetTile.rowLab?.let { it to facetRowTabWidth }
                false -> null
            }
            val newLayoutInfo = TileLayoutInfo(
                tilesPaddingLeftTop,
                geomWithAxisBounds = tileBounds.add(originDelta),
                geomOuterBounds = geomOuterBounds.add(originDelta),
                geomInnerBounds = geomInnerBounds.add(originDelta),
                geomContentBounds = geomContentBounds.add(originDelta),
                layoutInfo.axisInfos,
                hAxisShown = facetTile.hasHAxis,
                vAxisShown = facetTile.hasVAxis,
                trueIndex = facetTile.trueIndex,
                facetXLabels = xLabels,
                facetYLabel = yLabel
            )
            finalLayoutInfos.add(newLayoutInfo)
        }

        val plotInsets = DoubleInsets(tilesPaddingLeftTop, insets.rightBottom)
        return PlotLayoutInfo(finalLayoutInfos, plotInsets)
    }