Search code examples
pythonplotlytooltip

plotly interactive tooltip / hover text / popup


Tooltips of a figure are only displayed while hovering over the data point: https://plotly.com/python/hover-text-and-formatting

I'd like to have an easy way to customize the duration the tooltip is displayed after hovering over it or possibly display the tooltip permanently when clicking the data point.

This will allow me to include clickable links in the tooltip.

For data tables you can customize the tooltip display duration, but I don't see a similar option for figures: https://dash.plotly.com/datatable/tooltips

I think you can add your own tooltips via the event system or maybe change the css style of the resulting HTML somehow, but that seems to be overkill. I'd still accept an answer with a working example.


Solution

  • Here is a more complex solution that prevents the tooltip to fade out while you hover over it. For that you need an external css file with a css animation.

    See https://stackoverflow.com/a/75277310/2474025 for a more simple solution.

    assets/my.css:

    
    .hideclass:not(:hover) {
      animation: hideanimation 5s ease-in;
      animation-fill-mode: forwards;
    }
    .showclass {
      animation: showanimation 5s ease-out;
      animation-fill-mode: forwards;
    }
    
    
    @keyframes showanimation {
      0% {
        opacity: 0;
      }
      25% {
        opacity: 0.4;
      }
      50% {
        opacity: 0.7;
      }
    
      75% {
        opacity: 0.8;
      }
      100% {
        opacity: 1;
      }
    }
    
    
    @keyframes hideanimation {
      0% {
        opacity: 1;
      }
      25% {
        opacity: 0.5;
      }
      50% {
        opacity: 0;
      }
    
      75% {
        opacity: 0;
        max-width: 0;
        max-height: 0;
        overflow: hidden;
      }
      100% {
        opacity: 0;
        max-width: 0;
        max-height: 0;
        overflow: hidden;
      }
    }
    

    Python notebook with the following code needs to be in the same directory as the assets folder:

    
    from dash import Dash, dcc, html, Input, Output, no_update
    import plotly.express as px
    
    df = px.data.tips()
    
    fig = px.scatter(df, x='total_bill', y='tip')
    
    # Turn off native plotly.js hover effects - make sure to use
    # hoverinfo='none' rather than 'skip' which also halts events.
    fig.update_traces(hoverinfo='none', hovertemplate=None)
    
    # Hover distance defaults to 20 and means call hover event if mouse pointer is within x units close to the node.
    fig.update_layout(hoverdistance=5)
    
    app = Dash(__name__)
    
    app.layout = html.Div(
        [
            html.Link(rel='stylesheet', href='/assets/my.css'),
            # clear on unhover means the hover callback is called with hoverDate=None when moving away from the point.log_queue
            dcc.Graph(
                id='graph-basic-2',
                figure=fig,
                clear_on_unhover=True,
            ),
            html.Div(
                id='graph-tooltip',
                className='dash-bootstrap',
            ),
        ],
        className='dash-bootrstrap',
    )
    
    previous_style = None
    
    
    @app.callback(
        Output('graph-tooltip', 'style'),
        Output('graph-tooltip', 'className'),
        Output('graph-tooltip', 'children'),
        Input('graph-basic-2', 'hoverData'),
        prevent_initial_call=True,
    )
    def display_hover(hoverData):
        global previous_style
        if hoverData is None:
            return no_update, 'hideclass', no_update
    
        print('display')
    
        # demo only shows the first point, but other points may also be available
        pt = hoverData['points'][0]
        bbox = pt['bbox']
    
        children = [
            html.A('Link to external site 1', href='https://plot.ly', target='_blank'),
            html.Br(),
            html.A('Link to external site 2', href='https://plot.ly', target='_blank'),
        ]
        previous_style = {
            'position': 'absolute',
            'left': f'{bbox["x1"] + 20}px',
            'top': f'{bbox["y1"] + 20}px',
            'background-color': 'rgba(100, 100, 100, 0.8)',
            'padding': '1em',
        }
        return previous_style, 'showclass', children
    
    
    # if __name__ == '__main__':
    app.run_server(
        dev_tools_hot_reload=True,
    )