From b015c099422f9c1b448bdaec7543d1d0aed346ae Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Thu, 27 Apr 2023 14:21:51 +0300 Subject: [PATCH 1/6] Fix XY-ranges calculation, use height/width expansion for errorbar depend on its representation. --- .../plot/base/aes/AestheticsDefaults.kt | 4 --- .../datalore/plot/base/geom/ErrorBarGeom.kt | 32 +++++++++++++++++-- .../datalore/plot/base/geom/util/GeomUtil.kt | 9 ++++++ .../builder/assemble/PositionalScalesUtil.kt | 18 ++++++----- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt index 67e092cc867..fd6ae421600 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/aes/AestheticsDefaults.kt @@ -120,10 +120,6 @@ open class AestheticsDefaults { .update(Aes.COLOR, Color.BLACK) } - fun errorBarH(): AestheticsDefaults { - return errorBar() - } - fun crossBar(): AestheticsDefaults { return AestheticsDefaults() .update(Aes.WIDTH, 0.9) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt index 5182f90846d..20100e4ea0b 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt @@ -8,6 +8,7 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleSegment import jetbrains.datalore.base.geometry.DoubleVector +import jetbrains.datalore.base.interval.DoubleSpan import jetbrains.datalore.base.values.Color import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.aes.AesScaling @@ -23,7 +24,7 @@ import jetbrains.datalore.plot.base.render.SvgRoot import jetbrains.datalore.vis.svg.SvgGElement import jetbrains.datalore.vis.svg.SvgLineElement -class ErrorBarGeom : GeomBase() { +class ErrorBarGeom : GeomBase(), WithWidth, WithHeight { override val legendKeyElementFactory: LegendKeyElementFactory get() = ErrorBarLegendKeyElementFactory() @@ -38,10 +39,10 @@ class ErrorBarGeom : GeomBase() { val geomHelper = GeomHelper(pos, coord, ctx) val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.ERROR_BAR, ctx) - var dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH) + var dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), verticalAes()) val isVertical = dataPoints.any() if (!isVertical) { - dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT) + dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), horizontalAes()) } val xAes = if (isVertical) Aes.X else Aes.Y @@ -156,6 +157,9 @@ class ErrorBarGeom : GeomBase() { companion object { const val HANDLES_GROUPS = false + private fun verticalAes() = listOf(Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH) + private fun horizontalAes() = listOf(Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT) + private fun errorBarLegendShape(segments: List, p: DataPointAesthetics): SvgGElement { val g = SvgGElement() segments.forEach { segment -> @@ -193,4 +197,26 @@ class ErrorBarGeom : GeomBase() { return g } } + + override fun heightSpan( + p: DataPointAesthetics, + coordAes: Aes, + resolution: Double, + isDiscrete: Boolean + ): DoubleSpan? { + val isHorizontal = horizontalAes().all(p::defined) + if (!isHorizontal) return null + return PointDimensionsUtil.dimensionSpan(p, coordAes, Aes.HEIGHT, resolution) + } + + override fun widthSpan( + p: DataPointAesthetics, + coordAes: Aes, + resolution: Double, + isDiscrete: Boolean + ): DoubleSpan? { + val isVertical = verticalAes().all(p::defined) + if (!isVertical) return null + return PointDimensionsUtil.dimensionSpan(p, coordAes, Aes.WIDTH, resolution) + } } diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt index e40d10c682a..1e7db752b93 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt @@ -143,6 +143,15 @@ object GeomUtil { return dataPoints.filter { p -> p.defined(aes0) && p.defined(aes1) && p.defined(aes2) && p.defined(aes3) } } + fun withDefined( + dataPoints: Iterable, + aesList: List> + ): Iterable { + return dataPoints.filter { p -> + aesList.all { aes -> p.defined(aes) } + } + } + fun createGroups(dataPoints: Iterable): Map> { val pointsByGroup = HashMap>() for (p in dataPoints) { diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt index 8217a81cac2..50477e4f238 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt @@ -135,12 +135,14 @@ internal object PositionalScalesUtil { } private fun positionalDryRunAesthetics(layer: GeomLayer): Aesthetics { - val aesList = layer.renderedAes().filter { - Aes.affectingScaleX(it) || - Aes.affectingScaleY(it) || - it == Aes.HEIGHT || - it == Aes.WIDTH - } + val aesList = layer.renderedAes() + .filter { + Aes.affectingScaleX(it) || + Aes.affectingScaleY(it) || + it == Aes.HEIGHT || + it == Aes.WIDTH + } + .filter { layer.hasBinding(it) || layer.hasConstant(it) } val mappers = aesList.associateWith { Mappers.IDENTITY } return PlotUtil.createLayerAesthetics(layer, aesList, mappers) @@ -191,8 +193,8 @@ internal object PositionalScalesUtil { private fun computeLayerDryRunXYRangesAfterPosAdjustment( layer: GeomLayer, aes: Aesthetics, geomCtx: GeomContext ): Pair { - val posAesX = Aes.affectingScaleX(layer.renderedAes()) - val posAesY = Aes.affectingScaleY(layer.renderedAes()) + val posAesX = Aes.affectingScaleX(layer.renderedAes().filter { layer.hasBinding(it) || layer.hasConstant(it) }) + val posAesY = Aes.affectingScaleY(layer.renderedAes().filter { layer.hasBinding(it) || layer.hasConstant(it) }) val pos = PlotUtil.createPositionAdjustment(layer.posProvider, aes) if (pos.isIdentity) { From e65d67c742e14d5f94739494fa7bf2d4d768c293 Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Fri, 28 Apr 2023 12:39:46 +0300 Subject: [PATCH 2/6] Separate logic to identify renderedAes for errobar. --- .../datalore/plot/base/geom/ErrorBarGeom.kt | 33 +++---------------- .../datalore/plot/base/geom/util/GeomUtil.kt | 9 ----- .../plot/builder/assemble/GeomLayerBuilder.kt | 32 +++++++++++++++--- .../builder/assemble/PositionalScalesUtil.kt | 18 +++++----- 4 files changed, 40 insertions(+), 52 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt index 20100e4ea0b..ef03963064e 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt @@ -24,7 +24,7 @@ import jetbrains.datalore.plot.base.render.SvgRoot import jetbrains.datalore.vis.svg.SvgGElement import jetbrains.datalore.vis.svg.SvgLineElement -class ErrorBarGeom : GeomBase(), WithWidth, WithHeight { +class ErrorBarGeom : GeomBase() { override val legendKeyElementFactory: LegendKeyElementFactory get() = ErrorBarLegendKeyElementFactory() @@ -39,10 +39,10 @@ class ErrorBarGeom : GeomBase(), WithWidth, WithHeight { val geomHelper = GeomHelper(pos, coord, ctx) val colorsByDataPoint = HintColorUtil.createColorMarkerMapper(GeomKind.ERROR_BAR, ctx) - var dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), verticalAes()) + var dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH) val isVertical = dataPoints.any() if (!isVertical) { - dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), horizontalAes()) + dataPoints = GeomUtil.withDefined(aesthetics.dataPoints(), Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT) } val xAes = if (isVertical) Aes.X else Aes.Y @@ -157,9 +157,6 @@ class ErrorBarGeom : GeomBase(), WithWidth, WithHeight { companion object { const val HANDLES_GROUPS = false - private fun verticalAes() = listOf(Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH) - private fun horizontalAes() = listOf(Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT) - private fun errorBarLegendShape(segments: List, p: DataPointAesthetics): SvgGElement { val g = SvgGElement() segments.forEach { segment -> @@ -197,26 +194,4 @@ class ErrorBarGeom : GeomBase(), WithWidth, WithHeight { return g } } - - override fun heightSpan( - p: DataPointAesthetics, - coordAes: Aes, - resolution: Double, - isDiscrete: Boolean - ): DoubleSpan? { - val isHorizontal = horizontalAes().all(p::defined) - if (!isHorizontal) return null - return PointDimensionsUtil.dimensionSpan(p, coordAes, Aes.HEIGHT, resolution) - } - - override fun widthSpan( - p: DataPointAesthetics, - coordAes: Aes, - resolution: Double, - isDiscrete: Boolean - ): DoubleSpan? { - val isVertical = verticalAes().all(p::defined) - if (!isVertical) return null - return PointDimensionsUtil.dimensionSpan(p, coordAes, Aes.WIDTH, resolution) - } -} +} \ No newline at end of file diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt index 1e7db752b93..e40d10c682a 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/util/GeomUtil.kt @@ -143,15 +143,6 @@ object GeomUtil { return dataPoints.filter { p -> p.defined(aes0) && p.defined(aes1) && p.defined(aes2) && p.defined(aes3) } } - fun withDefined( - dataPoints: Iterable, - aesList: List> - ): Iterable { - return dataPoints.filter { p -> - aesList.all { aes -> p.defined(aes) } - } - } - fun createGroups(dataPoints: Iterable): Map> { val pointsByGroup = HashMap>() for (p in dataPoints) { diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt index b203b97fead..bbfa6c120be 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt @@ -267,8 +267,8 @@ class GeomLayerBuilder( override val geomKind: GeomKind = geomProvider.geomKind override val aestheticsDefaults: AestheticsDefaults = geomProvider.aestheticsDefaults() + private val myConstantByAes: TypedKeyHashMap = TypedKeyHashMap() private val myRenderedAes: List> - private val myConstantByAes: TypedKeyHashMap override val legendKeyElementFactory: LegendKeyElementFactory get() = geom.legendKeyElementFactory @@ -277,13 +277,37 @@ class GeomLayerBuilder( get() = geom is LiveMapGeom init { - myRenderedAes = GeomMeta.renders(geomProvider.geomKind, colorByAes, fillByAes) - // constant value by aes (default + specified) - myConstantByAes = TypedKeyHashMap() for (key in constantByAes.keys()) { myConstantByAes.put(key, constantByAes[key]) } + + myRenderedAes = GeomMeta.renders(geomProvider.geomKind, colorByAes, fillByAes).let { allRenderedAes -> + if (geomKind == GeomKind.ERROR_BAR) { + // ToDo Need refactoring... + // This geometry supports a dual set of aesthetics (vertical and horizontal representation). + // Check that the settings are not inconsistent + // and set the aesthetics needed for that geometry. + val definedAes = allRenderedAes.filter { aes -> hasBinding(aes) || hasConstant(aes) } + val isHorizontal = listOf(Aes.Y, Aes.XMIN, Aes.XMAX).all { aes -> aes in definedAes } + val isVertical = listOf(Aes.X, Aes.YMIN, Aes.YMAX).all { aes -> aes in definedAes } + require(!(isHorizontal && isVertical)) { + "For errorbar either x, ymin, ymax or y, xmin, xmax must be specified." + } + when { + isVertical -> listOf( + Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH, + Aes.ALPHA, Aes.COLOR, Aes.LINETYPE, Aes.SIZE + ) + else -> listOf( + Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT, + Aes.ALPHA, Aes.COLOR, Aes.LINETYPE, Aes.SIZE + ) + } + } else { + allRenderedAes + } + } } override fun renderedAes(): List> { diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt index 50477e4f238..8217a81cac2 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/PositionalScalesUtil.kt @@ -135,14 +135,12 @@ internal object PositionalScalesUtil { } private fun positionalDryRunAesthetics(layer: GeomLayer): Aesthetics { - val aesList = layer.renderedAes() - .filter { - Aes.affectingScaleX(it) || - Aes.affectingScaleY(it) || - it == Aes.HEIGHT || - it == Aes.WIDTH - } - .filter { layer.hasBinding(it) || layer.hasConstant(it) } + val aesList = layer.renderedAes().filter { + Aes.affectingScaleX(it) || + Aes.affectingScaleY(it) || + it == Aes.HEIGHT || + it == Aes.WIDTH + } val mappers = aesList.associateWith { Mappers.IDENTITY } return PlotUtil.createLayerAesthetics(layer, aesList, mappers) @@ -193,8 +191,8 @@ internal object PositionalScalesUtil { private fun computeLayerDryRunXYRangesAfterPosAdjustment( layer: GeomLayer, aes: Aesthetics, geomCtx: GeomContext ): Pair { - val posAesX = Aes.affectingScaleX(layer.renderedAes().filter { layer.hasBinding(it) || layer.hasConstant(it) }) - val posAesY = Aes.affectingScaleY(layer.renderedAes().filter { layer.hasBinding(it) || layer.hasConstant(it) }) + val posAesX = Aes.affectingScaleX(layer.renderedAes()) + val posAesY = Aes.affectingScaleY(layer.renderedAes()) val pos = PlotUtil.createPositionAdjustment(layer.posProvider, aes) if (pos.isIdentity) { From 7818ff1f858b9f17771380a84dcc8cd687152211 Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Fri, 28 Apr 2023 12:42:01 +0300 Subject: [PATCH 3/6] Remove unused import directive. --- .../kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt index ef03963064e..5182f90846d 100644 --- a/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt +++ b/plot-base-portable/src/commonMain/kotlin/jetbrains/datalore/plot/base/geom/ErrorBarGeom.kt @@ -8,7 +8,6 @@ package jetbrains.datalore.plot.base.geom import jetbrains.datalore.base.geometry.DoubleRectangle import jetbrains.datalore.base.geometry.DoubleSegment import jetbrains.datalore.base.geometry.DoubleVector -import jetbrains.datalore.base.interval.DoubleSpan import jetbrains.datalore.base.values.Color import jetbrains.datalore.plot.base.* import jetbrains.datalore.plot.base.aes.AesScaling @@ -194,4 +193,4 @@ class ErrorBarGeom : GeomBase() { return g } } -} \ No newline at end of file +} From 58f12e3b4a70b3ab361c29f09f9e2690e6249ad9 Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Fri, 28 Apr 2023 14:13:10 +0300 Subject: [PATCH 4/6] Code cleanup. --- .../plot/builder/assemble/GeomLayerBuilder.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt index bbfa6c120be..051b6357d56 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt @@ -286,23 +286,19 @@ class GeomLayerBuilder( if (geomKind == GeomKind.ERROR_BAR) { // ToDo Need refactoring... // This geometry supports a dual set of aesthetics (vertical and horizontal representation). - // Check that the settings are not inconsistent + // Check that the settings are consistent // and set the aesthetics needed for that geometry. val definedAes = allRenderedAes.filter { aes -> hasBinding(aes) || hasConstant(aes) } - val isHorizontal = listOf(Aes.Y, Aes.XMIN, Aes.XMAX).all { aes -> aes in definedAes } - val isVertical = listOf(Aes.X, Aes.YMIN, Aes.YMAX).all { aes -> aes in definedAes } - require(!(isHorizontal && isVertical)) { + val verticalAesSet = setOf(Aes.X, Aes.YMIN, Aes.YMAX) + val horizontalAesSet = setOf(Aes.Y, Aes.XMIN, Aes.XMAX) + val isVertical = verticalAesSet.all { aes -> aes in definedAes } + val isHorizontal = horizontalAesSet.all { aes -> aes in definedAes } + require(!(isVertical && isHorizontal)) { "For errorbar either x, ymin, ymax or y, xmin, xmax must be specified." } - when { - isVertical -> listOf( - Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH, - Aes.ALPHA, Aes.COLOR, Aes.LINETYPE, Aes.SIZE - ) - else -> listOf( - Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT, - Aes.ALPHA, Aes.COLOR, Aes.LINETYPE, Aes.SIZE - ) + allRenderedAes - when (isVertical) { + true -> horizontalAesSet + Aes.HEIGHT + false -> verticalAesSet + Aes.WIDTH } } else { allRenderedAes From 980973d8387d2ea203de449d1f5bc0da185cfa4c Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Tue, 2 May 2023 14:59:56 +0300 Subject: [PATCH 5/6] Fix requirements for the specified aesthetics for the errorbar. --- .../plot/builder/assemble/GeomLayerBuilder.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt index 051b6357d56..01c4e66815f 100644 --- a/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt +++ b/plot-builder-portable/src/commonMain/kotlin/jetbrains/datalore/plot/builder/assemble/GeomLayerBuilder.kt @@ -289,16 +289,14 @@ class GeomLayerBuilder( // Check that the settings are consistent // and set the aesthetics needed for that geometry. val definedAes = allRenderedAes.filter { aes -> hasBinding(aes) || hasConstant(aes) } - val verticalAesSet = setOf(Aes.X, Aes.YMIN, Aes.YMAX) - val horizontalAesSet = setOf(Aes.Y, Aes.XMIN, Aes.XMAX) - val isVertical = verticalAesSet.all { aes -> aes in definedAes } - val isHorizontal = horizontalAesSet.all { aes -> aes in definedAes } + val isVertical = setOf(Aes.YMIN, Aes.YMAX).all { aes -> aes in definedAes } + val isHorizontal = setOf(Aes.XMIN, Aes.XMAX).all { aes -> aes in definedAes } require(!(isVertical && isHorizontal)) { - "For errorbar either x, ymin, ymax or y, xmin, xmax must be specified." + "Either ymin, ymax or xmin, xmax must be specified for the errorbar." } allRenderedAes - when (isVertical) { - true -> horizontalAesSet + Aes.HEIGHT - false -> verticalAesSet + Aes.WIDTH + true -> setOf(Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT) + false -> setOf(Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH) } } else { allRenderedAes From 460f57e50db1ed5413ff71b841842e28ab50c404 Mon Sep 17 00:00:00 2001 From: Olga Larionova Date: Tue, 2 May 2023 15:01:29 +0300 Subject: [PATCH 6/6] Update demo notebook. --- docs/f-23b/horizontal_error_bars.ipynb | 300 ++++++++++++++++++++++--- 1 file changed, 264 insertions(+), 36 deletions(-) diff --git a/docs/f-23b/horizontal_error_bars.ipynb b/docs/f-23b/horizontal_error_bars.ipynb index 19d44875a00..a0b464f6205 100644 --- a/docs/f-23b/horizontal_error_bars.ipynb +++ b/docs/f-23b/horizontal_error_bars.ipynb @@ -4,10 +4,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Horizontal error bars\n", + "# Horizontal error bars and vertical \"dodge\"\n", "\n", - "`geom_errorbar()` can be plotted horizontally by assigning `xmin` and `xmax` aesthetics.\n", - "The height of the error bar is spepicified by the `height`." + "`geom_errorbar()` can be plotted horizontally by assigning `y`,`xmin`,`xmax` aesthetics. The height of the error bar is defined by the `height`.\n", + "\n", + "New type of position adjustment `'dodgev'` is used to adjust the position by dodging overlaps to the side. Function `position_dodgev(height)` allows to set the dodge height.\n", + "\n" ] }, { @@ -22,7 +24,7 @@ " \n", " \n", @@ -38,26 +40,121 @@ "LetsPlot.setup_html()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1. Data Preparation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ToothGrowth dataset describes the effect of Vitamin C on tooth growth in guinea pigs. Each animal received one of three dose levels of vitamin C (0.5, 1, and 2 mg/day) by one of two delivery methods: orange juice (OJ) or ascorbic acid (VC)." + ] + }, { "cell_type": "code", "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
lensuppdose
04.2VC0.5
111.5VC0.5
27.3VC0.5
35.8VC0.5
46.4VC0.5
\n", + "
" + ], + "text/plain": [ + " len supp dose\n", + "0 4.2 VC 0.5\n", + "1 11.5 VC 0.5\n", + "2 7.3 VC 0.5\n", + "3 5.8 VC 0.5\n", + "4 6.4 VC 0.5" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/ToothGrowth.csv\")\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "data = dict(\n", - " supp = ['OJ', 'OJ', 'OJ', 'VC', 'VC', 'VC'],\n", - " dose = [0.5, 1.0, 2.0, 0.5, 1.0, 2.0],\n", - " length = [13.23, 22.70, 26.06, 7.98, 16.77, 26.14],\n", - " len_min = [11.83, 21.2, 24.50, 4.24, 15.26, 23.35],\n", - " len_max = [15.63, 24.9, 27.11, 10.72, 19.28, 28.93]\n", - ")" + "* len : Tooth length\n", + "* dose : Dose in milligrams (0.5, 1, 2)\n", + "* supp : Supplement type (VC or OJ)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### 1. Default Presentation" + "Let's calculate the mean value of tooth length in each group, minimum and maximum values, and use these information to plot error bars." ] }, { @@ -68,15 +165,145 @@ { "data": { "text/html": [ - "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
suppdoselengthlen_minlen_max
0OJ0.513.238.221.5
1OJ1.022.7014.527.3
2OJ2.026.0622.430.9
3VC0.57.984.211.5
4VC1.016.7713.622.5
5VC2.026.1418.533.9
\n", + "
" + ], + "text/plain": [ + " supp dose length len_min len_max\n", + "0 OJ 0.5 13.23 8.2 21.5\n", + "1 OJ 1.0 22.70 14.5 27.3\n", + "2 OJ 2.0 26.06 22.4 30.9\n", + "3 VC 0.5 7.98 4.2 11.5\n", + "4 VC 1.0 16.77 13.6 22.5\n", + "5 VC 2.0 26.14 18.5 33.9" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np\n", + "\n", + "data = {}\n", + "\n", + "for supp_lvl in np.unique(df['supp']):\n", + " for dose_lvl in np.unique(df['dose']):\n", + " data_to_sum = df[(df['supp'] == supp_lvl) & (df['dose'] == dose_lvl)]\n", + "\n", + " mean = data_to_sum['len'].mean()\n", + " len_min = data_to_sum['len'].min()\n", + " len_max = data_to_sum['len'].max()\n", + "\n", + " data.setdefault('supp', []).append(supp_lvl)\n", + " data.setdefault('dose', []).append(dose_lvl)\n", + " data.setdefault('length', []).append(mean)\n", + " data.setdefault('len_min', []).append(len_min)\n", + " data.setdefault('len_max', []).append(len_max)\n", + " \n", + "pd.DataFrame(data) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2. Default Presentation" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -125,28 +352,29 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 2. `position_dodgev()`\n", + "#### 3. With `position = 'dodgev'`\n", + "\n", "\n", "To fix errorbars overlapping, use `position_dodgev(height)` - to move them vertically." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -200,27 +428,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### 3. Error-bars on bar plot" + "#### 4. Error-bars on bar plot" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "
\n", " " ], "text/plain": [ - "" + "" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" }