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

commonMain.jetbrains.datalore.plot.builder.scale.ScaleProviderBuilder.kt Maven / Gradle / Ivy

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

import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.stringFormat.StringFormat
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.DataFrame
import jetbrains.datalore.plot.base.Scale
import jetbrains.datalore.plot.base.Transform
import jetbrains.datalore.plot.base.scale.Scales
import jetbrains.datalore.plot.common.data.SeriesUtil.ensureApplicableRange

class ScaleProviderBuilder(private val aes: Aes) {

    private var _mapperProvider: MapperProvider? = null
    private var myName: String? = null
    private var myBreaks: List? = null
    private var myLabels: List? = null
    private var myLabelFormat: String? = null
    private var myMultiplicativeExpand: Double? = null
    private var myAdditiveExpand: Double? = null
    private var myLimits: List<*>? = null
    private var myTransform: Transform? = null

    private var myDiscreteDomain = false
    private var myDiscreteDomainReverse = false

    var mapperProvider: MapperProvider
        get() {
            if (_mapperProvider == null) {
                _mapperProvider = DefaultMapperProvider[aes]
            }
            return _mapperProvider ?: throw AssertionError("Set to null by another thread")
        }
        set(p: MapperProvider) {
            _mapperProvider = p
        }

    fun mapperProvider(mapperProvider: MapperProvider): ScaleProviderBuilder {
        this.mapperProvider = mapperProvider
        return this
    }

    fun name(name: String): ScaleProviderBuilder {
        myName = name
        return this
    }

    fun breaks(breaks: List): ScaleProviderBuilder {
        myBreaks = breaks
        return this
    }

    @Suppress("FunctionName")
    fun minorBreaks_NI(
        @Suppress("UNUSED_PARAMETER") minorBreaks: List
    ): ScaleProviderBuilder {
        // continuous scale
        throw IllegalStateException("Not implemented")
    }

    fun labels(labels: List): ScaleProviderBuilder {
        myLabels = ArrayList(labels)
        return this
    }

    fun labelFormat(format: String?): ScaleProviderBuilder {
        myLabelFormat = format
        return this
    }

    fun multiplicativeExpand(v: Double): ScaleProviderBuilder {
        myMultiplicativeExpand = v
        return this
    }

    fun additiveExpand(v: Double): ScaleProviderBuilder {
        myAdditiveExpand = v
        return this
    }

    fun limits(v: List<*>): ScaleProviderBuilder {
        // Limits for continuous scale : list(min, max)
        // Limits for discrete scale : list ("a", "b", "c")
        myLimits = v
        return this
    }

    @Suppress("FunctionName")
    fun rescaler_NI(
        @Suppress("UNUSED_PARAMETER") v: Any
    ): ScaleProviderBuilder {
        throw IllegalStateException("Not implemented")
    }

    @Suppress("FunctionName")
    fun oob_NI(
        @Suppress("UNUSED_PARAMETER") v: Any
    ): ScaleProviderBuilder {
        throw IllegalStateException("Not implemented")
    }

    fun transform(v: Transform): ScaleProviderBuilder {
        myTransform = v
        return this
    }

    @Suppress("FunctionName")
    fun guide_NI(
        @Suppress("UNUSED_PARAMETER") v: Any
    ): ScaleProviderBuilder {
        // Name of guide object, or object itself.
        throw IllegalStateException("Not implemented")
    }

    fun discreteDomain(b: Boolean): ScaleProviderBuilder {
        myDiscreteDomain = b
        return this
    }

    fun discreteDomainReverse(b: Boolean): ScaleProviderBuilder {
        myDiscreteDomainReverse = b
        return this
    }

    fun build(): ScaleProvider {
        return MyScaleProvider(this)
    }

    private class MyScaleProvider(b: ScaleProviderBuilder) :
        ScaleProvider {

        private val myName: String? = b.myName

        private val myBreaks: List? = b.myBreaks?.let { ArrayList(b.myBreaks!!) }
        private val myLabels: List? = b.myLabels?.let { ArrayList(b.myLabels!!) }
        private val myLabelFormat: String? = b.myLabelFormat
        private val myMultiplicativeExpand: Double? = b.myMultiplicativeExpand
        private val myAdditiveExpand: Double? = b.myAdditiveExpand
        private val myLimits: List<*>? = b.myLimits?.let { ArrayList(b.myLimits!!) }
        private val discreteDomainReverse: Boolean = b.myDiscreteDomainReverse
        private val myContinuousTransform: Transform? = b.myTransform

        private val myAes: Aes = b.aes
        private val mapperProvider: MapperProvider = b.mapperProvider

        override val discreteDomain: Boolean = b.myDiscreteDomain

        private fun scaleName(variable: DataFrame.Variable): String {
            return myName ?: variable.label
        }

        override fun createScale(defaultName: String, discreteDomain: Collection<*>): Scale {
            val name = myName ?: defaultName
            var scale: Scale

            // discrete domain
            var domainValues = discreteDomain.filterNotNull()

            val mapper = if (discreteDomain.isEmpty()) {
                absentMapper(defaultName)
            } else {
                mapperProvider.createDiscreteMapper(domainValues)::apply
            }

            if (discreteDomainReverse) {
                domainValues = domainValues.reversed()
            }

            scale = Scales.discreteDomain(
                name,
                domainValues,
                mapper
            )

            if (myLimits != null) {
                val limits = myLimits.filterNotNull().let { limits ->
                    if (discreteDomainReverse) {
                        limits.reversed()
                    } else {
                        limits
                    }
                }
                scale = scale.with()
                    .limits(limits)
                    .build()
            }

            return completeScale(scale)
        }

        override fun createScale(defaultName: String, continuousDomain: ClosedRange): Scale {
            val name = myName ?: defaultName
            var scale: Scale

            // continuous (numeric) domain
            val dataRange = ensureApplicableRange(continuousDomain)

            var lowerLimit: Double? = null
            var upperLimit: Double? = null
            if (myLimits != null) {
                var lower = true
                for (limit in myLimits) {
                    if (limit is Number) {
                        val v = limit.toDouble()
                        if (v.isFinite()) {
                            if (lower) {
                                lowerLimit = v
                            } else {
                                upperLimit = v
                            }
                        }
                    }
                    lower = false
                }
            }

            val mapper = mapperProvider.createContinuousMapper(
                dataRange,
                lowerLimit,
                upperLimit,
                myContinuousTransform
            )
            val continuousRange = mapper.isContinuous || myAes.isNumeric

            scale = Scales.continuousDomain(name, { v -> mapper.apply(v) }, continuousRange)

            if (mapper is WithGuideBreaks<*>) {
                @Suppress("UNCHECKED_CAST")
                mapper as WithGuideBreaks
                scale = scale.with()
                    .breaks(mapper.breaks)
                    .labelFormatter(mapper.formatter)
                    .build()
            }

            if (myContinuousTransform != null) {
                scale = scale.with()
                    .continuousTransform(myContinuousTransform)
                    .build()
            }

            if (myLimits != null) {
                val with = scale.with()
                if (lowerLimit != null) {
                    with.lowerLimit(lowerLimit)
                }
                if (upperLimit != null) {
                    with.upperLimit(upperLimit)
                }
                scale = with.build()
            }

            return completeScale(scale)
        }


        private fun completeScale(scale: Scale): Scale {
            val with = scale.with()
            if (myBreaks != null) {
                with.breaks(myBreaks)
            }
            if (myLabels != null) {
                with.labels(myLabels)
            }
            if (myLabelFormat != null) {
                with.labelFormatter(StringFormat.create(myLabelFormat)::format)
            }
            if (myMultiplicativeExpand != null) {
                with.multiplicativeExpand(myMultiplicativeExpand)
            }
            if (myAdditiveExpand != null) {
                with.additiveExpand(myAdditiveExpand)
            }
            return with.build()
        }

        private fun absentMapper(`var`: DataFrame.Variable): (Double?) -> T {
            // mapper for empty data is a special case - should never be used
            return { v -> throw IllegalStateException("Mapper for empty data series '" + `var`.name + "' was invoked with arg " + v) }
        }

        private fun absentMapper(label: String): (Double?) -> T {
            // mapper for empty data is a special case - should never be used
            return { v -> throw IllegalStateException("Mapper for empty data series '$label' was invoked with arg " + v) }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy