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

commonMain.jetbrains.datalore.plot.builder.assemble.PlotAssemblerUtil.kt Maven / Gradle / Ivy

There is a newer version: 4.5.3-alpha1
Show newest version
/*
 * 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.builder.assemble

import jetbrains.datalore.base.gcommon.collect.ClosedRange
import jetbrains.datalore.base.gcommon.collect.Iterables
import jetbrains.datalore.base.values.Color
import jetbrains.datalore.plot.base.Aes
import jetbrains.datalore.plot.base.Aesthetics
import jetbrains.datalore.plot.base.Scale
import jetbrains.datalore.plot.base.scale.ScaleUtil
import jetbrains.datalore.plot.builder.GeomLayer
import jetbrains.datalore.plot.builder.PlotUtil
import jetbrains.datalore.plot.builder.PlotUtil.computeLayerDryRunXYRanges
import jetbrains.datalore.plot.builder.VarBinding
import jetbrains.datalore.plot.builder.assemble.PlotGuidesAssemblerUtil.checkFitsColorBar
import jetbrains.datalore.plot.builder.assemble.PlotGuidesAssemblerUtil.createColorBarAssembler
import jetbrains.datalore.plot.builder.assemble.PlotGuidesAssemblerUtil.fitsColorBar
import jetbrains.datalore.plot.builder.assemble.PlotGuidesAssemblerUtil.guideTransformedDomainByAes
import jetbrains.datalore.plot.builder.assemble.PlotGuidesAssemblerUtil.mappedRenderedAesToCreateGuides
import jetbrains.datalore.plot.builder.layout.*
import jetbrains.datalore.plot.builder.theme.FacetsTheme
import jetbrains.datalore.plot.builder.theme.LegendTheme
import jetbrains.datalore.plot.common.data.SeriesUtil

internal object PlotAssemblerUtil {

    private fun updateAesRangeMap(
        aes: Aes<*>,
        range: ClosedRange?,
        rangeByAes: MutableMap, ClosedRange>
    ) {
        @Suppress("NAME_SHADOWING")
        var range = range
        if (range != null) {
            val wasRange = rangeByAes[aes]
            if (wasRange != null) {
                range = wasRange.span(range)
            }
            rangeByAes[aes] = range
        }
    }

    private fun updateRange(range: ClosedRange?, wasRange: ClosedRange?): ClosedRange? {
        @Suppress("NAME_SHADOWING")
        var range = range
        if (range != null) {
            if (wasRange != null) {
                range = wasRange.span(range)
            }
            return range
        }
        return wasRange
    }

    private fun updateRange(values: Iterable, wasRange: ClosedRange?): ClosedRange? {
        if (!Iterables.isEmpty(values)) {
            var newRange = ClosedRange.encloseAll(values)
            if (wasRange != null) {
                newRange = wasRange.span(newRange)
            }
            return newRange
        }
        return wasRange
    }

    fun createLegends(
        layersByPanel: List>,
        guideOptionsMap: Map, GuideOptions>,
        theme: LegendTheme
    ): List {

        // stitch together layers from all panels
        var planeCount = 0
        if (layersByPanel.isNotEmpty()) {
            planeCount = layersByPanel[0].size
        }

        val stitchedLayersList = ArrayList()
        for (i in 0 until planeCount) {
            val layersOnPlane = ArrayList()

            // collect layer[i] chunks from all panels
            for (panelLayers in layersByPanel) {
                layersOnPlane.add(panelLayers[i])
            }

            stitchedLayersList.add(
                StitchedPlotLayers(
                    layersOnPlane
                )
            )
        }

        val transformedDomainByAes = HashMap, ClosedRange>()
        for (stitchedPlotLayers in stitchedLayersList) {
            val layerTransformedDomainByAes = guideTransformedDomainByAes(stitchedPlotLayers, guideOptionsMap)
            for ((aes, transformedDomain) in layerTransformedDomainByAes) {
                updateAesRangeMap(
                    aes,
                    transformedDomain,
                    transformedDomainByAes
                )
            }
        }

        return createLegends(
            stitchedLayersList,
            transformedDomainByAes,
            guideOptionsMap,
            theme
        )
    }

    private fun createLegends(
        stitchedLayersList: List,
        transformedDomainByAes: Map, ClosedRange>,
        guideOptionsMap: Map, GuideOptions>,
        theme: LegendTheme
    ): List {

        val legendAssemblerByTitle = LinkedHashMap()
        val colorBarAssemblerByTitle = LinkedHashMap()

        for (stitchedLayers in stitchedLayersList) {
            val layerConstantByAes = HashMap, Any>()
            for (aes in stitchedLayers.renderedAes()) {
                if (stitchedLayers.hasConstant(aes)) {
                    layerConstantByAes[aes] = stitchedLayers.getConstant(aes)!!
                }
            }

            val layerBindingsByScaleName = LinkedHashMap>()
            val aesList = mappedRenderedAesToCreateGuides(stitchedLayers, guideOptionsMap)
            for (aes in aesList) {
                var colorBar = false
                val binding = stitchedLayers.getBinding(aes)
                val scale = stitchedLayers.getScale(aes)
                val scaleName = scale.name
                if (guideOptionsMap.containsKey(aes)) {
                    val guideOptions = guideOptionsMap[aes]
                    if (guideOptions is ColorBarOptions) {
                        checkFitsColorBar(binding.aes, scale)
                        colorBar = true
                        @Suppress("UNCHECKED_CAST")
                        val colorScale = scale as Scale
                        colorBarAssemblerByTitle[scaleName] = createColorBarAssembler(
                            scaleName, binding.aes,
                            transformedDomainByAes, colorScale, guideOptions, theme
                        )
                    }
                } else if (fitsColorBar(binding.aes, scale)) {
                    colorBar = true
                    @Suppress("UNCHECKED_CAST")
                    val colorScale = scale as Scale
                    colorBarAssemblerByTitle[scaleName] = createColorBarAssembler(
                        scaleName, binding.aes,
                        transformedDomainByAes, colorScale, null, theme
                    )
                }

                if (!colorBar) {
                    layerBindingsByScaleName.getOrPut(scaleName) { ArrayList() }.add(binding)
                }
            }

            for (scaleName in layerBindingsByScaleName.keys) {
                val legendAssembler = legendAssemblerByTitle.getOrPut(scaleName) {
                    LegendAssembler(
                        scaleName,
                        guideOptionsMap,
                        theme
                    )
                }

                val varBindings = layerBindingsByScaleName[scaleName]!!
                val legendKeyFactory = stitchedLayers.legendKeyElementFactory
                val aestheticsDefaults = stitchedLayers.aestheticsDefaults
                legendAssembler.addLayer(
                    legendKeyFactory,
                    varBindings,
                    layerConstantByAes,
                    aestheticsDefaults,
                    stitchedLayers.getScaleMap(),
                    transformedDomainByAes
                )
            }
        }

        val legendBoxInfos = ArrayList()
        for (legendTitle in colorBarAssemblerByTitle.keys) {
            val boxInfo = colorBarAssemblerByTitle[legendTitle]!!.createColorBar()
            if (!boxInfo.isEmpty) {
                legendBoxInfos.add(boxInfo)
            }
        }

        for (legendTitle in legendAssemblerByTitle.keys) {
            val boxInfo = legendAssemblerByTitle[legendTitle]!!.createLegend()
            if (!boxInfo.isEmpty) {
                legendBoxInfos.add(boxInfo)
            }
        }
        return legendBoxInfos
    }

    fun createPlotLayout(tileLayout: TileLayout, facets: PlotFacets, facetsTheme: FacetsTheme): PlotLayout {
        if (!facets.isDefined) {
            return SingleTilePlotLayout(tileLayout)
        }

        return FacetGridPlotLayout(
            facets,
            tileLayout,
            facetsTheme.showStrip()
        )
    }

    fun computePlotDryRunXYRanges(layersByTile: List>): Pair, ClosedRange> {
        // 'dry run' aesthetics use 'identity' mappers for positional aes (because the plot size is not yet determined)
        val dryRunAestheticsByTileLayer = HashMap()
        for (tileLayers in layersByTile) {
            for (layer in tileLayers) {
                val aesthetics = PlotUtil.createLayerDryRunAesthetics(layer)
                dryRunAestheticsByTileLayer[layer] = aesthetics
            }
        }

        // the "scale map" is shared by all layers.
        val layers0 = layersByTile[0]
        val scaleMap = layers0[0].scaleMap
        val xScale = scaleMap[Aes.X]
        val yScale = scaleMap[Aes.Y]
        var xInitialRange: ClosedRange? = RangeUtil.initialRange(xScale)
        var yInitialRange: ClosedRange? = RangeUtil.initialRange(yScale)

        var xRangeOverall: ClosedRange? = null
        var yRangeOverall: ClosedRange? = null
        for (tileLayers in layersByTile) {
            for (layer in tileLayers) {
                // use dry-run aesthetics to estimate ranges
                val aesthetics = dryRunAestheticsByTileLayer.getValue(layer)
                // adjust X/Y range with 'pos adjustment' and 'expands'
                val xyRanges = computeLayerDryRunXYRanges(layer, aesthetics)

                val xRangeLayer = updateRange(xInitialRange, xyRanges.first)
                val yRangeLayer = updateRange(yInitialRange, xyRanges.second)

                xRangeOverall = updateRange(xRangeLayer, xRangeOverall)
                yRangeOverall = updateRange(yRangeLayer, yRangeOverall)
            }
        }

        // 'expand' ranges and include '0' if necessary
        xRangeOverall = RangeUtil.expandRange(xRangeOverall, Aes.X, xScale, layers0)
        yRangeOverall = RangeUtil.expandRange(yRangeOverall, Aes.Y, yScale, layers0)

        // validate XY ranges
        xRangeOverall = SeriesUtil.ensureApplicableRange(xRangeOverall)
        yRangeOverall = SeriesUtil.ensureApplicableRange(yRangeOverall)
        return Pair(
            xRangeOverall,
            yRangeOverall
        )
    }

    private object RangeUtil {
        fun initialRange(scale: Scale): ClosedRange? {
            var initialRange: ClosedRange? = null

            // Take in account:
            // - scales domain if defined
            // - scales breaks if defined
            if (scale.isContinuousDomain) {
                initialRange = updateRange(
                    ScaleUtil.transformedDefinedLimits(scale).toList().filter { it.isFinite() },
                    initialRange
                )
            }

            if (scale.hasBreaks()) {
                val scaleBreaks = scale.getScaleBreaks()
                initialRange = updateRange(
                    scaleBreaks.transformedValues,
                    initialRange
                )
            }
            return initialRange
        }

        fun expandRange(
            range: ClosedRange?,
            aes: Aes,
            scale: Scale<*>,
            layers: List
        ): ClosedRange? {
            val includeZero = layers.any { it.rangeIncludesZero(aes) }

            @Suppress("NAME_SHADOWING")
            val range = when (includeZero) {
                true -> updateRange(ClosedRange.singleton(0.0), range)
                false -> range
            }

            return PlotUtil.rangeWithExpand(range, scale, includeZero)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy