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

commonMain.jetbrains.datalore.plot.config.PlotConfigUtil.kt Maven / Gradle / Ivy

/*
 * 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.config

import jetbrains.datalore.base.interval.DoubleSpan
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.ContinuousTransform
import jetbrains.datalore.plot.base.DataFrame
import jetbrains.datalore.plot.builder.VarBinding
import jetbrains.datalore.plot.builder.assemble.PlotFacets
import jetbrains.datalore.plot.common.data.SeriesUtil
import jetbrains.datalore.plot.config.PlotConfig.Companion.PLOT_COMPUTATION_MESSAGES

object PlotConfigUtil {

    fun toLayersDataByTile(dataByLayer: List, facets: PlotFacets): List> {
        // Plot consists of one or more tiles,
        // each tile consists of layers

        val layersDataByTile: List> = if (facets.isDefined) {
            List(facets.numTiles) { ArrayList() }
        } else {
            // Just one tile.
            listOf(ArrayList())
        }

        for (layerData in dataByLayer) {
            if (facets.isDefined) {
                val dataByTile = facets.dataByTile(layerData)
                for ((tileIndex, tileData) in dataByTile.withIndex()) {
                    layersDataByTile[tileIndex].add(tileData)
                }
            } else {
                layersDataByTile[0].add(layerData)
            }
        }
        return layersDataByTile
    }

    // backend
    fun addComputationMessage(accessor: OptionsAccessor, message: String?) {
        require(message != null)
        val computationMessages = ArrayList(
            getComputationMessages(
                accessor
            )
        )
        computationMessages.add(message)
        accessor.update(PLOT_COMPUTATION_MESSAGES, computationMessages)
    }

    // frontend
    fun findComputationMessages(spec: Map): List {
        val result: List = when {
            PlotConfig.isPlotSpec(spec) -> getComputationMessages(spec)
            PlotConfig.isGGBunchSpec(spec) -> {
                val bunchConfig = BunchConfig(spec)
                bunchConfig.bunchItems.flatMap { getComputationMessages(it.featureSpec) }
            }
            else -> throw RuntimeException("Unexpected plot spec kind: ${PlotConfig.specKind(spec)}")
        }

        return result.distinct()
    }

    private fun getComputationMessages(opts: Map): List {
        return getComputationMessages(OptionsAccessor(opts))
    }

    private fun getComputationMessages(accessor: OptionsAccessor): List {
        return accessor.getList(PLOT_COMPUTATION_MESSAGES).map { it as String }
    }

    internal fun createPlotAesBindingSetup(
        layerConfigs: List,
        excludeStatVariables: Boolean
    ): PlotAesBindingSetup {

        val dataByVarBinding = associateVarBindingsWithData(
            layerConfigs, excludeStatVariables
        )

        val varBindings = getVarBindings(layerConfigs, excludeStatVariables)
        val variablesByMappedAes = associateAesWithMappedVariables(
            varBindings
        )

        return PlotAesBindingSetup(
            varBindings = varBindings,
            dataByVarBinding = dataByVarBinding,
            variablesByMappedAes = variablesByMappedAes,
        )
    }

    private fun getVarBindings(
        layerConfigs: List, excludeStatVariables: Boolean
    ): List {
        return layerConfigs.flatMap { it.varBindings }.filter { !(excludeStatVariables && it.variable.isStat) }
    }

    private fun associateAesWithMappedVariables(varBindings: List): Map, List> {
        val variablesByMappedAes: MutableMap, MutableList> = HashMap()
        for (varBinding in varBindings) {
            val aes = varBinding.aes
            val variable = varBinding.variable
            variablesByMappedAes.getOrPut(aes) { ArrayList() }.add(variable)
        }
        return variablesByMappedAes
    }

    private fun associateVarBindingsWithData(
        layerConfigs: List, excludeStatVariables: Boolean
    ): Map {
        val dataByVarBinding: Map = layerConfigs.flatMap { layer ->
            layer.varBindings.filter { !(excludeStatVariables && it.variable.isStat) }
                .map { it to layer.combinedData }
        }.toMap()

        // Check that all variables in bindings are mapped to data.
        for ((varBinding, data) in dataByVarBinding) {
            val variable = varBinding.variable
            data.assertDefined(variable)
        }

        return dataByVarBinding
    }

    internal fun defaultScaleName(aes: Aes<*>, variablesByMappedAes: Map, List>): String {
        return if (variablesByMappedAes.containsKey(aes)) {
            val variables = variablesByMappedAes.getValue(aes)
            val labels = variables.map(DataFrame.Variable::label).distinct()
            if (labels.size > 1 && (aes == Aes.X || aes == Aes.Y)) {
                // Don't show multiple labels on X,Y axis.
                aes.name
            } else {
                labels.joinToString()
            }
        } else {
            aes.name
        }
    }

    /**
     * Front-end.
     * ToDo: 'domans' should be computed on 'transformed' data.
     */
    internal fun computeContinuousDomain(
        data: DataFrame,
        variable: DataFrame.Variable,
        transform: ContinuousTransform
    ): DoubleSpan? {
        return if (!transform.hasDomainLimits()) {
            data.range(variable)
        } else {
            val filtered = data.getNumeric(variable).filter {
                transform.isInDomain(it)
            }
            SeriesUtil.range(filtered)
        }
    }

    internal fun createScaleConfigs(scaleOptionsList: List<*>): List> {
        // merge options by 'aes'
        val mergedOpts = HashMap, MutableMap>()
        for (opts in scaleOptionsList) {
            @Suppress("UNCHECKED_CAST")
            val optsMap = opts as Map

            @Suppress("UNCHECKED_CAST")
            val aes = ScaleConfig.aesOrFail(optsMap) as Aes
            if (!mergedOpts.containsKey(aes)) {
                mergedOpts[aes] = HashMap()
            }

            mergedOpts[aes]!!.putAll(optsMap)
        }

        return mergedOpts.map { (aes, options) ->
            ScaleConfig(aes, options)
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy