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

commonMain.jetbrains.datalore.plot.builder.assemble.GeomLayerBuilder.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.assemble

import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.base.typedKey.TypedKeyHashMap
import jetbrains.datalore.plot.base.*
import jetbrains.datalore.plot.base.aes.AestheticsDefaults
import jetbrains.datalore.plot.base.annotations.Annotations
import jetbrains.datalore.plot.base.data.DataFrameUtil
import jetbrains.datalore.plot.base.data.TransformVar
import jetbrains.datalore.plot.base.geom.GeomBase
import jetbrains.datalore.plot.base.geom.LiveMapGeom
import jetbrains.datalore.plot.base.geom.LiveMapProvider
import jetbrains.datalore.plot.base.interact.ContextualMapping
import jetbrains.datalore.plot.base.interact.GeomTargetLocator.LookupSpec
import jetbrains.datalore.plot.base.interact.MappedDataAccess
import jetbrains.datalore.plot.base.pos.PositionAdjustments
import jetbrains.datalore.plot.base.render.LegendKeyElementFactory
import jetbrains.datalore.plot.base.stat.SimpleStatContext
import jetbrains.datalore.plot.base.stat.Stats
import jetbrains.datalore.plot.base.util.afterOrientation
import jetbrains.datalore.plot.builder.GeomLayer
import jetbrains.datalore.plot.builder.MarginSide
import jetbrains.datalore.plot.builder.VarBinding
import jetbrains.datalore.plot.builder.annotation.AnnotationLine
import jetbrains.datalore.plot.builder.annotation.AnnotationSpecification
import jetbrains.datalore.plot.builder.assemble.geom.GeomProvider
import jetbrains.datalore.plot.builder.assemble.geom.PointDataAccess
import jetbrains.datalore.plot.builder.data.DataProcessing
import jetbrains.datalore.plot.builder.data.GroupingContext
import jetbrains.datalore.plot.builder.data.StatInput
import jetbrains.datalore.plot.builder.interact.ContextualMappingProvider
import jetbrains.datalore.plot.builder.presentation.DefaultFontFamilyRegistry
import jetbrains.datalore.plot.builder.presentation.FontFamilyRegistry
import jetbrains.datalore.plot.builder.scale.ScaleProvider
import jetbrains.datalore.plot.builder.theme.ThemeTextStyle

class GeomLayerBuilder constructor(
    private val geomProvider: GeomProvider,
    private val stat: Stat,
    private val posProvider: PosProvider,
    private val fontFamilyRegistry: FontFamilyRegistry,
) {

    private val myBindings = ArrayList()
    private val myConstantByAes = TypedKeyHashMap()
    private var myGroupingVarName: String? = null
    private var myPathIdVarName: String? = null
    private val myScaleProviderByAes = HashMap, ScaleProvider<*>>()

    private var myDataPreprocessor: ((DataFrame, Map, Transform>) -> DataFrame)? = null
    private var myLocatorLookupSpec: LookupSpec = LookupSpec.NONE
    private var myContextualMappingProvider: ContextualMappingProvider = ContextualMappingProvider.NONE

    private var myIsLegendDisabled: Boolean = false
    private var isYOrientation: Boolean = false

    private var isMarginal: Boolean = false
    private var marginalSide: MarginSide = MarginSide.LEFT
    private var marginalSize: Double = Double.NaN

    private var myAnnotationsProvider: ((MappedDataAccess, DataFrame) -> Annotations?)? = null

    fun addBinding(v: VarBinding): GeomLayerBuilder {
        myBindings.add(v)
        return this
    }

    fun groupingVar(v: DataFrame.Variable): GeomLayerBuilder {
        myGroupingVarName = v.name
        return this
    }

    fun groupingVarName(v: String): GeomLayerBuilder {
        myGroupingVarName = v
        return this
    }

    fun pathIdVarName(v: String): GeomLayerBuilder {
        myPathIdVarName = v
        return this
    }

    fun  addConstantAes(aes: Aes, v: T): GeomLayerBuilder {
        myConstantByAes.put(aes, v)
        return this
    }

    fun  addScaleProvider(aes: Aes, scaleProvider: ScaleProvider): GeomLayerBuilder {
        myScaleProviderByAes[aes] = scaleProvider
        return this
    }

    fun locatorLookupSpec(v: LookupSpec): GeomLayerBuilder {
        myLocatorLookupSpec = v
        return this
    }

    fun contextualMappingProvider(v: ContextualMappingProvider): GeomLayerBuilder {
        myContextualMappingProvider = v
        return this
    }

    fun disableLegend(v: Boolean): GeomLayerBuilder {
        myIsLegendDisabled = v
        return this
    }


    fun yOrientation(v: Boolean): GeomLayerBuilder {
        isYOrientation = v
        return this
    }

    fun marginal(
        isMarginal: Boolean,
        marginalSide: MarginSide,
        marginalSize: Double
    ): GeomLayerBuilder {
        this.isMarginal = isMarginal
        this.marginalSide = marginalSide
        this.marginalSize = marginalSize
        return this
    }

    fun annotationSpecification(annotationSpec: AnnotationSpecification, themeTextStyle: ThemeTextStyle): GeomLayerBuilder {
        myAnnotationsProvider = { dataAccess, dataFrame ->
            AnnotationLine.createAnnotations(annotationSpec, dataAccess, dataFrame, themeTextStyle)
        }
        return this
    }

    fun build(
        data: DataFrame,
        scaleMap: TypedScaleMap,
        scaleMapppersNP: Map, ScaleMapper<*>>,
    ): GeomLayer {
        val transformByAes: Map, Transform> = scaleMap.keySet().associateWith {
            scaleMap[it].transform
        }

        @Suppress("NAME_SHADOWING")
        var data = data
        if (myDataPreprocessor != null) {
            // Test and Demo
            data = myDataPreprocessor!!(data, transformByAes)
        }

        // make sure 'original' series are transformed
        data = DataProcessing.transformOriginals(data, myBindings, transformByAes)

        val replacementBindings = HashMap(
            // No 'origin' variables beyond this point.
            // Replace all 'origin' variables in bindings with 'transform' variables
            myBindings.associate {
                it.aes to if (it.variable.isOrigin) {
                    val transformVar = DataFrameUtil.transformVarFor(it.aes)
                    VarBinding(transformVar, it.aes)
                } else {
                    it
                }
            }
        )

        // add 'transform' variable for each 'stat' variable
        val bindingsToPut = ArrayList()
        for (binding in replacementBindings.values) {
            val variable = binding.variable
            if (variable.isStat) {
                val aes = binding.aes
                val transform = transformByAes.getValue(aes)
                val transformVar = TransformVar.forAes(aes)
                data = DataFrameUtil.applyTransform(data, variable, transformVar, transform)
                bindingsToPut.add(VarBinding(transformVar, aes))
            }
        }

        // replace 'stat' vars with 'transform' vars in bindings
        for (binding in bindingsToPut) {
            replacementBindings[binding.aes] = binding
        }

        // (!) Positional aes scales have undefined `mapper` at this time because
        // dimensions of plot are not yet known.
        // Data Access shouldn't use aes mapper (!)
//        val dataAccess = PointDataAccess(data, replacementBindings, scaleMap)

        val groupingVariables = DataProcessing.defaultGroupingVariables(
            data,
            myBindings,
            myPathIdVarName
        )

        val groupingContext = GroupingContext(data, groupingVariables, myGroupingVarName, handlesGroups())
        return MyGeomLayer(
            data,
            geomProvider,
            posProvider,
            geomProvider.renders(),
            groupingContext.groupMapper,
//            replacementBindings.values,
            replacementBindings,
            myConstantByAes,
            scaleMap,
            scaleMapppersNP,
            myLocatorLookupSpec,
//            myContextualMappingProvider.createContextualMapping(dataAccess, data),
            myContextualMappingProvider,
            myIsLegendDisabled,
            isYOrientation = isYOrientation,
            isMarginal = isMarginal,
            marginalSide = marginalSide,
            marginalSize = marginalSize,
            fontFamilyRegistry = fontFamilyRegistry,
            annotationsProvider = myAnnotationsProvider
        )
    }

    private fun handlesGroups(): Boolean {
        return geomProvider.handlesGroups() || posProvider.handlesGroups()
    }


    private class MyGeomLayer(
        override val dataFrame: DataFrame,
        geomProvider: GeomProvider,
        override val posProvider: PosProvider,
        renderedAes: List>,
        override val group: (Int) -> Int,
        private val varBindings: Map, VarBinding>,
        constantByAes: TypedKeyHashMap,
        override val scaleMap: TypedScaleMap,
        override val scaleMapppersNP: Map, ScaleMapper<*>>,
        override val locatorLookupSpec: LookupSpec,
        private val contextualMappingProvider: ContextualMappingProvider,
        override val isLegendDisabled: Boolean,
        override val isYOrientation: Boolean,
        override val isMarginal: Boolean,
        override val marginalSide: MarginSide,
        override val marginalSize: Double,
        override val fontFamilyRegistry: FontFamilyRegistry,
        private val annotationsProvider : ((MappedDataAccess, DataFrame) -> Annotations?)?
    ) : GeomLayer {

        override val geom: Geom = geomProvider.createGeom()
        override val geomKind: GeomKind = geomProvider.geomKind
        override val aestheticsDefaults: AestheticsDefaults = geomProvider.aestheticsDefaults()

        private val myRenderedAes: List>
        private val myConstantByAes: TypedKeyHashMap

        override val legendKeyElementFactory: LegendKeyElementFactory
            get() = geom.legendKeyElementFactory

        override val isLiveMap: Boolean
            get() = geom is LiveMapGeom

        init {
            myRenderedAes = ArrayList(renderedAes)

            // constant value by aes (default + specified)
            myConstantByAes = TypedKeyHashMap()
            for (key in constantByAes.keys()) {
                myConstantByAes.put(key, constantByAes[key])
            }
        }

        override fun renderedAes(): List> {
            return myRenderedAes
        }

        override fun hasBinding(aes: Aes<*>): Boolean {
            return varBindings.containsKey(aes)
        }

        override fun  getBinding(aes: Aes): VarBinding {
            return varBindings[aes]!!
        }

        override fun hasConstant(aes: Aes<*>): Boolean {
            return myConstantByAes.containsKey(aes)
        }

        override fun  getConstant(aes: Aes): T {
            require(hasConstant(aes)) { "Constant value is not defined for aes $aes" }
            return myConstantByAes[aes]
        }

        override fun  getDefault(aes: Aes): T {
            return aestheticsDefaults.defaultValue(aes)
        }

        override fun preferableNullDomain(aes: Aes<*>): DoubleSpan {
            @Suppress("NAME_SHADOWING")
            val aes = aes.afterOrientation(isYOrientation)
            return (geom as GeomBase).preferableNullDomain(aes)
        }

        override fun rangeIncludesZero(aes: Aes<*>): Boolean {
            @Suppress("NAME_SHADOWING")
            val aes = aes.afterOrientation(isYOrientation)
            return aestheticsDefaults.rangeIncludesZero(aes)
        }

        override fun setLiveMapProvider(liveMapProvider: LiveMapProvider) {
            if (geom is LiveMapGeom) {
                geom.setLiveMapProvider(liveMapProvider)
            } else {
                throw IllegalStateException("Not Livemap: " + geom::class.simpleName)
            }
        }

        override fun createContextualMapping(): ContextualMapping {
            val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation)
            return contextualMappingProvider.createContextualMapping(dataAccess, dataFrame)
        }

        override fun createAnnotations(): Annotations? {
            return annotationsProvider?.let { provider ->
                val dataAccess = PointDataAccess(dataFrame, varBindings, scaleMap, isYOrientation)
                provider(dataAccess, dataFrame)
            }
        }
    }

    companion object {

        fun demoAndTest(
            geomProvider: GeomProvider,
            stat: Stat,
            posProvider: PosProvider = PosProvider.wrap(PositionAdjustments.identity()),
        ): GeomLayerBuilder {
            val builder = GeomLayerBuilder(geomProvider, stat, posProvider, DefaultFontFamilyRegistry())
            builder.myDataPreprocessor = { data, transformByAes ->
                val transformedData = DataProcessing.transformOriginals(data, builder.myBindings, transformByAes)
                when (builder.stat) {
                    Stats.IDENTITY -> transformedData
                    else -> {
                        val statCtx = SimpleStatContext(transformedData)
                        val groupingVariables = DataProcessing.defaultGroupingVariables(
                            data,
                            builder.myBindings,
                            builder.myPathIdVarName
                        )
                        val groupingCtx = GroupingContext(
                            transformedData,
                            groupingVariables,
                            builder.myGroupingVarName,
                            expectMultiple = true  // ?
                        )
                        val statInput = StatInput(
                            transformedData,
                            builder.myBindings,
                            transformByAes,
                            statCtx,
                            flipXY = false
                        )
                        val dataAndGroupingContext = DataProcessing.buildStatData(
                            statInput,
                            builder.stat,
                            groupingCtx,
                            facetVariables = emptyList(),
                            varsWithoutBinding = emptyList(),
                            orderOptions = emptyList(),
                            aggregateOperation = null,
                            ::println
                        )

                        dataAndGroupingContext.data
                    }
                }
            }

            return builder
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy