Is it possible to make tooltips clickable URL?

I am new with Bokeh and try to make data in the tooltips clickable hyperlink to a website. I found OpenURL example, but this one is only for tapping a point on the map. In the example below, I would like to make the name of the period and capital clickable, so that a click opens a new tab in the browser to show a Wikipedia webpage.

Current code looks like below and I would like to make Capital

China = x_range,y_range = ((7827151.696402039,14636773.672271816), (7377090.428741338,2093763.0722344585))

p = figure(tools='pan, wheel_zoom', x_range=x_range, y_range=y_range, 
           x_axis_type="mercator", y_axis_type="mercator")
p.add_tile(WMTSTileSource(url=url, attribution=attribution))
hover =HoverTool()
hover.tooltips = [
    ('', '@{Period Label}'),
    ('Start Date', '@Inception'),
    ('Capital', '@{Capital Label}'),
    ('Capital Coordinates', '@{Capital Coordinates}'),  
]
p.tools.append(hover)
p.circle(x='Capital Mercator X', y='Capital Mercator Y', fill_color='red', size=10, source=df)
show(p)

Legends have built-in predefined actions for click events

It is not currently possible to add custom actions (e.g. opening a URL) for legend clicks.

1 Like

Your example doesn’t use a Legend at all, but rather a hovertool…

You can certainly build a custom html tooltip for your hovertool that has hyperlinks attached:

from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool, CustomJSHover

src = ColumnDataSource(data={'x':[0,1,2],'y':[2,3,1],'link':['https://google.com','https://reddit.com','https://twitter.com']})

tt = '''
    <div>
        <a [email protected]{safe}}>@link{safe}</a>
    </div>
    '''
f = figure()
r = f.scatter(x='x',y='y',source=src)
hvr = HoverTool(renderers=[r],tooltips=tt)
f.add_tools(hvr)
show(f)

I mostly took this from the guide → Configuring plot tools — Bokeh 2.4.3 Documentation

However, this won’t accomplish what I think you seek because users won’t be able to click on the hyperlink in the tooltip because as soon as they move the mouse to go click on it, the hover will disappear. Trawling through github discussions/issues, it appears there is currently no “easy” way to make a HoverTool “sticky” (Feature Request: HoverTool tooltips stick to a point on click. · Issue #5724 · bokeh/bokeh · GitHub).

One proposed workaround for this would be to implement the above, but also add a TapTool with a callback that retrieves the url string from the tapped glyph, and window.open (see Window.open() - Web APIs | MDN) to open it. This way you can open the url link stored in the CDS:

from bokeh.plotting import figure, show, save
from bokeh.models import ColumnDataSource, HoverTool, TapTool, CustomJS

src = ColumnDataSource(data={'x':[0,1,2],'y':[2,3,1],'link':['https://google.com','https://reddit.com','https://twitter.com']})

toolt = '''
    <div>
        <a [email protected]{safe}}>@link{safe}</a>
    </div>
    '''
f = figure()
r = f.scatter(x='x',y='y',source=src)
hvr = HoverTool(renderers=[r],tooltips=toolt)
f.add_tools(hvr)

tap_cb = CustomJS(code='''
                  var l = cb_data.source.data['link'][cb_data.source.inspected.indices[0]]
                  window.open(l)
                  ''')
tapt = TapTool(renderers=[r],callback=tap_cb,behavior='inspect')
f.add_tools(tapt)

show(f)
2 Likes

Thank you for your very useful info and code. Yes, you got me right. Sorry, “legend” in my question was my confusion. It should be read as “tooltips”. So, now my question (above) was already edited to correct my mistake.

It is a pity that such a simple yet valuable function is not available out of the box. Your workaround is very close, but as you know, it is not exactly what I want. If the feature is implemented, there could be multiple links in tooltips (e.g period, capital), your solution is surely not enough. So, Let’s hope this could become a feature in the near future. Thanks again!

Thanks for your reference. Although it was not about legend (see another reply), it is very useful for me too. Thanks!

Got a final potential workaround for you. Idea is to mimic a hovertool with a datatable that’s off to the side of the figure. The table runs on a CDSView/IndexFilter that will filter the source down to the last hovered index in the figure. From there it’s just figuring out the HTMLTemplateFormatter syntax to make the datatable a hyperlink. This setup will allow you to embed multiple clickable hyperlinks for each item in your CDS.

from bokeh.plotting import figure, save
from bokeh.models import ColumnDataSource, HoverTool, CustomJS, DataTable, TableColumn, CDSView, IndexFilter, HTMLTemplateFormatter
from bokeh.layouts import row

src = ColumnDataSource(data={'x':[0,1,2],'y':[2,3,1],'link':['https://google.com','https://reddit.com','https://twitter.com']})

#filter that runs on the last hovered index
filt = IndexFilter(indices=[])
view = CDSView(source=src,filters=[filt])

#formatting the table to display as hyperlink
fmt = HTMLTemplateFormatter(template='<a href="<%= value %>" target="_blank" rel="noopener noreferrer"><%= value %></a>')

#make the table, additing this formatter to the link
cols = [TableColumn(title='Link',field='link',formatter=fmt)]
#table's view has the filter applied
tbl = DataTable(columns=cols,view=view,source=src)

f = figure()
r = f.scatter(x='x',y='y',source=src)

#if the inspected indices are greater than zero, want to update the filter to include that index
hvr_cb = CustomJS(args=dict(src=src,view=view,filt=filt)
                         ,code="""
                         if (src.inspected.indices.length>0){
                             //get first inspected indices                        
                             var ind = src.inspected.indices[0]
                             //update filter to that index
                             filt.indices = [ind]
                             console.log(filt.indices)
                             //update view
                             src.change.emit()
                             }
                        """)
#add that callback to the hover
hvr = HoverTool(renderers=[r],tooltips=[('','@link')],callback=hvr_cb)
f.add_tools(hvr)

save(row([f,tbl]),'tbl_test.html')

tbl_tip