The code below comes from a jupyter notebook:
from bokeh.io import show, output_notebook
from bokeh.plotting import ColumnDataSource, figure
from bokeh.models import HoverTool, Range1d
output_notebook()
fig = figure(tools=[HoverTool(tooltips=[("html", '@html{safe}')])])
fig.quad(left="left", top="top", bottom="bottom", right="right",
source=ColumnDataSource({"left": [1,3], "bottom": [1,3],
"right": [2,4], "top": [2,4],
"html":["<b>I'm bold</b>", "<span
style='color:red;font-size:32px;'>BIG RED TEXT</span>"]}))
show(fig)
I need to make the HoverTool tooltips stick to exactly where they are on a clicking the point, so if a user wanted to highlight the and copy the text in the tooltip they could. This codepen has an example of the type of behavior I would like to see. I know that this must be possible by either injecting some type of CustomJS or altering BokehJS coffescript and building BokehJS from scratch but I haven't been able to figure it out. Does anybody out there have any idea how to do this?
UPDATE:
It might be possible to a create a custom tool using the tap_tool.coffee, hover_tool.coffee or tooltip.coffee. I'll update this if I figure it out.
This is a workaround creating your own Hover text using models.Label inside the 'callback' function of models.HoverTool . Also models.TapTool is used for toggle updating the Label text. You can't edit the text in the glyph models.Label, but an TextInput widget has been added where the text is updated as one hover the graph glyphs.
import bokeh
import bokeh.plotting
fig = bokeh.plotting.figure()
d_source = bokeh.models.ColumnDataSource({"left": [1,3,1],
"bottom": [1,3,3],"right": [2,4,2],"top": [2,4,4]})
h_source = bokeh.models.ColumnDataSource({"do_hover":[True,True] })
fig.quad(left="left", top="top", bottom="bottom", right="right",
source=d_source)
myToolTip = bokeh.models.Label(x=2.5,y=2.5, text="",
background_fill_color = "#ffffff")
HoverCallback = bokeh.models.CustomJS(args=dict(d_source=d_source,
myToolTip=myToolTip,h_source=h_source),code="""
function findWhereIam(x,y,s){
// To find where the cursor is.
selection = -1;
for (i = 0; i < s.data.left.length; i++) {
x0 = s.data.left[i];
x1 = s.data.right[i];
y0 = s.data.bottom[i];
y1 = s.data.top[i];
if (x>x0 && x<x1 && y>y0 && y<y1){
// It's inside rectangle!!!
selection = i;
}
}
return selection
}
if (h_source.data.do_hover[0]){
x_data = cb_data['geometry'].x;
y_data = cb_data['geometry'].y;
var selection = findWhereIam(x_data,y_data,d_source)
if (selection>=0){
x0 = d_source.data.left[selection];
x1 = d_source.data.right[selection];
y0 = d_source.data.bottom[selection];
y1 = d_source.data.top[selection];
myToolTip.x = 0.5 * (x0+x1);
myToolTip.y = 0.5 * (y0+y1);
myToolTip.text = "on:"+selection;
myToolTip.text_font_size = "24pt";
myToolTip.background_fill_color = "#ffffff";
myToolTip.border_line_color = "#000000";
myToolTip.text_align = "center";
myToolTip.trigger('change');
current_selection.value = myToolTip.text;
current_selection.trigger('change');
}else{
myToolTip.text = ""; //erase
myToolTip.trigger('change');
current_selection.value = myToolTip.text;
current_selection.trigger('change');
}
}
""")
TapCallback = bokeh.models.CustomJS(args=dict(h_source=h_source), code="""
h_source.data.do_hover[0] = !h_source.data.do_hover[0];
""")
current_selection = bokeh.models.widgets.TextInput(value="",
title="Selection")
HoverCallback.args.update(dict(current_selection=current_selection))
fig.add_tools(bokeh.models.HoverTool(tooltips=None,callback=HoverCallback))
fig.add_tools(bokeh.models.TapTool(callback=TapCallback))
fig.add_layout(myToolTip)
page = bokeh.plotting.gridplot([[fig],[current_selection]])
bokeh.io.output_notebook()
bokeh.io.show(page)

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With