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

commonMain.jetbrains.datalore.plot.PlotSizeHelper.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

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.assemble.PlotFacets
import jetbrains.datalore.plot.builder.presentation.Defaults.ASPECT_RATIO
import jetbrains.datalore.plot.builder.presentation.Defaults.DEF_LIVE_MAP_SIZE
import jetbrains.datalore.plot.builder.presentation.Defaults.DEF_PLOT_SIZE
import jetbrains.datalore.plot.builder.presentation.Defaults.MIN_PLOT_WIDTH
import jetbrains.datalore.plot.config.BunchConfig
import jetbrains.datalore.plot.config.Option
import jetbrains.datalore.plot.config.OptionsAccessor
import jetbrains.datalore.plot.config.PlotConfig
import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.max

object PlotSizeHelper {

    /**
     * Semi-open API.
     * Used in Lets-Plot-Kotlin, IDEA Plugin(?)
     */
    fun scaledFigureSize(
        figureSpec: Map,
        containerWidth: Int,
        containerHeight: Int
    ): Pair {
        return when {
            PlotConfig.isGGBunchSpec(figureSpec) -> {
                // don't scale GGBunch size
                val bunchSize = plotBunchSize(figureSpec)
                Pair(ceil(bunchSize.x).toInt(), ceil(bunchSize.y).toInt())
            }
            PlotConfig.isPlotSpec(figureSpec) -> {
                // for single plot: scale component to fit in requested size
                val aspectRatio = figureAspectRatio(figureSpec)
                if (aspectRatio >= 1.0) {
                    val plotHeight = containerWidth / aspectRatio
                    val scaling = if (plotHeight > containerHeight) containerHeight / plotHeight else 1.0
                    Pair(floor(containerWidth * scaling).toInt(), floor(plotHeight * scaling).toInt())
                } else {
                    val plotWidth = containerHeight * aspectRatio
                    val scaling = if (plotWidth > containerWidth) containerWidth / plotWidth else 1.0
                    Pair(floor(plotWidth * scaling).toInt(), floor(containerHeight * scaling).toInt())
                }
            }
            else ->
                // was failure - just keep given size
                Pair(containerWidth, containerHeight)
        }
    }

    /**
     * Plot spec can be either raw or processed
     */
    fun singlePlotSize(
        plotSpec: Map<*, *>,
        plotSize: DoubleVector?,
        plotMaxWidth: Double?,
        plotPreferredWidth: Double?,
        facets: PlotFacets,
        containsLiveMap: Boolean
    ): DoubleVector {
        if (plotSize != null) {
            return plotSize
        }

        val defaultSize = getSizeOptionOrNull(plotSpec) ?: defaultSinglePlotSize(facets, containsLiveMap)
        val scaledSize = plotPreferredWidth?.let { w ->
            defaultSize.mul(max(MIN_PLOT_WIDTH, w) / defaultSize.x)
        } ?: defaultSize

        return if (plotMaxWidth != null && plotMaxWidth < scaledSize.x) {
            scaledSize.mul(max(MIN_PLOT_WIDTH, plotMaxWidth) / scaledSize.x)
        } else {
            scaledSize
        }
    }

    private fun bunchItemBoundsList(bunchSpec: Map): List {
        val bunchConfig = BunchConfig(bunchSpec)
        if (bunchConfig.bunchItems.isEmpty()) {
            throw IllegalArgumentException("No plots in the bunch")
        }

        val plotBounds = ArrayList()
        for (bunchItem in bunchConfig.bunchItems) {
            plotBounds.add(
                DoubleRectangle(
                    DoubleVector(bunchItem.x, bunchItem.y),
                    bunchItemSize(bunchItem)
                )
            )
        }
        return plotBounds
    }

    /**
     * Expects 'processed specs' (aka client specs)
     */
    internal fun bunchItemSize(bunchItem: BunchConfig.BunchItem): DoubleVector {
        return if (bunchItem.hasSize()) {
            bunchItem.size
        } else {
            singlePlotSize(
                bunchItem.featureSpec,
                null, null, null,
                PlotFacets.undefined(), false
            )
        }
    }

    private fun defaultSinglePlotSize(facets: PlotFacets, containsLiveMap: Boolean): DoubleVector {
        var plotSize = DEF_PLOT_SIZE
        if (facets.isDefined) {
            val panelWidth = DEF_PLOT_SIZE.x * (0.5 + 0.5 / facets.colCount)
            val panelHeight = DEF_PLOT_SIZE.y * (0.5 + 0.5 / facets.rowCount)
            plotSize = DoubleVector(panelWidth * facets.colCount, panelHeight * facets.rowCount)
        } else if (containsLiveMap) {
            plotSize = DEF_LIVE_MAP_SIZE
        }
        return plotSize
    }

    private fun getSizeOptionOrNull(singlePlotSpec: Map<*, *>): DoubleVector? {
        if (!singlePlotSpec.containsKey(Option.Plot.SIZE)) {
            return null
        }
        @Suppress("UNCHECKED_CAST")
        val map = OptionsAccessor(singlePlotSpec as Map)
            .getMap(Option.Plot.SIZE)
        val sizeSpec = OptionsAccessor.over(map)
        val width = sizeSpec.getDouble("width")
        val height = sizeSpec.getDouble("height")
        if (width == null || height == null) {
            return null
        }
        return DoubleVector(width, height)
    }

    /**
     * @param figureFpec Plot or plot bunch specification (can be 'raw' or processed).
     * @return Figure dimatsions width/height ratio.
     */
    fun figureAspectRatio(figureFpec: Map<*, *>): Double {
        return when {
            PlotConfig.isPlotSpec(figureFpec) -> {
                // single plot
                getSizeOptionOrNull(figureFpec)?.let { it.x / it.y } ?: ASPECT_RATIO
            }
            PlotConfig.isGGBunchSpec(figureFpec) -> {
                // bunch
                @Suppress("UNCHECKED_CAST")
                val bunchSize = plotBunchSize(figureFpec as Map)
                bunchSize.x / bunchSize.y
            }
            else -> throw RuntimeException("Unexpected plot spec kind: " + PlotConfig.specKind(figureFpec))
        }
    }

    fun plotBunchSize(plotBunchFpec: Map): DoubleVector {
        require(PlotConfig.isGGBunchSpec(plotBunchFpec)) {
            "Plot Bunch is expected but was kind: ${PlotConfig.specKind(plotBunchFpec)}"
        }
        return plotBunchSize(bunchItemBoundsList(plotBunchFpec))
    }

    private fun plotBunchSize(bunchItemBoundsIterable: Iterable): DoubleVector {
        return bunchItemBoundsIterable
            .fold(DoubleRectangle(DoubleVector.ZERO, DoubleVector.ZERO)) { acc, bounds ->
                acc.union(bounds)
            }
            .dimension
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy