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

commonMain.jetbrains.datalore.plot.base.aes.AestheticsBuilder.kt Maven / Gradle / Ivy

/*
 * 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.base.aes

import jetbrains.datalore.base.function.Function
import jetbrains.datalore.base.gcommon.base.Preconditions.checkArgument
import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.gcommon.collect.Iterables
import jetbrains.datalore.base.gcommon.collect.Sets
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.Aes.Companion.ALPHA
import jetbrains.datalore.plot.base.Aes.Companion.ANGLE
import jetbrains.datalore.plot.base.Aes.Companion.COLOR
import jetbrains.datalore.plot.base.Aes.Companion.FAMILY
import jetbrains.datalore.plot.base.Aes.Companion.FILL
import jetbrains.datalore.plot.base.Aes.Companion.FLOW
import jetbrains.datalore.plot.base.Aes.Companion.FONTFACE
import jetbrains.datalore.plot.base.Aes.Companion.FRAME
import jetbrains.datalore.plot.base.Aes.Companion.HEIGHT
import jetbrains.datalore.plot.base.Aes.Companion.HJUST
import jetbrains.datalore.plot.base.Aes.Companion.INTERCEPT
import jetbrains.datalore.plot.base.Aes.Companion.LABEL
import jetbrains.datalore.plot.base.Aes.Companion.LINETYPE
import jetbrains.datalore.plot.base.Aes.Companion.LOWER
import jetbrains.datalore.plot.base.Aes.Companion.MIDDLE
import jetbrains.datalore.plot.base.Aes.Companion.SHAPE
import jetbrains.datalore.plot.base.Aes.Companion.SIZE
import jetbrains.datalore.plot.base.Aes.Companion.SLOPE
import jetbrains.datalore.plot.base.Aes.Companion.SPEED
import jetbrains.datalore.plot.base.Aes.Companion.SYM_X
import jetbrains.datalore.plot.base.Aes.Companion.SYM_Y
import jetbrains.datalore.plot.base.Aes.Companion.UPPER
import jetbrains.datalore.plot.base.Aes.Companion.VJUST
import jetbrains.datalore.plot.base.Aes.Companion.WEIGHT
import jetbrains.datalore.plot.base.Aes.Companion.WIDTH
import jetbrains.datalore.plot.base.Aes.Companion.X
import jetbrains.datalore.plot.base.Aes.Companion.XEND
import jetbrains.datalore.plot.base.Aes.Companion.XINTERCEPT
import jetbrains.datalore.plot.base.Aes.Companion.XMAX
import jetbrains.datalore.plot.base.Aes.Companion.XMIN
import jetbrains.datalore.plot.base.Aes.Companion.Y
import jetbrains.datalore.plot.base.Aes.Companion.YEND
import jetbrains.datalore.plot.base.Aes.Companion.YINTERCEPT
import jetbrains.datalore.plot.base.Aes.Companion.YMAX
import jetbrains.datalore.plot.base.Aes.Companion.YMIN
import jetbrains.datalore.plot.base.Aes.Companion.Z
import jetbrains.datalore.plot.base.Aesthetics
import jetbrains.datalore.plot.base.DataPointAesthetics
import jetbrains.datalore.plot.base.render.linetype.LineType
import jetbrains.datalore.plot.base.render.point.PointShape
import jetbrains.datalore.plot.common.data.SeriesUtil
import kotlin.jvm.JvmOverloads

class AestheticsBuilder @JvmOverloads constructor(private var myDataPointCount: Int = 0) {

    private val myIndexFunctionMap: MutableMap, (Int) -> Any?>
    private var myGroup = constant(0)
    private val myConstantAes = Sets.newHashSet(Aes.values())  // initially contains all Aes;
    private val myOverallRangeByNumericAes = HashMap, ClosedRange>()

    init {
        myIndexFunctionMap = HashMap()
        for (aes in Aes.values()) {
            // Safe cast because AesInitValue.get(aes) is guaranteed to return correct type.
            myIndexFunctionMap[aes] =
                constant(
                    AesInitValue[aes]
                )
        }
    }

    fun dataPointCount(v: Int): AestheticsBuilder {
        myDataPointCount = v
        return this
    }

    fun overallRange(aes: Aes, range: ClosedRange): AestheticsBuilder {
        // It's full range (length px) of x/y axis
        myOverallRangeByNumericAes[aes] = range
        return this
    }

    fun x(v: (Int) -> Double?): AestheticsBuilder {
        return aes(X, v)
    }

    fun y(v: (Int) -> Double?): AestheticsBuilder {
        return aes(Y, v)
    }

    fun color(v: (Int) -> Color?): AestheticsBuilder {
        return aes(COLOR, v)
    }

    fun fill(v: (Int) -> Color?): AestheticsBuilder {
        return aes(FILL, v)
    }

    fun alpha(v: (Int) -> Double?): AestheticsBuilder {
        return aes(ALPHA, v)
    }

    fun shape(v: (Int) -> PointShape?): AestheticsBuilder {
        return aes(SHAPE, v)
    }

    fun lineType(v: (Int) -> LineType?): AestheticsBuilder {
        return aes(LINETYPE, v)
    }

    fun size(v: (Int) -> Double?): AestheticsBuilder {
        return aes(SIZE, v)
    }

    fun width(v: (Int) -> Double?): AestheticsBuilder {
        return aes(WIDTH, v)
    }

    fun weight(v: (Int) -> Double?): AestheticsBuilder {
        return aes(WEIGHT, v)
    }

    fun frame(v: (Int) -> String?): AestheticsBuilder {
        return aes(FRAME, v)
    }

    fun speed(v: (Int) -> Double?): AestheticsBuilder {
        return aes(SPEED, v)
    }

    fun flow(v: (Int) -> Double?): AestheticsBuilder {
        return aes(FLOW, v)
    }

    fun group(v: (Int) -> Int): AestheticsBuilder {
        myGroup = v
        return this
    }

    fun label(v: (Int) -> Any?): AestheticsBuilder {
        return aes(LABEL, v)
    }

    fun family(v: (Int) -> String?): AestheticsBuilder {
        return aes(FAMILY, v)
    }

    fun fontface(v: (Int) -> String?): AestheticsBuilder {
        return aes(FONTFACE, v)
    }

    fun hjust(v: (Int) -> Any?): AestheticsBuilder {
        return aes(HJUST, v)
    }

    fun vjust(v: (Int) -> Any?): AestheticsBuilder {
        return aes(VJUST, v)
    }

    fun angle(v: (Int) -> Double?): AestheticsBuilder {
        return aes(ANGLE, v)
    }

    fun xmin(v: (Int) -> Double?): AestheticsBuilder {
        return aes(XMIN, v)
    }

    fun xmax(v: (Int) -> Double?): AestheticsBuilder {
        return aes(XMAX, v)
    }

    fun ymin(v: (Int) -> Double?): AestheticsBuilder {
        return aes(YMIN, v)
    }

    fun ymax(v: (Int) -> Double?): AestheticsBuilder {
        return aes(YMAX, v)
    }

    fun symX(v: (Int) -> Double?): AestheticsBuilder {
        return aes(SYM_X, v)
    }

    fun symY(v: (Int) -> Double?): AestheticsBuilder {
        return aes(SYM_Y, v)
    }

    fun  constantAes(aes: Aes, v: T): AestheticsBuilder {
        myConstantAes.add(aes)
        myIndexFunctionMap[aes] = constant(v)
        return this
    }

    fun  aes(aes: Aes, v: (Int) -> T?): AestheticsBuilder {
        myConstantAes.remove(aes)
        myIndexFunctionMap[aes] = v
        return this
    }

    fun build(): Aesthetics {
        return MyAesthetics(this)
    }


    private class MyAesthetics internal constructor(b: AestheticsBuilder) : Aesthetics {
        private val myDataPointCount: Int = b.myDataPointCount
        private val myIndexFunctionMap =
            TypedIndexFunctionMap(b.myIndexFunctionMap)
        val group = b.myGroup
        private val myConstantAes: Set>
        private val myOverallRangeByNumericAes: Map, ClosedRange>

        private val myResolutionByAes = HashMap, Double>()
        private val myRangeByNumericAes = HashMap, ClosedRange?>()

        override val isEmpty: Boolean
            get() = myDataPointCount == 0

        init {
            myConstantAes = HashSet(b.myConstantAes)
            myOverallRangeByNumericAes = HashMap(b.myOverallRangeByNumericAes)
        }

        fun  aes(aes: Aes): (Int) -> T {
            return myIndexFunctionMap[aes]
        }

        override fun dataPointAt(index: Int): DataPointAesthetics {
            return MyDataPointAesthetics(
                index,
                this
            )
        }

        override fun dataPointCount(): Int {
            return myDataPointCount
        }

        override fun dataPoints(): Iterable {
            val self = this
            return object : Iterable {
                override fun iterator(): Iterator =
                    MyDataPointsIterator(
                        myDataPointCount,
                        self
                    )
            }
        }

        override fun range(aes: Aes): ClosedRange? {
            if (!myRangeByNumericAes.containsKey(aes)) {
                val r = when {
                    myDataPointCount <= 0 ->
                        ClosedRange(0.0, 0.0)
                    myConstantAes.contains(aes) -> {
                        // constant should not be null
                        val v = numericValues(aes).iterator().next()!!
                        if (v.isFinite()) {
                            ClosedRange(v, v)
                        } else null
                    }
                    else -> {
                        val values = numericValues(aes)
                        SeriesUtil.range(values)
                    }
                }
                myRangeByNumericAes[aes] = r
            }

            return myRangeByNumericAes[aes]
        }

        override fun overallRange(aes: Aes): ClosedRange {
            return myOverallRangeByNumericAes[aes] ?: error("Overall range is unknown for $aes")
        }

        override fun resolution(aes: Aes, naValue: Double): Double {
            if (!myResolutionByAes.containsKey(aes)) {
                val resolution: Double =
                    when {
                        myConstantAes.contains(aes) -> 0.0
                        else -> {
                            val values = numericValues(aes)
                            SeriesUtil.resolution(values, naValue)
                        }
                    }
                myResolutionByAes[aes] = resolution
            }

            return myResolutionByAes[aes]!!
        }

        override fun numericValues(aes: Aes): Iterable {
            checkArgument(aes.isNumeric, "Numeric aes is expected: $aes")
            return object : Iterable {
                override fun iterator(): Iterator {
                    return AesIterator(
                        myDataPointCount,
                        aes(aes)
                    )
                }
            }
        }

        override fun groups(): Iterable {
            return object : Iterable {
                override fun iterator(): Iterator {
                    return AesIterator(
                        myDataPointCount,
                        group
                    )
                }
            }
        }
    }

    private class MyDataPointsIterator internal constructor(
        private val myLength: Int,
        private val myAesthetics: MyAesthetics
    ) : Iterator {
        private var myIndex = 0

        override fun hasNext(): Boolean {
            return myIndex < myLength
        }

        override fun next(): DataPointAesthetics {
            if (hasNext()) {
                return myAesthetics.dataPointAt(myIndex++)
            }
            throw NoSuchElementException("index=$myIndex")
        }
    }

    private class AesIterator internal constructor(private val myLength: Int, private val myAes: (Int) -> T) :
        Iterator {
        private var myIndex = 0

        override fun hasNext(): Boolean {
            return myIndex < myLength
        }

        override fun next(): T {
            if (hasNext()) {
                return myAes(myIndex++)
            }
            throw NoSuchElementException("index=$myIndex")
        }
    }


    private class MyDataPointAesthetics(
        private val myIndex: Int?,
        private val myAesthetics: MyAesthetics
    ) : DataPointAesthetics {

        override fun index(): Int {
            return myIndex!!
        }

        override fun x(): Double? {
            return get(X)
        }

        override fun y(): Double? {
            return get(Y)
        }

        override fun z(): Double? {
            return get(Z)
        }

        override fun ymin(): Double? {
            return get(YMIN)
        }

        override fun ymax(): Double? {
            return get(YMAX)
        }

        override fun color(): Color {
            return get(COLOR)
        }

        override fun fill(): Color {
            return get(FILL)
        }

        override fun alpha(): Double? {
            return get(ALPHA)
        }

        override fun shape(): PointShape {
            return get(SHAPE)
        }

        override fun lineType(): LineType {
            return get(LINETYPE)
        }

        override fun size(): Double? {
            return get(SIZE)
        }

        override fun width(): Double? {
            return get(WIDTH)
        }

        override fun height(): Double? {
            return get(HEIGHT)
        }

        override fun weight(): Double? {
            return get(WEIGHT)
        }

        override fun intercept(): Double? {
            return get(INTERCEPT)
        }

        override fun slope(): Double? {
            return get(SLOPE)
        }

        override fun interceptX(): Double? {
            return get(XINTERCEPT)
        }

        override fun interceptY(): Double? {
            return get(YINTERCEPT)
        }

        override fun lower(): Double? {
            return get(LOWER)
        }

        override fun middle(): Double? {
            return get(MIDDLE)
        }

        override fun upper(): Double? {
            return get(UPPER)
        }

        override fun frame(): String {
            return get(FRAME)
        }

        override fun speed(): Double? {
            return get(SPEED)
        }

        override fun flow(): Double? {
            return get(FLOW)
        }

        override fun xmin(): Double? {
            return get(XMIN)
        }

        override fun xmax(): Double? {
            return get(XMAX)
        }

        override fun xend(): Double? {
            return get(XEND)
        }

        override fun yend(): Double? {
            return get(YEND)
        }

        override fun label(): Any? {
            return get(LABEL)
        }

        override fun family(): String {
            return get(FAMILY)
        }

        override fun fontface(): String {
            return get(FONTFACE)
        }

        override fun hjust(): Any {
            return get(HJUST)
        }

        override fun vjust(): Any {
            return get(VJUST)
        }

        override fun angle(): Double? {
            return get(ANGLE)
        }

        override fun symX(): Double? {
            return get(SYM_X)
        }

        override fun symY(): Double? {
            return get(SYM_Y)
        }

        override fun group(): Int? {
            return myAesthetics.group(myIndex!!)
        }

        override fun numeric(aes: Aes): Double? {
            return get(aes)
        }

        override fun  get(aes: Aes): T {
            return myAesthetics.aes(aes)(myIndex!!)
        }
    }


    private class ArrayAes internal constructor(private val myVal: Array) : Function {
        override fun apply(value: Int): ValueT {
            return myVal[value]
        }
    }

    private class MapperAes internal constructor(
        private val myL: List,
        private val myF: ((Double) -> ValueT)
    ) : Function {
        override fun apply(value: Int): ValueT {
            return myF(myL[value])
        }
    }

    companion object {
        fun  constant(v: T): (Int) -> T = { v }

        fun  array(v: Array): (Int) -> T {
            return { value -> v[value] }
        }

//        fun  array(vararg v: T): (Int) -> T {
//            return { value -> v[value] }
//        }

        fun  collection(v: Collection): (Int) -> T {
            return { value -> Iterables[v, value] }
        }

        fun  listMapper(v: List, f: (Double?) -> T?): (Int) -> T? {
            return { value -> f(v[value]) }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy