diff --git a/docs/examples/images/tooltips_8.png b/docs/examples/images/tooltips_8.png
new file mode 100644
index 00000000000..bf13852aa93
Binary files /dev/null and b/docs/examples/images/tooltips_8.png differ
diff --git a/docs/examples/images/tooltips_9.png b/docs/examples/images/tooltips_9.png
new file mode 100644
index 00000000000..cb39e47bcea
Binary files /dev/null and b/docs/examples/images/tooltips_9.png differ
diff --git a/docs/examples/jupyter-notebooks/tooltip_config.ipynb b/docs/examples/jupyter-notebooks/tooltip_config.ipynb
index ececdd06e4f..38686e2f741 100644
--- a/docs/examples/jupyter-notebooks/tooltip_config.ipynb
+++ b/docs/examples/jupyter-notebooks/tooltip_config.ipynb
@@ -39,36 +39,15 @@
"data": {
"text/html": [
"\n",
- "
\n",
" \n",
+ " \n",
" "
]
},
@@ -115,7 +94,7 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
"execution_count": 4,
@@ -184,7 +163,7 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
"execution_count": 5,
@@ -254,7 +233,7 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
"execution_count": 6,
@@ -331,7 +310,7 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
"execution_count": 7,
@@ -407,7 +386,7 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
"execution_count": 8,
@@ -503,7 +482,171 @@
{
"data": {
"text/html": [
- " \n",
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# List of variables to place in a multiline tooltip with the default formatting\n",
+ "p + geom_point(shape=21, \n",
+ " color='white',\n",
+ " tooltips=layer_tooltips(['manufacturer', 'model', 'class', 'year'])) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " "
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Define the format for the variable from the list and specify an additional line\n",
+ "p + geom_point(shape=21, \n",
+ " color='white',\n",
+ " tooltips=layer_tooltips(['manufacturer', 'model', 'class', 'drv'])\n",
+ " .format('drv', '{}wd')\n",
+ " .line('cty/hwy [mpg]|@cty/@hwy')\n",
+ " ) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 9,
+ "execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
@@ -605,13 +748,13 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 10,
+ "execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
@@ -679,13 +822,13 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 11,
+ "execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
@@ -768,13 +911,13 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 12,
+ "execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
@@ -861,19 +1004,19 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 13,
+ "execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
@@ -953,7 +1096,7 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
@@ -963,13 +1106,13 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": 17,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 15,
+ "execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
@@ -1030,13 +1173,13 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": 18,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 16,
+ "execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
@@ -1107,13 +1250,13 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": 19,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
- " \n",
+ " \n",
" "
],
"text/plain": [
- ""
+ ""
]
},
- "execution_count": 17,
+ "execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
@@ -1202,9 +1345,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.9"
+ "version": "3.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 4
-}
+}
\ No newline at end of file
diff --git a/docs/tooltips.md b/docs/tooltips.md
new file mode 100644
index 00000000000..1fb8840f5b7
--- /dev/null
+++ b/docs/tooltips.md
@@ -0,0 +1,46 @@
+# Tooltip Customization
+
+- [Tooltip variable list](#variables)
+ - [Examples](#example-variables)
+
+------
+
+
+The following functions set lines, define formatting of the tooltip, its location and width:
+```
+tooltips=layer_tooltips(variables)
+ .format(field, format)
+ .line(template)
+ .anchor(position)
+ .min_width(value)
+```
+
+
+### Tooltip variable list: `layer_tooltips(variables = ['varName1', ... , 'varNameN'] )`
+The `variables` parameter defines a list of variable names, which values will be placed line by line in the general tooltip.
+If formatting is specified for a variable from this list (with the `format` function), it will be applied.
+Otherwise, the default formatting is used.
+Additional tooltip lines can be specified using the `line` functions.
+
+
+
+### Examples
+
+Set list of variables to place them in a multiline tooltip with the default formatting:
+
+```
+ggplot(mpg) + geom_point(aes(x='displ', y='cty', fill='drv', size='hwy'), shape=21, color='black',
+ tooltips=layer_tooltips(['manufacturer', 'model', 'class', 'drv']))
+```
+![](examples/images/tooltips_8.png)
+
+
+Define the format for the variable from the list and specify an additional line:
+```
+ggplot(mpg) + geom_point(aes(x='displ', y='cty', fill='drv', size='hwy'), shape=21, color='black',
+ tooltips=layer_tooltips(['manufacturer', 'model', 'class', 'drv'])
+ .format('drv', '{}wd')
+ .line('cty/hwy [mpg]|@cty/@hwy'))
+```
+
+![](examples/images/tooltips_9.png)
diff --git a/future_changes.md b/future_changes.md
index aaf060d6128..e50e3567770 100644
--- a/future_changes.md
+++ b/future_changes.md
@@ -2,6 +2,10 @@
### Added
+- In tooltip customization API:
+ - `layer_tooltips(variables)` - the new parameter `variables` defines a list of variable names, which values will be placed line by line in the general tooltip.
+ See: [Tooltip Customization](https://github.com/JetBrains/lets-plot/blob/master/docs/tooltips.md).
+
### Changed
### Fixed
diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt
index 3e910ee32a0..ca5d2eb2d5a 100644
--- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt
+++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/Option.kt
@@ -85,6 +85,7 @@ object Option {
const val SHOW_LEGEND = "show_legend"
const val TOOLTIPS = "tooltips"
const val TOOLTIP_LINES = "tooltip_lines"
+ const val TOOLTIP_VARIABLES = "tooltip_variables"
const val TOOLTIP_FORMATS = "tooltip_formats"
const val TOOLTIP_ANCHOR = "tooltip_anchor"
const val TOOLTIP_MIN_WIDTH = "tooltip_min_width"
diff --git a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/TooltipConfig.kt b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/TooltipConfig.kt
index 015056f60fd..28a4bb0328a 100644
--- a/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/TooltipConfig.kt
+++ b/plot-config-portable/src/commonMain/kotlin/jetbrains/datalore/plot/config/TooltipConfig.kt
@@ -30,13 +30,15 @@ class TooltipConfig(
} else {
null
},
- tooltipFormats = getList(Option.Layer.TOOLTIP_FORMATS)
+ tooltipFormats = getList(Option.Layer.TOOLTIP_FORMATS),
+ tooltipVariables = getStringList(Option.Layer.TOOLTIP_VARIABLES)
).parse()
}
private inner class TooltipConfigParseHelper(
private val tooltipLines: List?,
- tooltipFormats: List<*>
+ tooltipFormats: List<*>,
+ tooltipVariables: List
) {
// Key is Pair: +
private val myValueSources: MutableMap, ValueSource> = prepareFormats(tooltipFormats)
@@ -44,11 +46,22 @@ class TooltipConfig(
createValueSource(fieldName = field.first, isAes = field.second, format = format)
}.toMutableMap()
+ // Create tooltip lines from the given variable list
+ private val myLinesForVariableList = tooltipVariables.map { variableName ->
+ val valueSource = getValueSource(VARIABLE_NAME_PREFIX + variableName)
+ TooltipLine.defaultLineForValueSource(valueSource)
+ }
+
internal fun parse(): TooltipSpecification {
val lines = tooltipLines?.map(::parseLine)
+ val allTooltipLines = when {
+ lines != null -> myLinesForVariableList + lines
+ myLinesForVariableList.isNotEmpty() -> myLinesForVariableList
+ else -> null
+ }
return TooltipSpecification(
myValueSources.map { it.value },
- lines,
+ allTooltipLines,
TooltipSpecification.TooltipProperties(
anchor = readAnchor(),
minWidth = readMinWidth(),
diff --git a/plot-config/src/jvmTest/kotlin/plot/config/TooltipConfigTest.kt b/plot-config/src/jvmTest/kotlin/plot/config/TooltipConfigTest.kt
index bd3ac70b3d0..1e30b343d1a 100644
--- a/plot-config/src/jvmTest/kotlin/plot/config/TooltipConfigTest.kt
+++ b/plot-config/src/jvmTest/kotlin/plot/config/TooltipConfigTest.kt
@@ -14,6 +14,7 @@ import jetbrains.datalore.plot.config.Option.Layer.GEOM
import jetbrains.datalore.plot.config.Option.Layer.TOOLTIPS
import jetbrains.datalore.plot.config.Option.Layer.TOOLTIP_FORMATS
import jetbrains.datalore.plot.config.Option.Layer.TOOLTIP_LINES
+import jetbrains.datalore.plot.config.Option.Layer.TOOLTIP_VARIABLES
import jetbrains.datalore.plot.config.Option.Meta.KIND
import jetbrains.datalore.plot.config.Option.Plot.LAYERS
import jetbrains.datalore.plot.config.Option.PlotBase.DATA
@@ -44,7 +45,6 @@ class TooltipConfigTest {
Aes.SHAPE.name to "class"
)
-
@Test
fun defaultTooltips() {
val geomLayer = buildGeomPointLayer(data, mapping, tooltips = null)
@@ -517,6 +517,82 @@ class TooltipConfigTest {
)
}
+ @Test
+ fun `variables list to place in a tooltip with default formatting`() {
+ val tooltipConfig = mapOf(
+ TOOLTIP_VARIABLES to listOf(
+ "model name",
+ "class",
+ "displ",
+ "hwy",
+ "origin"
+ )
+ )
+ val geomLayer = buildGeomPointLayer(data, mapping, tooltips = tooltipConfig)
+ val expectedLines = listOf(
+ "model name: dodge",
+ "class: suv",
+ "displ: 1.6",
+ "hwy: 160.0",
+ "origin: US"
+
+ )
+ val lines = getGeneralTooltipStrings(geomLayer)
+ assertTooltipStrings(expectedLines, lines)
+ }
+
+ @Test
+ fun `variables list should use the defined formatting`() {
+ val tooltipConfig = mapOf(
+ TOOLTIP_VARIABLES to listOf(
+ "model name",
+ "class",
+ "displ",
+ "hwy",
+ "origin"
+ ),
+ TOOLTIP_FORMATS to listOf(
+ mapOf(
+ FIELD to "hwy",
+ FORMAT to "{.2f} mpg"
+ )
+ )
+ )
+ val geomLayer = buildGeomPointLayer(data, mapping, tooltips = tooltipConfig)
+ val expectedLines = listOf(
+ "model name: dodge",
+ "class: suv",
+ "displ: 1.6",
+ "hwy: 160.00 mpg",
+ "origin: US"
+ )
+ val lines = getGeneralTooltipStrings(geomLayer)
+ assertTooltipStrings(expectedLines, lines)
+ }
+
+ @Test
+ fun `tooltip lines should be formed by variables list and line functions`() {
+ val tooltipConfig = mapOf(
+ TOOLTIP_VARIABLES to listOf(
+ "model name",
+ "class",
+ "displ"
+ ),
+ TOOLTIP_LINES to listOf(
+ "@|@hwy mpg"
+ )
+ )
+ val geomLayer = buildGeomPointLayer(data, mapping, tooltips = tooltipConfig)
+ val expectedLines = listOf(
+ "model name: dodge",
+ "class: suv",
+ "displ: 1.6",
+ "hwy: 160.0 mpg"
+ )
+ val lines = getGeneralTooltipStrings(geomLayer)
+ assertTooltipStrings(expectedLines, lines)
+ }
+
companion object {
private fun buildGeomPointLayer(
diff --git a/python-package/lets_plot/plot/tooltip.py b/python-package/lets_plot/plot/tooltip.py
index 839e1043916..e23993d7d8f 100644
--- a/python-package/lets_plot/plot/tooltip.py
+++ b/python-package/lets_plot/plot/tooltip.py
@@ -3,7 +3,7 @@
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
from .core import FeatureSpec
-from typing import List, Dict
+from typing import List
#
# Tooltips
@@ -68,13 +68,20 @@ class layer_tooltips(FeatureSpec):
"""
- def __init__(self):
- """Initialize self."""
+ def __init__(self, variables: List[str] = None):
+ """
+ Initialize self.
+
+ :param variables: List of strings
+ Variable names to place in the general tooltip with default formatting.
+ """
+
self._tooltip_formats: List = []
self._tooltip_lines: List = None
self._tooltip_anchor = None
self._tooltip_min_width = None
self._tooltip_color = None
+ self._tooltip_variables = variables
super().__init__('tooltips', name=None)
def as_dict(self):
@@ -107,6 +114,7 @@ def as_dict(self):
d['tooltip_anchor'] = self._tooltip_anchor
d['tooltip_min_width'] = self._tooltip_min_width
d['tooltip_color'] = self._tooltip_color
+ d['tooltip_variables'] = self._tooltip_variables
return d
def format(self, field=None, format=None):