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

commonMain.jetbrains.datalore.plot.config.FacetConfig.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.stringFormat.StringFormat
import jetbrains.datalore.plot.base.DataFrame
import jetbrains.datalore.plot.base.data.DataFrameUtil
import jetbrains.datalore.plot.builder.assemble.PlotFacets
import jetbrains.datalore.plot.builder.assemble.PlotFacets.Companion.DEF_FORMATTER
import jetbrains.datalore.plot.builder.assemble.PlotFacets.Companion.DEF_ORDER_DIR
import jetbrains.datalore.plot.builder.assemble.facet.FacetGrid
import jetbrains.datalore.plot.builder.assemble.facet.FacetScales
import jetbrains.datalore.plot.builder.assemble.facet.FacetWrap
import jetbrains.datalore.plot.config.Option.Facet
import jetbrains.datalore.plot.config.Option.Facet.FACETS_FILL_DIR
import jetbrains.datalore.plot.config.Option.Facet.X_FORMAT
import jetbrains.datalore.plot.config.Option.Facet.X_ORDER
import jetbrains.datalore.plot.config.Option.Facet.Y_FORMAT
import jetbrains.datalore.plot.config.Option.Facet.Y_ORDER

internal class FacetConfig(options: Map) : OptionsAccessor(options) {

    fun createFacets(dataByLayer: List): PlotFacets {
        return when (val name = getStringSafe(Facet.NAME)) {
            Facet.NAME_GRID -> createGrid(dataByLayer)
            Facet.NAME_WRAP -> createWrap(dataByLayer)
            else -> throw IllegalArgumentException("Facet 'grid' or 'wrap' expected but was: `$name`")
        }
    }

    private fun createGrid(
        dataByLayer: List
    ): PlotFacets {
        var nameX: String? = null
        val levelsX = LinkedHashSet()
        if (has(Facet.X)) {
            nameX = getStringSafe(Facet.X)
            for (data in dataByLayer) {
                if (DataFrameUtil.hasVariable(data, nameX)) {
                    val variable = DataFrameUtil.findVariableOrFail(data, nameX)
                    levelsX.addAll(data.distinctValues(variable))
                }
            }
        }

        var nameY: String? = null
        val levelsY = LinkedHashSet()
        if (has(Facet.Y)) {
            nameY = getStringSafe(Facet.Y)
            for (data in dataByLayer) {
                if (DataFrameUtil.hasVariable(data, nameY)) {
                    val variable = DataFrameUtil.findVariableOrFail(data, nameY)
                    levelsY.addAll(data.distinctValues(variable))
                }
            }
        }

        val scales: FacetScales = getScalesOption()

        return FacetGrid(
            nameX, nameY, ArrayList(levelsX), ArrayList(levelsY),
            getOrderOption(X_ORDER),
            getOrderOption(Y_ORDER),
            getFormatterOption(X_FORMAT),
            getFormatterOption(Y_FORMAT),
            scales
        )
    }

    private fun createWrap(
        dataByLayer: List
    ): PlotFacets {
        // 'facets' cal be just one name or a list of names.
        val facets = getAsStringList(Facet.FACETS)

        val ncol = getInteger(Facet.NCOL)
        val nrow = getInteger(Facet.NROW)

        val facetLevels = ArrayList>()
        for (name in facets) {
            val levels = HashSet()
            for (data in dataByLayer) {
                if (DataFrameUtil.hasVariable(data, name)) {
                    val variable = DataFrameUtil.findVariableOrFail(data, name)
                    levels.addAll(data.get(variable).filterNotNull())
                }
            }
            facetLevels.add(levels.toList())
        }

        // facet ordering
        val orderOption = getAsList(Facet.FACETS_ORDER).map {
            toOrderVal(it)
        }
        // Num of order values must be same as num of factes.
        val ordering = (orderOption + List(facets.size) { DEF_ORDER_DIR }).take(facets.size)

        // facet formatting
        val formatterOption = getAsList(Facet.FACETS_FORMAT).map {
            toFormatterVal(it)
        }
        // Num of formatters must be same as num of factes.
        val formatters = (formatterOption + List(facets.size) { DEF_FORMATTER }).take(facets.size)

        val scales: FacetScales = getScalesOption()

        return FacetWrap(facets, facetLevels, nrow, ncol, getDirOption(), ordering, formatters, scales)
    }


    private fun getOrderOption(optionName: String): Int {
        return toOrderVal(get(optionName))
    }

    private fun toOrderVal(optionVal: Any?): Int {
        return when (optionVal) {
            null -> DEF_ORDER_DIR
            is Number -> optionVal.toInt()
            else -> throw IllegalArgumentException(
                "Unsupported `order` value: $optionVal.\n" +
                        "Use: 1 (natural), -1 (descending) or 0 (no ordering)."
            )
        }
    }

    private fun getDirOption(): FacetWrap.Direction {
        return when (val opt = get(FACETS_FILL_DIR)) {
            null -> FacetWrap.Direction.H
            else -> when (opt.toString().uppercase()) {
                "V" -> FacetWrap.Direction.V
                "H" -> FacetWrap.Direction.H
                else -> throw IllegalArgumentException(
                    "Unsupported `dir` value: $opt.\n" +
                            "Use: 'H' (horizontal) or 'V' (vertical)."
                )
            }
        }
    }

    private fun toFormatterVal(optionVal: Any?): (Any) -> String {
        return when (optionVal) {
            null -> DEF_FORMATTER
            else -> {
                val fmt = StringFormat.forOneArg(optionVal.toString())
                return { value: Any -> fmt.format(value) }
            }
        }
    }

    private fun getFormatterOption(optionName: String): (Any) -> String {
        return toFormatterVal(get(optionName))
    }

    private fun getScalesOption(): FacetScales {
        return getString(Facet.SCALES)?.let {
            when (it.lowercase()) {
                Facet.SCALES_FIXED -> FacetScales.FIXED
                Facet.SCALES_FREE -> FacetScales.FREE
                Facet.SCALES_FREE_X -> FacetScales.FREE_X
                Facet.SCALES_FREE_Y -> FacetScales.FREE_Y
                else -> throw IllegalArgumentException(
                    "Unsupported `scales` value: $it.\n" +
                            "Use: ${Facet.SCALES_FIXED}, ${Facet.SCALES_FREE}, " +
                            "${Facet.SCALES_FREE_X} or ${Facet.SCALES_FREE_Y}"
                )
            }
        } ?: FacetScales.FIXED
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy