update hovertool in callback

I have a plot with checkboxes to toggle line visibility.

I would also like to turn off the hovering on the invisible lines. I am not sure how to proceed, I tried to update the “names” attribute of the HoverTool. The attribute is updated as expected as can be checked in the console log. After one check the hover is still on for all lines, after two checks the hover seems to die until the page is refreshed.

I thought updating names would be enough according to the docs bokeh.models.tools — Bokeh 0.12.4 documentation on “names”:

“A list of names to query for. If set, only renderers that have a matching value for their name attribute will be used.”

Any ideas how I could proceed? Here is my code:

from bokeh.plotting import figure, output_file

from bokeh.models import CustomJS, HoverTool, CheckboxGroup

from bokeh.layouts import gridplot

from bokeh.resources import CDN

from bokeh.embed import file_html

fig = figure(plot_width=400,plot_height=400,toolbar_location=None)

plots =

plots.append( fig.line(x=range(10),y=range(10),name=‘first’) )

plots.append( fig.line(x=range(10),y=range(10)[::-1],name=‘second’) )

plots.append( fig.line(x=range(10),y=[i**0.5 for i in range(10)],name=‘third’) )

fig.add_tools(HoverTool(mode=‘vline’,line_policy=‘next’,names=))

labels = [‘first’,‘second’,‘third’]

N_plots = range(len(plots))

checkbox = CheckboxGroup(labels=names,active=,width=200)

checkbox_iterable =[(‘p’+str(i),plots[i]) for i in N_plots]+[(‘checkbox’,checkbox),(‘hover’,fig.tools[-1])]

checkbox_code = “”“var indexOf = .indexOf;”""+’’.join([‘p’+str(i)+’.visible = indexOf.call(checkbox.active, ‘+str(i)+’) >= 0;’ for i in N_plots])

checkbox_code += ‘hover.names = ;’+’’.join([‘if (p’+str(i)+’.visible) hover.names.push("’+labels[i]+’");’ for i in N_plots])+‘console.log(hover.names);’

checkbox.callback = CustomJS(args={key: value for key,value in checkbox_iterable}, code=checkbox_code)

grid = gridplot([[fig,checkbox]],toolbar_location=‘left’)

outfile=open(‘hovtest.html’,‘w’)

outfile.write(file_html(grid,CDN,‘test’))

outfile.close()

``

Thank you,

Sébastien

Here is another attempt with one hovertool for each line. Again I can set up the “names” attribute as desired. In fact it’s working as I would expect when I first check any box since it deactivates the hovertool for the other two lines. However I don’t seem to be able to reactivate it after it got deactivated once.

from bokeh.plotting import figure, output_file

from bokeh.models import CustomJS, HoverTool, CheckboxGroup, ColumnDataSource

from bokeh.layouts import gridplot

from bokeh.resources import CDN

from bokeh.embed import file_html

from collections import OrderedDict

TOOLTIPS = [ (“x”, “~x"), ("y", "~y”) ]

TOOLS = [‘crosshair’]

source = ColumnDataSource(data={‘x’:range(10),‘y0’:range(10),‘y1’:range(10)[::-1],‘y2’:[i**0.5 for i in range(10)]})

fig = figure(tools=TOOLS)

fig.tools[0].dimensions = ‘height’

plots =

plots.append( fig.line(x=‘x’,y=‘y0’, name=“0”, source=source) )

plots.append( fig.line(x=‘x’,y=‘y1’, name=“1”, source=source) )

plots.append( fig.line(x=‘x’,y=‘y2’, name=“2”, source=source) )

names = [‘first’,‘second’,‘third’]

ID=0

for plot in plots:

fig.add_tools( HoverTool(mode='vline',line_policy='interp',renderers=[plot],names=[str(ID)],tooltips=OrderedDict( [('name',names[ID])]+TOOLTIPS )) )

ID+=1

N_plots = range(len(plots))

checkbox = CheckboxGroup(labels=names,active=,width=200)

checkbox_iterable =[(‘p’+str(i),plots[i]) for i in N_plots]+[(‘hover’+str(i),fig.tools[1:][i]) for i in N_plots]+[(‘checkbox’,checkbox)]

checkbox_code = “”“var indexOf = .indexOf;”""+’’.join([‘p’+str(i)+’.visible = indexOf.call(checkbox.active, ‘+str(i)+’) >= 0;’ for i in N_plots])

checkbox_code += ‘’.join([‘if(p’+str(i)+’.visible) hover’+str(i)+’.names = ["’+str(i)+’"];’ for i in N_plots])+’’.join([‘if(p’+str(i)+’.visible==false) hover’+str(i)+’.names = [“no”];’ for i in N_plots])

checkbox_code += ‘’.join([‘console.log(hover’+str(i)+’.names);’ for i in N_plots])

checkbox.callback = CustomJS(args={key: value for key,value in checkbox_iterable}, code=checkbox_code)

grid = gridplot([[fig,checkbox]],toolbar_location=‘left’)

outfile=open(‘hovtest.html’,‘w’)

outfile.write(file_html(grid,CDN,‘test’))

outfile.close()

``

Manipulating the glyphs will prevent unwanted hovertool interactions and has the same effect as toggling visibility. Here is your example reworked to illustrate a possible solution using this approach.

from bokeh.plotting import figure, output_file
from bokeh.models import CustomJS, HoverTool, CheckboxGroup
from bokeh.layouts import gridplot
from bokeh.resources import CDN
from bokeh.embed import file_html
fig = figure(plot_width=400,plot_height=400,toolbar_location=None
)
plots = []
plots.append( fig.line(x=range(10),y=range(10),name='first') )
plots.append( fig.line(x=range(10),y=range(10)[::-1],name='second') )
plots.append( fig.line(x=range(10),y=[i**0.5 for i in range(10)],name='third'
) )
hover = HoverTool(mode='vline',line_policy='next'
,names=[])
fig.add_tools(hover)
labels = ['first','second','third'
]
N_plots = range(len(plots))
checkbox = CheckboxGroup(labels=labels, active=[],width=200
)
js_args = {
'p0': plots[0    ],
'p1': plots[1    ],
'p2': plots[2    ],
'fig'    : fig,
'checkbox'
: checkbox,
}
checkbox_code =
'''
var indexOf = [].indexOf;
var new_renderers = []
if(indexOf.call(checkbox.active, 0) >= 0) new_renderers.push(p0);
if(indexOf.call(checkbox.active, 1) >= 0) new_renderers.push(p1);
if(indexOf.call(checkbox.active, 2) >= 0) new_renderers.push(p2);
fig.renderers = new_renderers;
'''

checkbox.callback = CustomJS(args=js_args, code=checkbox_code)
grid = gridplot([[fig,checkbox]],toolbar_location='left'
)
outfile=open('hovtest.html','w')
outfile.write(file_html(grid,CDN,'test'))
outfile.close()

···

On Tue, Jan 31, 2017 at 8:50 AM, [email protected] wrote:

Here is another attempt with one hovertool for each line. Again I can set up the “names” attribute as desired. In fact it’s working as I would expect when I first check any box since it deactivates the hovertool for the other two lines. However I don’t seem to be able to reactivate it after it got deactivated once.

from bokeh.plotting import figure, output_file

from bokeh.models import CustomJS, HoverTool, CheckboxGroup, ColumnDataSource

from bokeh.layouts import gridplot

from bokeh.resources import CDN

from bokeh.embed import file_html

from collections import OrderedDict

TOOLTIPS = [ (“x”, “~x"), ("y", "~y”) ]

TOOLS = [‘crosshair’]

source = ColumnDataSource(data={‘x’:range(10),‘y0’:range(10),‘y1’:range(10)[::-1],‘y2’:[i**0.5 for i in range(10)]})

fig = figure(tools=TOOLS)

fig.tools[0].dimensions = ‘height’

plots =

plots.append( fig.line(x=‘x’,y=‘y0’, name=“0”, source=source) )

plots.append( fig.line(x=‘x’,y=‘y1’, name=“1”, source=source) )

plots.append( fig.line(x=‘x’,y=‘y2’, name=“2”, source=source) )

names = [‘first’,‘second’,‘third’]

ID=0

for plot in plots:

fig.add_tools( HoverTool(mode=‘vline’,line_policy=‘interp’,renderers=[plot],names=[str(ID)],tooltips=OrderedDict( [(‘name’,names[ID])]+TOOLTIPS )) )

ID+=1

N_plots = range(len(plots))

checkbox = CheckboxGroup(labels=names,active=,width=200)

checkbox_iterable =[(‘p’+str(i),plots[i]) for i in N_plots]+[(‘hover’+str(i),fig.tools[1:][i]) for i in N_plots]+[(‘checkbox’,checkbox)]

checkbox_code = “”“var indexOf = .indexOf;”“”+‘’.join([‘p’+str(i)+‘.visible = indexOf.call(checkbox.active, ‘+str(i)+’) >= 0;’ for i in N_plots])

checkbox_code += ‘’.join([‘if(p’+str(i)+‘.visible) hover’+str(i)+‘.names = ["’+str(i)+‘"];’ for i in N_plots])+‘’.join([‘if(p’+str(i)+‘.visible==false) hover’+str(i)+‘.names = [“no”];’ for i in N_plots])

checkbox_code += ‘’.join([‘console.log(hover’+str(i)+‘.names);’ for i in N_plots])

checkbox.callback = CustomJS(args={key: value for key,value in checkbox_iterable}, code=checkbox_code)

grid = gridplot([[fig,checkbox]],toolbar_location=‘left’)

outfile=open(‘hovtest.html’,‘w’)

outfile.write(file_html(grid,CDN,‘test’))

outfile.close()

``

You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/75d377ae-090a-4e8f-bbad-fe24fed03d24%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Great ! this is exactly what I was looking for. Thank you

I didn’t notice it first but this solution requires some more thinking when using a legend external to the plot. It tries to redraw the legend and produces an error in console:

Uncaught TypeError: Cannot read property ‘draw_legend’ of undefined

``

I guess this is because we set the new_renderers to an empty list and the final set of renderers only contains GlyphRenderer objects. There should be “Legend”,“LinearAxis”,“Grid” and “BoxAnnotation” in the renderers. It doesn’t look so simple to fix, I can’t just use those as the start of new_renderers.

Changing the line visibility had the advantage of not having to worry about all of this.

Yeah, I completely missed that. It might be possible to create a new set of renderers to replace the non-glyph ones, but that might be opening a can of worms. A brute-force approach that might work is replacing the figure with a newly generated figure on callback. I’m not sure how to do this within a JavaScript callback, though. Here is an example using bokeh server:

from [bokeh.io](http://bokeh.io) import curdoc
from bokeh.plotting import figure
from bokeh.models import HoverTool, CheckboxGroup
from bokeh.layouts import row
from bokeh.resources import CDN
from bokeh.embed import file_html
labels = ['first','second','third'
]
checkbox = CheckboxGroup(labels=labels, active=[0, 1, 2],width=200
)
def create_fig():
    fig = figure(plot_width=400,plot_height=400,toolbar_location=None    )
if 0 in checkbox.active:
fig.line(x=range(10),y=range(10), legend='first'    )
if 1 in checkbox.active:
fig.line(x=range(10),y=range(10)[::-1], legend='second'    )
if 2 in checkbox.active:
fig.line(x=range(10),y=[i**0.5 for i in range(10)], legend='third'    )
hover = HoverTool(mode='vline',line_policy='next'    ,names=[])
fig.add_tools(hover)
return fig
def callback(attr, old, new):
    layout.children[0
] = create_fig()
layout = row(create_fig(), checkbox)
checkbox.on_change('active'
, callback)
curdoc().add_root(layout)

···

On Mon, Feb 6, 2017 at 10:22 AM, [email protected] wrote:

I didn’t notice it first but this solution requires some more thinking when using a legend external to the plot. It tries to redraw the legend and produces an error in console:

Uncaught TypeError: Cannot read property ‘draw_legend’ of undefined

``

I guess this is because we set the new_renderers to an empty list and the final set of renderers only contains GlyphRenderer objects. There should be “Legend”,“LinearAxis”,“Grid” and “BoxAnnotation” in the renderers. It doesn’t look so simple to fix, I can’t just use those as the start of new_renderers.

Changing the line visibility had the advantage of not having to worry about all of this.

You received this message because you are subscribed to the Google Groups “Bokeh Discussion - Public” group.

To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].

To post to this group, send email to [email protected].

To view this discussion on the web visit https://groups.google.com/a/continuum.io/d/msgid/bokeh/a03e3ea6-9048-4cdc-8c35-b230ca23b1b5%40continuum.io.

For more options, visit https://groups.google.com/a/continuum.io/d/optout.

Hi, how about to use in callback

document.getElementsByClassName(‘bk-tooltip bk-tooltip-custom’)[0].style.display = ‘none’;

``

with this the hover will only be hidden but will still be executed. You can use this to map your hovertools and hide them based on your widgets states.

Corleo

The bokeh server solution does exactly what I would like to do, I could not find a way to reproduce it in CustomJS. It felt like I would need to basically write what figure() does in the JS callback…

Corleo - I am not sure how you suggest using that line, there is no object with class ‘bk-tooltip bk-tooltip-custom’ in the html file I produce

Hi, Sébastien.

I think bk-tooltip-custom showed up to me because I was using a custom hovertool but you can use just bk-tooltip class instead. And this workarround that I suggested will work only when executed within a hover callback.
document.getElementsByClassName(‘bk-tooltip’)[0].style.display = ‘none’;

``

``

To accomplish what you need you have to define a hover callback for each glyph ploted and another
one to toggle plot visibility based on checkbox states. Also I saw that you have passed checkbox object as an argument to callback function but since this function is the checkbox’ own callback you don’t need to because you can access it using cb_data and cb_obj.

I think you want some like this, write? To execute do it in terminal:
$bokeh serve --show sebastien

``

This toggle plot visibility feature with hover is a nice idea. I will save it to myself! :slight_smile:
Corleo

sebastien.py (1.79 KB)

I tested and it worked ! Thank you Corleo

I was passing the checkbox to its own callback because I usually reuse the checkbox callback code for other widgets that modify the checkbox.active list, like:

clear_button_code = “”“checkbox.active=;”""+checkbox_code

``