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

commonMain.jetbrains.datalore.plot.MonolithicCommon.kt Maven / Gradle / Ivy

There is a newer version: 3.2.0
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

import jetbrains.datalore.base.geometry.DoubleRectangle
import jetbrains.datalore.base.geometry.DoubleVector
import jetbrains.datalore.plot.builder.FigureBuildInfo
import jetbrains.datalore.plot.builder.assemble.PlotAssembler
import jetbrains.datalore.plot.builder.presentation.Defaults
import jetbrains.datalore.plot.config.*
import jetbrains.datalore.plot.server.config.BackendSpecTransformUtil
import jetbrains.datalore.vis.svgToString.SvgToString
import kotlin.math.max

object MonolithicCommon {

    /**
     * Static SVG export
     */
    fun buildSvgImagesFromRawSpecs(
        plotSpec: MutableMap,
        plotSize: DoubleVector?,
        svgToString: SvgToString,
        computationMessagesHandler: ((List) -> Unit)
    ): List {
        @Suppress("NAME_SHADOWING")
        val plotSpec = processRawSpecs(plotSpec, frontendOnly = false)
        val buildResult = buildPlotsFromProcessedSpecs(plotSpec, plotSize)
        if (buildResult.isError) {
            val errorMessage = (buildResult as PlotsBuildResult.Error).error
            throw RuntimeException(errorMessage)
        }

        val success = buildResult as PlotsBuildResult.Success
        val computationMessages = success.buildInfos.flatMap { it.computationMessages }
        if (computationMessages.isNotEmpty()) {
            computationMessagesHandler(computationMessages)
        }

        return success.buildInfos.map { buildInfo ->
            FigureToPlainSvg(buildInfo).eval()
        }.map { svgToString.render(it) }
    }


    fun buildPlotsFromProcessedSpecs(
        plotSpec: MutableMap,
        plotSize: DoubleVector?,
        plotMaxWidth: Double? = null,
        plotPreferredWidth: Double? = null
    ): PlotsBuildResult {
        throwTestingErrors()  // noop

        @Suppress("NAME_SHADOWING")
        val plotSize = plotSize?.let {
            // Fix error (Batik):
            //  org.apache.batik.bridge.BridgeException: null:-1
            //  The attribute "height" of the element  cannot be negative
            DoubleVector(
                max(0.0, it.x),
                max(0.0, it.y)
            )
        }

        PlotConfig.assertFigSpecOrErrorMessage(plotSpec)
        if (PlotConfig.isFailure(plotSpec)) {
            val errorMessage = PlotConfig.getErrorMessage(plotSpec)
            return PlotsBuildResult.Error(errorMessage)
        }

        return when (PlotConfig.figSpecKind(plotSpec)) {
            FigKind.PLOT_SPEC -> PlotsBuildResult.Success(
                listOf(
                    buildSinglePlotFromProcessedSpecs(
                        plotSpec,
                        plotSize,
                        plotMaxWidth,
                        plotPreferredWidth
                    )
                )
            )

            FigKind.SUBPLOTS_SPEC -> PlotsBuildResult.Success(
                listOf(
                    buildCompositeFigureFromProcessedSpecs(
                        plotSpec,
                        plotSize,
                        plotMaxWidth,
                        plotPreferredWidth
                    )
                )
            )

            FigKind.GG_BUNCH_SPEC -> buildGGBunchFromProcessedSpecs(
                plotSpec,
                plotMaxWidth,
                plotPreferredWidth
            )
        }
    }

    private fun buildGGBunchFromProcessedSpecs(
        bunchSpec: MutableMap,
        maxWidth: Double?,
        preferredWidth: Double?
    ): PlotsBuildResult {

        val naturalSize = PlotSizeHelper.plotBunchSize(bunchSpec)
        val scaledSize = preferredWidth?.let { w ->
            naturalSize.mul(max(Defaults.MIN_PLOT_WIDTH, w) / naturalSize.x)
        } ?: naturalSize
        val neededSize = if (maxWidth != null && maxWidth < scaledSize.x) {
            scaledSize.mul(max(Defaults.MIN_PLOT_WIDTH, maxWidth) / scaledSize.x)
        } else {
            scaledSize
        }

        val scalingCoef = neededSize.x / naturalSize.x

        val bunchConfig = BunchConfig(bunchSpec)
        if (bunchConfig.bunchItems.isEmpty()) return PlotsBuildResult.Error(
            "No plots in the bunch"
        )

        val buildInfos = ArrayList()
        for (bunchItem in bunchConfig.bunchItems) {
            val plotSpec = bunchItem.featureSpec as MutableMap
            val itemSize = PlotSizeHelper.bunchItemSize(bunchItem)
            val itemBounds = DoubleRectangle(
                DoubleVector(bunchItem.x, bunchItem.y).mul(scalingCoef),
                itemSize.mul(scalingCoef)
            )

            val plotFigureBuildInfo = buildSinglePlotFromProcessedSpecs(
                plotSpec,
                itemSize,
                plotMaxWidth = null,
                plotPreferredWidth = null
            ).withBounds(itemBounds)

            buildInfos.add(plotFigureBuildInfo)
        }

        return PlotsBuildResult.Success(buildInfos)
    }

    private fun buildSinglePlotFromProcessedSpecs(
        plotSpec: Map,
        plotSize: DoubleVector?,
        plotMaxWidth: Double?,
        plotPreferredWidth: Double?
    ): PlotFigureBuildInfo {

        val computationMessages = ArrayList()
        val config = PlotConfigClientSide.create(plotSpec) {
            computationMessages.addAll(it)
        }

        val preferredSize = PlotSizeHelper.singlePlotSize(
            plotSpec,
            plotSize,
            plotMaxWidth,
            plotPreferredWidth,
            config.facets,
            config.containsLiveMap
        )

        val assembler = createPlotAssembler(config)
        return PlotFigureBuildInfo(
            assembler,
            plotSpec,
            DoubleRectangle(DoubleVector.ZERO, preferredSize),
            computationMessages
        )
    }

    private fun buildCompositeFigureFromProcessedSpecs(
        plotSpec: Map,
        plotSize: DoubleVector?,
        plotMaxWidth: Double?,
        plotPreferredWidth: Double?
    ): CompositeFigureBuildInfo {
        // ToDo: collect computationMessages.
        val computationMessages = ArrayList()
        val compositeFigureConfig = CompositeFigureConfig(plotSpec) {
            computationMessages.addAll(it)
        }

        val preferredSize = PlotSizeHelper.subPlotsSize(
            plotSpec,
            plotSize,
            plotMaxWidth,
            plotPreferredWidth,
        )

        return buildCompositeFigure(
            compositeFigureConfig,
            preferredSize
        )
    }

    private fun buildCompositeFigure(
        compositeFigureConfig: CompositeFigureConfig,
        preferredSize: DoubleVector,
    ): CompositeFigureBuildInfo {

        val elements: List = compositeFigureConfig.elementConfigs.map {
            it?.let {
                when (PlotConfig.figSpecKind(it)) {
                    FigKind.PLOT_SPEC -> buildSinglePlotFromProcessedSpecs(
                        plotSpec = it.toMap(),
                        plotSize = null,           // Will be updateed by sub-plots layout.
                        plotMaxWidth = null,
                        plotPreferredWidth = null
                    )

                    FigKind.SUBPLOTS_SPEC -> {
                        val gridOptions = it as CompositeFigureConfig
                        buildCompositeFigure(
                            gridOptions,
                            preferredSize = DoubleVector.ZERO // Will be updateed by sub-plots layout.
                        )
                    }

                    FigKind.GG_BUNCH_SPEC -> throw IllegalArgumentException("SubPlots can't contain GGBunch.")
                }
            }
        }

        return CompositeFigureBuildInfo(
            elements = elements,
            layout = compositeFigureConfig.createLayout(),
            DoubleRectangle(DoubleVector.ZERO, preferredSize),
        )
    }

    private fun createPlotAssembler(config: PlotConfigClientSide): PlotAssembler {
        return PlotConfigClientSideUtil.createPlotAssembler(config)
    }

    private fun throwTestingErrors() {
        // testing errors
//        throw RuntimeException()
//        throw RuntimeException("My sudden crush")
//        throw IllegalArgumentException("User configuration error")
//        throw IllegalStateException("User configuration error")
//        throw IllegalStateException()   // Huh?
    }

    /**
     * Applies all transformations to the plot specifications.
     * @param plotSpec: raw specifications of a single plot or GGBunch
     */
    fun processRawSpecs(plotSpec: MutableMap, frontendOnly: Boolean): MutableMap {
        PlotConfig.assertFigSpecOrErrorMessage(plotSpec)
        if (PlotConfig.isFailure(plotSpec)) {
            return plotSpec
        }

        // "Backend" transforms.
        @Suppress("NAME_SHADOWING")
        val plotSpec = if (frontendOnly) {
            plotSpec
        } else {
            BackendSpecTransformUtil.processTransform(plotSpec)
        }

        if (PlotConfig.isFailure(plotSpec)) {
            return plotSpec
        }

        // "Frontend" transforms.
        return PlotConfigClientSide.processTransform(plotSpec)
    }


    sealed class PlotsBuildResult {
        val isError: Boolean = this is Error

        class Error(val error: String) : PlotsBuildResult()

        class Success(
            val buildInfos: List
        ) : PlotsBuildResult()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy