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

commonMain.jetbrains.datalore.plot.config.PlotConfig.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.plot.base.Aes
import jetbrains.datalore.plot.base.DataFrame
import jetbrains.datalore.plot.base.GeomKind
import jetbrains.datalore.plot.base.Transform
import jetbrains.datalore.plot.base.data.DataFrameUtil
import jetbrains.datalore.plot.builder.assemble.PlotFacets
import jetbrains.datalore.plot.builder.data.OrderOptionUtil
import jetbrains.datalore.plot.builder.scale.MapperProvider
import jetbrains.datalore.plot.builder.scale.ScaleProvider
import jetbrains.datalore.plot.config.Option.Meta
import jetbrains.datalore.plot.config.Option.Meta.DATA_META
import jetbrains.datalore.plot.config.Option.Meta.Kind
import jetbrains.datalore.plot.config.Option.Plot.CAPTION
import jetbrains.datalore.plot.config.Option.Plot.CAPTION_TEXT
import jetbrains.datalore.plot.config.Option.Plot.FACET
import jetbrains.datalore.plot.config.Option.Plot.LAYERS
import jetbrains.datalore.plot.config.Option.Plot.SCALES
import jetbrains.datalore.plot.config.Option.Plot.SUBTITLE_TEXT
import jetbrains.datalore.plot.config.Option.Plot.TITLE
import jetbrains.datalore.plot.config.Option.Plot.TITLE_TEXT
import jetbrains.datalore.plot.config.Option.PlotBase.DATA
import jetbrains.datalore.plot.config.Option.PlotBase.MAPPING

abstract class PlotConfig(
    opts: Map,
    private val isClientSide: Boolean
) : OptionsAccessor(opts, DEF_OPTIONS) {

    val layerConfigs: List
    val facets: PlotFacets

    protected val scaleConfigs: List>
    protected val mapperProviderByAes: Map, MapperProvider<*>>
    protected val scaleProviderByAes: Map, ScaleProvider<*>>
    protected val transformByAes: Map, Transform>

    protected var sharedData: DataFrame
        private set

    val title: String?
        get() = getMap(TITLE)[TITLE_TEXT] as String?
    val subtitle: String?
        get() = getMap(TITLE)[SUBTITLE_TEXT] as String?
    val caption: String?
        get() = getMap(CAPTION)[CAPTION_TEXT] as String?

    val containsLiveMap: Boolean
        get() = layerConfigs.any(LayerConfig::isLiveMap)

    init {

        val (plotMappings, plotData) = DataMetaUtil.createDataFrame(
            options = this,
            commonData = DataFrame.Builder.emptyFrame(),
            commonDiscreteAes = emptySet(),
            commonMappings = emptyMap(),
            isClientSide = isClientSide
        )

        sharedData = plotData

        if (!isClientSide) {
            update(MAPPING, plotMappings)
        }

        layerConfigs = createLayerConfigs(sharedData)

        // build all scales
        val excludeStatVariables = !isClientSide

        scaleConfigs = PlotConfigUtil.createScaleConfigs(getList(SCALES) + DataMetaUtil.createScaleSpecs(opts))

        mapperProviderByAes = PlotConfigMapperProviders.createMapperProviders(
            layerConfigs,
            scaleConfigs,
            excludeStatVariables
        )

        scaleProviderByAes = PlotConfigScaleProviders.createScaleProviders(
            layerConfigs,
            scaleConfigs,
            excludeStatVariables
        )

        transformByAes = PlotConfigTransforms.createTransforms(
            layerConfigs,
            scaleProviderByAes,
            mapperProviderByAes,
            excludeStatVariables
        )

        facets = if (has(FACET)) {
            val facetOptions = getMap(FACET)
            val facetConfig = FacetConfig(facetOptions)
            val dataByLayer = ArrayList()
            for (layerConfig in layerConfigs) {
                dataByLayer.add(layerConfig.combinedData)
            }
            facetConfig.createFacets(dataByLayer)
        } else {
            PlotFacets.undefined()
        }
    }

    private fun createLayerConfigs(sharedData: DataFrame): List {

        val layerConfigs = ArrayList()
        val layerOptionsList = getList(LAYERS)

        val isMapPlot = layerOptionsList
            .mapNotNull { layerOptions -> (layerOptions as? Map<*, *>)?.getString(Option.Layer.GEOM) }
            .map(Option.GeomName::toGeomKind)
            .any { it in listOf(GeomKind.LIVE_MAP, GeomKind.MAP) }

        for (layerOptions in layerOptionsList) {
            require(layerOptions is Map<*, *>) { "Layer options: expected Map but was ${layerOptions!!::class.simpleName}" }
            @Suppress("UNCHECKED_CAST")
            layerOptions as Map

            val layerConfig = createLayerConfig(
                layerOptions,
                sharedData,
                getMap(MAPPING),
                getMap(DATA_META),
                DataMetaUtil.getOrderOptions(this.mergedOptions, getMap(MAPPING)),
                isMapPlot
            )
            layerConfigs.add(layerConfig)
        }
        return layerConfigs
    }

    protected abstract fun createLayerConfig(
        layerOptions: Map,
        sharedData: DataFrame,
        plotMappings: Map<*, *>,
        plotDataMeta: Map<*, *>,
        plotOrderOptions: List,
        isMapPlot: Boolean
    ): LayerConfig


    protected fun replaceSharedData(plotData: DataFrame) {
        check(!isClientSide)   // This class is immutable on client-side
        sharedData = plotData
        update(DATA, DataFrameUtil.toMap(plotData))
    }

    companion object {
        private const val ERROR_MESSAGE = "__error_message"
        private val DEF_OPTIONS: Map = emptyMap()
        internal const val PLOT_COMPUTATION_MESSAGES = "computation_messages"

        fun failure(message: String): Map {
            return mapOf(ERROR_MESSAGE to message)
        }

        fun assertPlotSpecOrErrorMessage(opts: Map) {
            val identified = isFailure(opts) ||
                    isPlotSpec(opts) ||
                    isGGBunchSpec(opts)

            if (!identified) {
                throw IllegalArgumentException("Invalid root feature kind: absent or unsupported  `kind` key")
            }
        }

        fun assertPlotSpec(opts: Map) {
            val identified = isPlotSpec(opts) || isGGBunchSpec(opts)
            if (!identified) {
                throw IllegalArgumentException("Invalid root feature kind: absent or unsupported  `kind` key")
            }
        }

        fun isFailure(opts: Map): Boolean {
            return opts.containsKey(ERROR_MESSAGE)
        }

        fun getErrorMessage(opts: Map): String {
            return opts[ERROR_MESSAGE].toString()
        }

        fun isPlotSpec(opts: Map<*, *>): Boolean {
            return Kind.PLOT == specKind(opts)
        }

        fun isGGBunchSpec(opts: Map<*, *>): Boolean {
            return Kind.GG_BUNCH == specKind(opts)
        }

        fun specKind(opts: Map<*, *>): Any? {
            return opts[Meta.KIND]
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy