fun createGeomProvider()

in plot-stem/src/commonMain/kotlin/org/jetbrains/letsPlot/core/spec/GeomProviderFactory.kt [60:469]


    fun createGeomProvider(
        geomKind: GeomKind,
        layerConfig: LayerConfig,
        aopConversion: AesOptionConversion,
        expFormat: ExponentFormat,
        tz: TimeZone?,
    ): GeomProvider {
        return when (geomKind) {
            GeomKind.HISTOGRAM -> GeomProvider.histogram {
                val geom = HistogramGeom()
                if (layerConfig.hasOwn(Option.Geom.Histogram.BREAKS)) {
                    geom.setBreaks(layerConfig.getDoubleList(Option.Geom.Histogram.BREAKS))
                }
                geom
            }

            GeomKind.AREA -> GeomProvider.area {
                val geom = AreaGeom()

                if (layerConfig.hasOwn(Option.Geom.Area.FLAT)) {
                    geom.flat = layerConfig.getBoolean(Option.Geom.Area.FLAT)
                }
                geom
            }

            GeomKind.DENSITY -> GeomProvider.density {
                val geom = DensityGeom()
                if (layerConfig.hasOwn(Option.Stat.Density.QUANTILES)) {
                    geom.quantiles = layerConfig.getBoundedDoubleList(Option.Stat.Density.QUANTILES, 0.0, 1.0)
                }
                if (layerConfig.hasOwn(Option.Geom.Density.QUANTILE_LINES)) {
                    geom.quantileLines =
                        layerConfig.getBoolean(Option.Geom.Density.QUANTILE_LINES, DensityGeom.DEF_QUANTILE_LINES)
                }
                geom
            }

            GeomKind.DOT_PLOT -> GeomProvider.dotplot {
                val geom = DotplotGeom()
                if (layerConfig.hasOwn(Option.Geom.Dotplot.DOTSIZE)) {
                    geom.dotSize = layerConfig.getDouble(Option.Geom.Dotplot.DOTSIZE)!!
                }
                if (layerConfig.hasOwn(Option.Geom.Dotplot.STACKRATIO)) {
                    geom.stackRatio = layerConfig.getDouble(Option.Geom.Dotplot.STACKRATIO)!!
                }
                if (layerConfig.hasOwn(Option.Geom.Dotplot.STACKGROUPS)) {
                    geom.stackGroups = layerConfig.getBoolean(Option.Geom.Dotplot.STACKGROUPS)
                }
                if (layerConfig.hasOwn(Option.Geom.Dotplot.STACKDIR)) {
                    geom.stackDir = layerConfig.getString(Option.Geom.Dotplot.STACKDIR)!!.let {
                        when (it.lowercase()) {
                            "up" -> DotplotGeom.Stackdir.UP
                            "down" -> DotplotGeom.Stackdir.DOWN
                            "center" -> DotplotGeom.Stackdir.CENTER
                            "centerwhole" -> DotplotGeom.Stackdir.CENTERWHOLE
                            else -> throw IllegalArgumentException(
                                "Unsupported ${Option.Geom.Dotplot.STACKDIR}: '$it'. " +
                                        "Use one of: up, down, center, centerwhole."
                            )
                        }
                    }
                }
                if (layerConfig.hasOwn(Option.Geom.Dotplot.METHOD)) {
                    geom.method = DotplotStat.Method.safeValueOf(layerConfig.getString(Option.Geom.Dotplot.METHOD)!!)
                }
                geom
            }

            GeomKind.TILE -> GeomProvider.tile {
                TileGeom().apply {
                    this.widthUnit = dimensionUnit(layerConfig, Option.Geom.Tile.WIDTH_UNIT) ?: TileGeom.DEF_WIDTH_UNIT
                    this.heightUnit =
                        dimensionUnit(layerConfig, Option.Geom.Tile.HEIGHT_UNIT) ?: TileGeom.DEF_HEIGHT_UNIT
                }
            }

            GeomKind.HEX -> GeomProvider.hex {
                HexGeom().apply {
                    this.widthUnit = dimensionUnit(layerConfig, Option.Geom.Hex.WIDTH_UNIT) ?: HexGeom.DEF_WIDTH_UNIT
                    this.heightUnit = dimensionUnit(layerConfig, Option.Geom.Hex.HEIGHT_UNIT) ?: HexGeom.DEF_HEIGHT_UNIT
                }
            }

            GeomKind.ERROR_BAR -> GeomProvider.errorBar {
                ErrorBarGeom().apply {
                    if (layerConfig.hasOwn(Option.Geom.ErrorBar.WIDTH_UNIT)) {
                        this.widthUnit = dimensionUnit(layerConfig, Option.Geom.ErrorBar.WIDTH_UNIT)!!
                    }
                }
            }

            GeomKind.CROSS_BAR -> GeomProvider.crossBar { ctx ->
                val geom = CrossBarGeom()
                if (layerConfig.hasOwn(Option.Geom.CrossBar.FATTEN)) {
                    geom.fattenMidline = layerConfig.getDouble(Option.Geom.CrossBar.FATTEN)!!
                }
                val isVertical = isVertical(ctx, geomKind.name)
                val midlineAes = if (isVertical) Aes.Y else Aes.X
                val midlineIsMapped = ctx.hasBinding(midlineAes) || ctx.hasConstant(midlineAes)
                if (!midlineIsMapped) {
                    // The `fattenMidline` variable affects the legend: if set to 0, the midline is omitted.
                    geom.fattenMidline = 0.0
                }
                if (layerConfig.hasOwn(Option.Geom.CrossBar.WIDTH_UNIT)) {
                    geom.widthUnit = dimensionUnit(layerConfig, Option.Geom.CrossBar.WIDTH_UNIT)!!
                }
                geom
            }

            GeomKind.POINT_RANGE -> GeomProvider.pointRange {
                val geom = PointRangeGeom()
                if (layerConfig.hasOwn(Option.Geom.PointRange.FATTEN)) {
                    geom.fattenMidPoint = layerConfig.getDouble(Option.Geom.PointRange.FATTEN)!!
                }
                geom
            }

            GeomKind.BAND -> GeomProvider.band { ctx ->
                BandGeom(isVertical(ctx, geomKind.name))
            }

            GeomKind.BOX_PLOT -> GeomProvider.boxplot {
                val geom = BoxplotGeom()
                if (layerConfig.hasOwn(Option.Geom.Boxplot.FATTEN)) {
                    geom.fattenMidline = layerConfig.getDouble(Option.Geom.Boxplot.FATTEN)!!
                }
                if (layerConfig.hasOwn(Option.Geom.Boxplot.WHISKER_WIDTH)) {
                    geom.whiskerWidth = layerConfig.getDouble(Option.Geom.Boxplot.WHISKER_WIDTH)!!
                }
                if (layerConfig.hasOwn(Option.Geom.Boxplot.WIDTH_UNIT)) {
                    geom.widthUnit = dimensionUnit(layerConfig, Option.Geom.Boxplot.WIDTH_UNIT)!!
                }
                geom
            }

            GeomKind.AREA_RIDGES -> GeomProvider.arearidges {
                val geom = AreaRidgesGeom()
                if (layerConfig.hasOwn(Option.Geom.AreaRidges.SCALE)) {
                    geom.scale = layerConfig.getDoubleDef(Option.Geom.AreaRidges.SCALE, AreaRidgesGeom.DEF_SCALE)
                }
                if (layerConfig.hasOwn(Option.Geom.AreaRidges.MIN_HEIGHT)) {
                    geom.minHeight =
                        layerConfig.getDoubleDef(Option.Geom.AreaRidges.MIN_HEIGHT, AreaRidgesGeom.DEF_MIN_HEIGHT)
                }
                if (layerConfig.hasOwn(Option.Stat.DensityRidges.QUANTILES)) {
                    geom.quantiles = layerConfig.getBoundedDoubleList(Option.Stat.DensityRidges.QUANTILES, 0.0, 1.0)
                }
                if (layerConfig.hasOwn(Option.Geom.AreaRidges.QUANTILE_LINES)) {
                    geom.quantileLines =
                        layerConfig.getBoolean(Option.Geom.AreaRidges.QUANTILE_LINES, AreaRidgesGeom.DEF_QUANTILE_LINES)
                }
                geom
            }

            GeomKind.VIOLIN -> GeomProvider.violin {
                val geom = ViolinGeom()
                if (layerConfig.hasOwn(Option.Stat.YDensity.QUANTILES)) {
                    geom.quantiles = layerConfig.getBoundedDoubleList(Option.Stat.YDensity.QUANTILES, 0.0, 1.0)
                }
                if (layerConfig.hasOwn(Option.Geom.Violin.QUANTILE_LINES)) {
                    geom.quantileLines =
                        layerConfig.getBoolean(Option.Geom.Violin.QUANTILE_LINES, ViolinGeom.DEF_QUANTILE_LINES)
                }
                if (layerConfig.hasOwn(Option.Geom.Violin.SHOW_HALF)) {
                    geom.showHalf = layerConfig.getDouble(Option.Geom.Violin.SHOW_HALF)!!
                }
                geom
            }

            GeomKind.SINA -> GeomProvider.sina {
                val geom = SinaGeom()
                if (layerConfig.hasOwn(Option.Geom.Sina.SEED)) {
                    geom.seed = layerConfig.getLong(Option.Geom.Sina.SEED)!!
                }
                if (layerConfig.hasOwn(Option.Stat.Sina.QUANTILES)) {
                    geom.quantiles = layerConfig.getBoundedDoubleList(Option.Stat.Sina.QUANTILES, 0.0, 1.0)
                }
                if (layerConfig.hasOwn(Option.Geom.Sina.SHOW_HALF)) {
                    geom.showHalf = layerConfig.getDouble(Option.Geom.Sina.SHOW_HALF)!!
                }
                geom
            }

            GeomKind.Y_DOT_PLOT -> GeomProvider.ydotplot {
                val geom = YDotplotGeom()
                if (layerConfig.hasOwn(Option.Geom.YDotplot.DOTSIZE)) {
                    geom.dotSize = layerConfig.getDouble(Option.Geom.YDotplot.DOTSIZE)!!
                }
                if (layerConfig.hasOwn(Option.Geom.YDotplot.STACKRATIO)) {
                    geom.stackRatio = layerConfig.getDouble(Option.Geom.YDotplot.STACKRATIO)!!
                }
                if (layerConfig.hasOwn(Option.Geom.YDotplot.STACKGROUPS)) {
                    geom.stackGroups = layerConfig.getBoolean(Option.Geom.YDotplot.STACKGROUPS)
                }
                if (layerConfig.hasOwn(Option.Geom.YDotplot.STACKDIR)) {
                    geom.yStackDir = layerConfig.getString(Option.Geom.YDotplot.STACKDIR)!!.let {
                        when (it.lowercase()) {
                            "left" -> YDotplotGeom.YStackdir.LEFT
                            "right" -> YDotplotGeom.YStackdir.RIGHT
                            "center" -> YDotplotGeom.YStackdir.CENTER
                            "centerwhole" -> YDotplotGeom.YStackdir.CENTERWHOLE
                            else -> throw IllegalArgumentException(
                                "Unsupported ${Option.Geom.YDotplot.STACKDIR}: '$it'. " +
                                        "Use one of: left, right, center, centerwhole."
                            )
                        }
                    }
                }
                if (layerConfig.hasOwn(Option.Geom.YDotplot.METHOD)) {
                    geom.method = DotplotStat.Method.safeValueOf(layerConfig.getString(Option.Geom.YDotplot.METHOD)!!)
                }
                geom
            }

            GeomKind.STEP -> GeomProvider.step {
                val geom = StepGeom()
                if (layerConfig.hasOwn(Option.Geom.Step.DIRECTION)) {
                    geom.setDirection(layerConfig.getString(Option.Geom.Step.DIRECTION)!!)
                }
                if (layerConfig.hasOwn(Option.Geom.Step.PADDED)) {
                    geom.padded = layerConfig.getBoolean(Option.Geom.Step.PADDED, StepGeom.DEF_PADDED)
                }
                geom
            }

            GeomKind.SEGMENT -> GeomProvider.segment {
                val geom = SegmentGeom()
                if (layerConfig.has(Option.Geom.Segment.ARROW)) {
                    val arrowConfig = ArrowSpecConfig.create(layerConfig[Option.Geom.Segment.ARROW]!!)
                    geom.arrowSpec = arrowConfig.createArrowSpec()
                }
                if (layerConfig.has(Option.Geom.Segment.ANIMATION)) {
                    geom.animation = layerConfig[Option.Geom.Segment.ANIMATION]
                }
                if (layerConfig.has(Option.Geom.Segment.FLAT)) {
                    geom.flat = layerConfig.getBoolean(Option.Geom.Segment.FLAT)
                }
                if (layerConfig.has(Option.Geom.Segment.GEODESIC)) {
                    geom.geodesic = layerConfig.getBoolean(Option.Geom.Segment.GEODESIC)
                }
                layerConfig.getDouble(Option.Geom.Segment.SPACER)?.let { geom.spacer = it }
                geom
            }

            GeomKind.CURVE -> return GeomProvider.curve {
                val geom = CurveGeom()
                layerConfig.getDouble(Option.Geom.Curve.CURVATURE)?.let { geom.curvature = it }
                layerConfig.getDouble(Option.Geom.Curve.ANGLE)?.let { geom.angle = it }
                layerConfig.getInteger(Option.Geom.Curve.NCP)?.let { geom.ncp = it }
                if (layerConfig.has(Option.Geom.Segment.ARROW)) {
                    val arrowConfig = ArrowSpecConfig.create(layerConfig[Option.Geom.Curve.ARROW]!!)
                    geom.arrowSpec = arrowConfig.createArrowSpec()
                }
                layerConfig.getDouble(Option.Geom.Segment.SPACER)?.let { geom.spacer = it }
                geom
            }

            GeomKind.PATH -> GeomProvider.path {
                val geom = PathGeom()
                if (layerConfig.has(Option.Geom.Path.ANIMATION)) {
                    geom.animation = layerConfig[Option.Geom.Path.ANIMATION]
                }
                if (layerConfig.has(Option.Geom.Path.FLAT)) {
                    geom.flat = layerConfig.getBoolean(Option.Geom.Path.FLAT)
                }
                if (layerConfig.has(Option.Geom.Segment.GEODESIC)) {
                    geom.geodesic = layerConfig.getBoolean(Option.Geom.Segment.GEODESIC)
                }
                geom
            }

            GeomKind.POINT -> GeomProvider.point {
                val geom = PointGeom()

                if (layerConfig.has(Option.Geom.Point.ANIMATION)) {
                    geom.animation = layerConfig[Option.Geom.Point.ANIMATION]
                }

                geom.sizeUnit = layerConfig.getString(Option.Geom.Point.SIZE_UNIT)?.lowercase()
                geom
            }

            GeomKind.TEXT -> GeomProvider.text {
                val geom = TextGeom()
                applyTextOptions(layerConfig, geom, expFormat, tz)
                geom
            }

            GeomKind.LABEL -> GeomProvider.label {
                val geom = LabelGeom()

                applyTextOptions(layerConfig, geom, expFormat, tz)
                applyLabelOptions(layerConfig, geom.labelOptions)

                geom
            }

            GeomKind.TEXT_REPEL -> GeomProvider.textRepel {
                val geom = TextRepelGeom()
                applyTextOptions(layerConfig, geom, expFormat, tz)
                applyRepelOptions(layerConfig, geom)
                geom
            }

            GeomKind.LABEL_REPEL -> GeomProvider.labelRepel {
                val geom = LabelRepelGeom()
                applyTextOptions(layerConfig, geom, expFormat, tz)
                applyLabelOptions(layerConfig, geom.labelOptions)
                applyRepelOptions(layerConfig, geom)
                geom
            }

            GeomKind.IMAGE -> GeomProvider.image {
                require(layerConfig.hasOwn(Option.Geom.Image.HREF)) { "Image reference URL (href) is not specified." }
                for (s in listOf(
                    Option.Geom.Image.XMIN,
                    Option.Geom.Image.XMAX,
                    Option.Geom.Image.YMIN,
                    Option.Geom.Image.YMAX
                )) {
                    require(layerConfig.hasOwn(s)) { "'$s' is not specified." }
                }
                ImageGeom(
                    imageUrl = layerConfig.getString(Option.Geom.Image.HREF)!!,
                )
            }

            GeomKind.PIE -> GeomProvider.pie {
                val geom = PieGeom()
                layerConfig.getDouble(Pie.HOLE)?.let { geom.holeSize = it }
                layerConfig.getDouble(Pie.SPACER_WIDTH)?.let { geom.spacerWidth = it }
                layerConfig.getColor(Pie.SPACER_COLOR, aopConversion)?.let { geom.spacerColor = it }
                layerConfig.getString(Pie.STROKE_SIDE)?.let {
                    geom.strokeSide = when (it.lowercase()) {
                        "outer" -> PieGeom.StrokeSide.OUTER
                        "inner" -> PieGeom.StrokeSide.INNER
                        "both" -> PieGeom.StrokeSide.BOTH
                        else -> throw IllegalArgumentException(
                            "Unsupported value for ${Pie.STROKE_SIDE} parameter: '$it'. " +
                                    "Use one of: outer, inner, both."
                        )
                    }
                }
                geom.sizeUnit = layerConfig.getString(Pie.SIZE_UNIT)?.lowercase()
                geom.start = layerConfig.getDouble(Pie.START)
                geom.clockwise = (layerConfig.getInteger(Pie.DIRECTION) ?: 1) == 1
                geom
            }

            GeomKind.LOLLIPOP -> GeomProvider.lollipop {
                val directionValue = layerConfig.getString(Option.Geom.Lollipop.DIRECTION)?.lowercase()
                val direction = directionValue?.let {
                    when (it) {
                        "v" -> LollipopGeom.Direction.ORTHOGONAL_TO_AXIS
                        "h" -> LollipopGeom.Direction.ALONG_AXIS
                        "s" -> LollipopGeom.Direction.SLOPE
                        else -> throw IllegalArgumentException(
                            "Unsupported value for ${Option.Geom.Lollipop.DIRECTION} parameter: '$it'. " +
                                    "Use one of: v, h, s."
                        )
                    }
                } ?: LollipopGeom.Direction.ORTHOGONAL_TO_AXIS
                val slope = layerConfig.getDouble(Option.Geom.Lollipop.SLOPE) ?: 0.0
                LollipopGeom().apply {
                    this.direction = direction
                    this.slope = slope
                    if (layerConfig.hasOwn(Option.Geom.Lollipop.INTERCEPT)) {
                        this.intercept = layerConfig.getDouble(Option.Geom.Lollipop.INTERCEPT)!!
                    }
                    if (layerConfig.hasOwn(Option.Geom.Lollipop.FATTEN)) {
                        this.fatten = layerConfig.getDouble(Option.Geom.Lollipop.FATTEN)!!
                    }
                }
            }

            GeomKind.SPOKE -> GeomProvider.spoke {
                val geom = SpokeGeom()
                layerConfig[Spoke.ARROW]?.let {
                    val arrowConfig = ArrowSpecConfig.create(it)
                    geom.arrowSpec = arrowConfig.createArrowSpec()
                }

                layerConfig.getString(Spoke.PIVOT)?.let {
                    geom.pivot = when (it.lowercase()) {
                        "tail" -> SpokeGeom.Pivot.TAIL
                        "middle", "mid" -> SpokeGeom.Pivot.MIDDLE
                        "tip" -> SpokeGeom.Pivot.TIP
                        else -> throw IllegalArgumentException(
                            "Unsupported value for ${Spoke.PIVOT} parameter: '$it'. " +
                                    "Use one of: tail, middle, mid, tip."
                        )
                    }
                }
                geom
            }

            GeomKind.STAT_R2 -> GeomProvider.statR2 {
                val geom = StatR2Geom()
                layerConfig.getString(LABEL_X)?.let { geom.labelX = it }
                layerConfig.getString(LABEL_Y)?.let { geom.labelY = it }

                geom
            }

            else -> {
                require(PROVIDER.containsKey(geomKind)) { "Provider doesn't support geom kind: '$geomKind'" }
                PROVIDER.getValue(geomKind)
            }
        }
    }