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

commonMain.jetbrains.datalore.plot.builder.assemble.PlotAssemblerPlotContext.kt Maven / Gradle / Ivy

There is a newer version: 4.5.3-alpha1
Show newest version
/*
 * Copyright (c) 2022. 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.assemble

import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.plot.base.*
import jetbrains.datalore.plot.base.aes.AestheticsDefaults
import jetbrains.datalore.plot.base.data.TransformVar
import jetbrains.datalore.plot.base.render.LegendKeyElementFactory
import jetbrains.datalore.plot.base.scale.ScaleUtil
import jetbrains.datalore.plot.builder.GeomLayer
import jetbrains.datalore.plot.common.data.SeriesUtil

internal class PlotAssemblerPlotContext(
    layersByTile: List>,
    private val scaleMap: TypedScaleMap
) : PlotContext {

    private val stitchedPlotLayers: List = createStitchedLayers(layersByTile)
    private val transformedDomainByAes: MutableMap, DoubleSpan> = HashMap()
    private val tooltipFormatters: MutableMap, (Any?) -> String> = HashMap()

    override val layers: List = stitchedPlotLayers.map(::ContextPlotLayer)

    override fun getScale(aes: Aes<*>): Scale<*> {
        checkPositionalAes(aes)
        return scaleMap[aes]
    }

    override fun overallTransformedDomain(aes: Aes<*>): DoubleSpan {
        checkPositionalAes(aes)
        return transformedDomainByAes.getOrPut(aes) {
            computeOverallTransformedDomain(aes, stitchedPlotLayers, scaleMap)
        }
    }

    override fun getTooltipFormatter(aes: Aes<*>, defaultValue: () -> (Any?) -> String): (Any?) -> String {
        checkPositionalAes(aes)
        return tooltipFormatters.getOrPut(aes, defaultValue)
    }


    private companion object {
        fun createStitchedLayers(
            layersByPanel: List>,
        ): List {
            if (layersByPanel.isEmpty()) return emptyList()

            // stitch together layers from all panels
            val layerCount = layersByPanel[0].size

            val stitchedLayers = ArrayList()
            for (i in 0 until layerCount) {
                val layersOnPlane = ArrayList()

                // Collect layer[i] chunks from all panels.
                for (panelLayers in layersByPanel) {
                    layersOnPlane.add(panelLayers[i])
                }

                stitchedLayers.add(StitchedPlotLayer(layersOnPlane))
            }

            return stitchedLayers
        }

        fun computeOverallTransformedDomain(
            aes: Aes<*>,
            stitchedLayers: List,
            scaleMap: TypedScaleMap
        ): DoubleSpan {
            checkPositionalAes(aes)

            fun isMatching(v: DataFrame.Variable, aes: Aes<*>, isYOrientation: Boolean): Boolean {
                val varAes = TransformVar.toAes(v)
                return when {
                    Aes.isPositionalXY(varAes) -> Aes.toAxisAes(
                        varAes,
                        isYOrientation
                    ) == aes // collecting pos variables
                    else -> varAes == aes
                }
            }

            val domainsRaw = ArrayList()
            for (layer in stitchedLayers) {
                val variables = layer.getVariables()
                    .filter { it.isTransform }
                    .filter { isMatching(it, aes, layer.isYOrientation) }

                for (transformVar in variables) {
                    val domain = layer.getDataRange(transformVar)
                    if (domain != null) {
                        domainsRaw.add(domain)
                    }
                }
            }

            val overallTransformedDomain = domainsRaw.reduceOrNull { acc, v -> acc.union(v) }

            val scale = scaleMap.get(aes)
            return if (scale.isContinuousDomain) {
                finalizeOverallTransformedDomain(overallTransformedDomain, scale.transform as ContinuousTransform)
            } else {
                // Discrete domain
                overallTransformedDomain ?: DoubleSpan.singleton(0.0)
            }
        }

        private fun finalizeOverallTransformedDomain(
            transformedDomain: DoubleSpan?,
            transform: ContinuousTransform
        ): DoubleSpan {
            val (dataLower, dataUpper) = when (transformedDomain) {
                null -> Pair(Double.NaN, Double.NaN)
                else -> Pair(transformedDomain.lowerEnd, transformedDomain.upperEnd)
            }
            val (scaleLower, scaleUpper) = ScaleUtil.transformedDefinedLimits(transform)

            val lowerEnd = if (scaleLower.isFinite()) scaleLower else dataLower
            val upperEnd = if (scaleUpper.isFinite()) scaleUpper else dataUpper

            val newRange = when {
                lowerEnd.isFinite() && upperEnd.isFinite() -> DoubleSpan(lowerEnd, upperEnd)
                lowerEnd.isFinite() -> DoubleSpan(lowerEnd, lowerEnd)
                upperEnd.isFinite() -> DoubleSpan(upperEnd, upperEnd)
                else -> null
            }

            return SeriesUtil.ensureApplicableRange(newRange)
        }

        fun checkPositionalAes(aes: Aes<*>) {
            // expect only X,Y or not positional
            check(!Aes.isPositionalXY(aes) || aes == Aes.X || aes == Aes.Y) {
                "Positional aesthetic should be either X or Y but was $aes"
            }
        }
    }

    private class ContextPlotLayer(
        private val stitchedPlotLayer: StitchedPlotLayer
    ) : PlotContext.Layer {
        override val isLegendDisabled: Boolean get() = stitchedPlotLayer.isLegendDisabled
        override val aestheticsDefaults: AestheticsDefaults get() = stitchedPlotLayer.aestheticsDefaults
        override val legendKeyElementFactory: LegendKeyElementFactory get() = stitchedPlotLayer.legendKeyElementFactory

        override fun renderedAes(): List> = stitchedPlotLayer.renderedAes()

        override fun hasBinding(aes: Aes<*>): Boolean = stitchedPlotLayer.hasBinding(aes)

        override fun hasConstant(aes: Aes<*>): Boolean = stitchedPlotLayer.hasConstant(aes)

        override fun  getConstant(aes: Aes): T = stitchedPlotLayer.getConstant(aes)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy