holoviz / panel

Panel: The powerful data exploration & web app framework for Python
https://panel.holoviz.org
BSD 3-Clause "New" or "Revised" License
4.62k stars 504 forks source link

Please add tooltips to panes and widgets #1298

Open MarcSkovMadsen opened 4 years ago

MarcSkovMadsen commented 4 years ago

From time to time I would really like to show a tooltip to the user. It could be on a button, an image or something third.

It's also requested by this post on discourse https://discourse.holoviz.org/t/is-it-possible-to-include-hover-information-over-a-pane/545

maximlt commented 4 years ago

Yes, that's really a classic element of a UI that is missing.

sdc50 commented 1 year ago

@philippjfr it looks like tooltips are supported in Bokeh 3. Do you know if Panel will also support them once it's compatible with Bokeh 3.x?

philippjfr commented 1 year ago

Yep, it's already implemented on branch-1.0, unfortunately it is not yet implemented for all widgets.

hoxbro commented 1 year ago

The current status of description support for widgets is shown in the table below. I think a handful more can be checked off by giving StaticText a description and using it for composite sliders like the editable sliders.

Type Support description
ArrayInput :heavy_check_mark:
AutocompleteInput :heavy_check_mark:
Button :heavy_check_mark:
CheckBoxGroup :x:
CheckButtonGroup :x:
Checkbox :x:
CodeEditor :x:
ColorPicker :heavy_check_mark:
CrossSelector :heavy_check_mark:
DataFrame :x:
DatePicker :heavy_check_mark:
DateRangeSlider :x:
DateSlider :x:
DatetimeInput :heavy_check_mark:
DatetimePicker :heavy_check_mark:
DatetimeRangeInput :x:
DatetimeRangePicker :heavy_check_mark:
DatetimeRangeSlider :x:
DiscretePlayer :x:
DiscreteSlider :x:
EditableFloatSlider :x:
EditableIntSlider :x:
EditableRangeSlider :x:
FileDownload :heavy_check_mark:
FileInput :heavy_check_mark:
FileSelector :x:
FloatInput :heavy_check_mark:
FloatSlider :x:
IntInput :heavy_check_mark:
IntRangeSlider :x:
IntSlider :x:
JSONEditor :x:
LiteralInput :heavy_check_mark:
MenuButton :x:
MultiChoice :heavy_check_mark:
MultiSelect :heavy_check_mark:
NumberInput :heavy_check_mark:
PasswordInput :heavy_check_mark:
Player :x:
RadioBoxGroup :x:
RadioButtonGroup :x:
RangeSlider :x:
Select :heavy_check_mark:
Spinner :heavy_check_mark:
StaticText :x:
Tabulator :x:
TextAreaInput :heavy_check_mark:
TextEditor :x:
TextInput :heavy_check_mark:
Toggle :x:
ToggleGroup :x:
VideoStream :x:
Code used ``` python import datetime as dt import pandas as pd import panel as pn from panel.widgets import * from panel.widgets import __all__ as wlist from panel.widgets.indicators import __all__ as ilist pn.extension("codeeditor", "jsoneditor", "tabulator", "texteditor") # terminal o = {"description": "Test"} oo = dict(**o, options=list("ABC")) do = dict(**o, start=dt.datetime(2020, 1, 1), end=dt.datetime(2020, 1, 2)) df = dict(**o, value=pd.DataFrame(range(10))) widgets = ( ArrayInput(name="ArrayInput", **o), AutocompleteInput(name="AutocompleteInput", **o), Button(name="Button", **o), Checkbox(name="Checkbox", **o), CheckBoxGroup(name="CheckBoxGroup", **oo), CheckButtonGroup(name="CheckButtonGroup", **oo), CodeEditor(name="CodeEditor", **o), ColorPicker(name="ColorPicker", **o), CrossSelector(name="CrossSelector", **o), DataFrame(name="DataFrame", **df), DatePicker(name="DatePicker", **o), DateRangeSlider(name="DateRangeSlider", **do), DatetimePicker(name="DatetimePicker", **do), DatetimeRangeSlider(name="DatetimeRangeSlider", **do), DateSlider(name="DateSlider", **do), DatetimeInput(name="DatetimeInput", **do), DatetimeRangeInput(name="DatetimeRangeInput", **do), StaticText(name="StaticText", value="Test", **o), DatetimeRangePicker(name="DatetimeRangePicker", **do), # Debugger(name="Debugger", **o), DiscretePlayer(name="DiscretePlayer", **o), DiscreteSlider(name="DiscreteSlider", **oo), EditableFloatSlider(name="EditableFloatSlider", **o), EditableIntSlider(name="EditableIntSlider", **o), EditableRangeSlider(name="EditableRangeSlider", **o), FileDownload(name="FileDownload", **o), FileInput(name="FileInput", **o), FileSelector(name="FileSelector", **o), FloatInput(name="FloatInput", **o), FloatSlider(name="FloatSlider", **o), IntInput(name="IntInput", **o), IntRangeSlider(name="IntRangeSlider", **o), IntSlider(name="IntSlider", **o), JSONEditor(name="JSONEditor", **o), LiteralInput(name="LiteralInput", **o), MenuButton(name="MenuButton", **o), MultiChoice(name="MultiChoice", **o), MultiSelect(name="MultiSelect", **o), NumberInput(name="NumberInput", **o), PasswordInput(name="PasswordInput", **o), Player(name="Player", **o), RadioBoxGroup(name="RadioBoxGroup", **oo), RadioButtonGroup(name="RadioButtonGroup", **oo), RangeSlider(name="RangeSlider", **o), Select(name="Select", **o), Spinner(name="Spinner", **o), Tabulator(name="Tabulator", **df), # Terminal(name="Terminal", **o), TextAreaInput(name="TextAreaInput", **o), TextEditor(name="TextEditor", **o), TextInput(name="TextInput", **o), Toggle(name="Toggle", **o), ToggleGroup(name="ToggleGroup", **oo), VideoStream(name="VideoStream", **o), ) widgets = sorted(widgets, key=lambda x: x.name) layout = [] for w in widgets: info = f"{w.name}
Description: {'description' in w.param}" l = pn.Row(pn.pane.HTML(info, width=300), w) layout += [l, pn.layout.Divider()] pn.Column(*layout) # Cell 2 - Not included print(set(wlist) - set(ilist) - set(w.name for w in widgets)) # Cell 3 - Generate table for w in widgets: print(f"|{w.name}\t|{':heavy_check_mark:' if 'description' in w.param else ':x:'}|") ``` # Not included: - Debugger / Terminal because it Terminal does not work with `inline` in Notebook. - Ace same as CodeEditor. - Others are not relevant, as far as I can see. ``` {'Ace', 'CompositeWidget', 'Debugger', 'Grammar', 'GrammarList', 'SpeechToText', 'Terminal', 'TextToSpeech', 'Utterance', 'Voice', 'Widget'} ```
MarcSkovMadsen commented 1 year ago

A tooltip on the button would make a big difference. Clicking the button often have the biggest impact, so it's nice to know beforehand what it's does.

It should probably not have the icon though.

johann-petrak commented 4 months ago

Is there any hope to make showing the "description" parameter as a Tooltip for DateRangeSlider, FloatSlider, CheckBoxGroup? Is there a workaround for how to do it myself until then?

hoxbro commented 4 months ago

I have run my code again, and Button now has a description on hover pn.widgets.Button(name='Test', description='Text') image

A way to add tooltips to widgets that do not have description is to use pn.widgets.TooltipIcon and play a bit around with margins.

image

pn.Row(
    pn.widgets.DatetimeRangeSlider(name="DatetimeRangeSlider", **do),
    pn.widgets.TooltipIcon(value="Test", styles={"margin-top": "20px"}),
)
Type Support description
ArrayInput :heavy_check_mark:
AutocompleteInput :heavy_check_mark:
Button :heavy_check_mark:
CheckBoxGroup :x:
CheckButtonGroup :heavy_check_mark:
Checkbox :x:
CodeEditor :x:
ColorPicker :heavy_check_mark:
CrossSelector :heavy_check_mark:
DataFrame :x:
DatePicker :heavy_check_mark:
DateRangeSlider :x:
DateSlider :x:
DatetimeInput :heavy_check_mark:
DatetimePicker :heavy_check_mark:
DatetimeRangeInput :x:
DatetimeRangePicker :heavy_check_mark:
DatetimeRangeSlider :x:
DiscretePlayer :x:
DiscreteSlider :x:
EditableFloatSlider :x:
EditableIntSlider :x:
EditableRangeSlider :x:
FileDownload :heavy_check_mark:
FileInput :heavy_check_mark:
FileSelector :x:
FloatInput :heavy_check_mark:
FloatSlider :x:
IntInput :heavy_check_mark:
IntRangeSlider :x:
IntSlider :x:
JSONEditor :x:
LiteralInput :heavy_check_mark:
MenuButton :x:
MultiChoice :heavy_check_mark:
MultiSelect :heavy_check_mark:
NumberInput :heavy_check_mark:
PasswordInput :heavy_check_mark:
Player :x:
RadioBoxGroup :x:
RadioButtonGroup :heavy_check_mark:
RangeSlider :x:
Select :heavy_check_mark:
Spinner :heavy_check_mark:
StaticText :x:
Tabulator :x:
TextAreaInput :heavy_check_mark:
TextEditor :x:
TextInput :heavy_check_mark:
Toggle :x:
ToggleGroup :heavy_check_mark:
VideoStream :x:
Code ``` python import datetime as dt import pandas as pd import panel as pn from panel.widgets import * from panel.widgets import __all__ as wlist from panel.widgets.indicators import __all__ as ilist pn.extension("codeeditor", "jsoneditor", "tabulator", "texteditor") # terminal o = {} oo = dict(**o, options=list("ABC")) do = dict(**o, start=dt.datetime(2020, 1, 1), end=dt.datetime(2020, 1, 2)) df = dict(**o, value=pd.DataFrame(range(10))) widgets = ( ArrayInput(name="ArrayInput", **o), AutocompleteInput(name="AutocompleteInput", **o), Button(name="Button", **o), Checkbox(name="Checkbox", **o), CheckBoxGroup(name="CheckBoxGroup", **oo), CheckButtonGroup(name="CheckButtonGroup", **oo), CodeEditor(name="CodeEditor", **o), ColorPicker(name="ColorPicker", **o), CrossSelector(name="CrossSelector", **o), DataFrame(name="DataFrame", **df), DatePicker(name="DatePicker", **o), DateRangeSlider(name="DateRangeSlider", **do), DatetimePicker(name="DatetimePicker", **do), DatetimeRangeSlider(name="DatetimeRangeSlider", **do), DateSlider(name="DateSlider", **do), DatetimeInput(name="DatetimeInput", **do), DatetimeRangeInput(name="DatetimeRangeInput", **do), StaticText(name="StaticText", value="Test", **o), DatetimeRangePicker(name="DatetimeRangePicker", **do), # Debugger(name="Debugger", **o), DiscretePlayer(name="DiscretePlayer", **o), DiscreteSlider(name="DiscreteSlider", **oo), EditableFloatSlider(name="EditableFloatSlider", **o), EditableIntSlider(name="EditableIntSlider", **o), EditableRangeSlider(name="EditableRangeSlider", **o), FileDownload(name="FileDownload", **o), FileInput(name="FileInput", **o), FileSelector(name="FileSelector", **o), FloatInput(name="FloatInput", **o), FloatSlider(name="FloatSlider", **o), IntInput(name="IntInput", **o), IntRangeSlider(name="IntRangeSlider", **o), IntSlider(name="IntSlider", **o), JSONEditor(name="JSONEditor", **o), LiteralInput(name="LiteralInput", **o), MenuButton(name="MenuButton", **o), MultiChoice(name="MultiChoice", **o), MultiSelect(name="MultiSelect", **o), NumberInput(name="NumberInput", **o), PasswordInput(name="PasswordInput", **o), Player(name="Player", **o), RadioBoxGroup(name="RadioBoxGroup", **oo), RadioButtonGroup(name="RadioButtonGroup", **oo), RangeSlider(name="RangeSlider", **o), Select(name="Select", **o), Spinner(name="Spinner", **o), Tabulator(name="Tabulator", **df), # Terminal(name="Terminal", **o), TextAreaInput(name="TextAreaInput", **o), TextEditor(name="TextEditor", **o), TextInput(name="TextInput", **o), Toggle(name="Toggle", **o), ToggleGroup(name="ToggleGroup", **oo), VideoStream(name="VideoStream", **o), ) widgets = sorted(widgets, key=lambda x: x.name) layout = [] for w in widgets: info = f"{w.name}
Description: {'description' in w.param}" l = pn.Row(pn.pane.HTML(info, width=300), w) layout += [l, pn.layout.Divider()] pn.Column(*layout) # Cell 2 - Not included print(set(wlist) - set(ilist) - set(w.name for w in widgets)) # Cell 3 - Generate table print("| Type | Support description |") print("| --- | --- |") for w in widgets: print(f"|{w.name}\t|{':heavy_check_mark:' if 'description' in w.param else ':x:'}|") ```
johann-petrak commented 4 months ago

Thanks!

However, when I try to show a tooltip for the Button widget it simply does not work. I am using panel version 1.2.3.

The problem I initially encountered with putting a TooltipIcon into the same row is that it is not shown after the Widget name, like for other widgets. If I just allow for the space the icon needs, and the widget is in a row inside of a WidgetBox, then by default no tooltip is shown for TooltipIcons which are at the far right of the row, because, the tooltip is always shown to the right of the icon, and not automatically moved to the left in case there is no space to the right.

However this can be solved by using a bokeh Tooltip inside of the TooltipIcon and specifying position "left":

date_range_tt = pn.widgets.TooltipIcon(
        value=Tooltip(
            content="the content .... ",
            position="left",),
        styles={"margin-top": "20px"},
        width=10,
    )