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",
+ " len | \n",
+ " supp | \n",
+ " dose | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 4.2 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 11.5 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 7.3 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " 5.8 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " 6.4 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ "
\n",
+ " \n",
+ "
\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",
+ " supp | \n",
+ " dose | \n",
+ " length | \n",
+ " len_min | \n",
+ " len_max | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " OJ | \n",
+ " 0.5 | \n",
+ " 13.23 | \n",
+ " 8.2 | \n",
+ " 21.5 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " OJ | \n",
+ " 1.0 | \n",
+ " 22.70 | \n",
+ " 14.5 | \n",
+ " 27.3 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " OJ | \n",
+ " 2.0 | \n",
+ " 26.06 | \n",
+ " 22.4 | \n",
+ " 30.9 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " VC | \n",
+ " 0.5 | \n",
+ " 7.98 | \n",
+ " 4.2 | \n",
+ " 11.5 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " VC | \n",
+ " 1.0 | \n",
+ " 16.77 | \n",
+ " 13.6 | \n",
+ " 22.5 | \n",
+ "
\n",
+ " \n",
+ " 5 | \n",
+ " VC | \n",
+ " 2.0 | \n",
+ " 26.14 | \n",
+ " 18.5 | \n",
+ " 33.9 | \n",
+ "
\n",
+ " \n",
+ "
\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"
}
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-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 fdc213800df..f05d3c63be1 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
@@ -268,8 +268,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
@@ -278,13 +278,31 @@ 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 consistent
+ // and set the aesthetics needed for that geometry.
+ val definedAes = allRenderedAes.filter { aes -> hasBinding(aes) || hasConstant(aes) }
+ 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)) {
+ "Either ymin, ymax or xmin, xmax must be specified for the errorbar."
+ }
+ allRenderedAes - when (isVertical) {
+ true -> setOf(Aes.Y, Aes.XMIN, Aes.XMAX, Aes.HEIGHT)
+ false -> setOf(Aes.X, Aes.YMIN, Aes.YMAX, Aes.WIDTH)
+ }
+ } else {
+ allRenderedAes
+ }
+ }
}
override fun renderedAes(): List> {