Changing hover renderers on JS callback must be initialized with all renderers

I have multiple glyphs that interact with the same hovertool, but I only want one to display at a time (due to overlapping data points and other reasons). The user should be able to toggle between them, using the radiogroup.

In the toy example below I have the desired behavior with the CustomJS successfully toggling the hovertool, when initialized as I have here. However, I have found this only works when I initialize the HoverTool with renderers=[r_l0, r_l1], which means until the user uses the radiogroup for the first time, both renderers produce the hovertool.

How can I edit this toy example to initialize the plot with hover for r_l0, and the ABILITY to toggle from r_l0 to r_l1 and back?


from bokeh.models.glyphs import Circle

from bokeh.models import ColumnDataSource, CustomJS, HoverTool

from bokeh.plotting import Figure, show, output_notebook

from bokeh.models.widgets.groups import RadioGroup

from bokeh.layouts import row

output_notebook()

source1 = ColumnDataSource(dict(

x=[1,2,3],

y=[1,2,3],

names=['John', 'Paul', 'Patrick']))

source2 = ColumnDataSource(dict(

x=[3,2,1],

y=[1,1,2],

names=['Sophie', 'Leslie', 'Hannah']))

plot = Figure()

l0 = Circle(x='x', y='y', size=30, fill_color='blue')

r_l0 = plot.add_glyph(source1, l0)

l1 = Circle(x='x', y='y', size=30, fill_color='pink')

r_l1 = plot.add_glyph(source2, l1)

code = """

if (radio.active == 0) {

hover.renderers = [r_l0]

} else {

hover.renderers = [r_l1]

}

"""

callback = CustomJS(code=code, args={})

radio = RadioGroup(labels=['Boys', 'Girls'], active=0, callback=callback, width=200)

hover = HoverTool(tooltips=[

("Name", "@names")], renderers=[r_l0, r_l1])

plot.add_tools(hover)

callback.args = dict(radio=radio, r_l0=r_l0, r_l1=r_l1, hover=hover)

layout = row(radio, plot)

show(layout)

Thanks very much.

First of all, thank you for making this and sharing it as it solves a problem I have!

The issues is that the callback must be invoked before the user clicks a radio button or hovers over a point, however it’s only invoked through the radio button. I was able to initialize it by adding these two lines (I also used a Plot instead of Figure, but it shouldn’t be too different):

from bokeh.events import MouseMove

plot.js_on_event(MouseMove, callback)

This triggers the callback when the mouse moves onto the plot area, which it has to do to hover. So it should instantiate the hover tool as you want with one instead of both. I’m not sure if it’s the most efficient way as the program will be constantly calling the callback, but I couldn’t find a way to generate an event. In some libraries you could just generate an event and trigger the callback.

···

On Thursday, January 18, 2018 at 8:49:02 PM UTC-8, Paige McKenzie wrote:

I have multiple glyphs that interact with the same hovertool, but I only want one to display at a time (due to overlapping data points and other reasons). The user should be able to toggle between them, using the radiogroup.

In the toy example below I have the desired behavior with the CustomJS successfully toggling the hovertool, when initialized as I have here. However, I have found this only works when I initialize the HoverTool with renderers=[r_l0, r_l1], which means until the user uses the radiogroup for the first time, both renderers produce the hovertool.

How can I edit this toy example to initialize the plot with hover for r_l0, and the ABILITY to toggle from r_l0 to r_l1 and back?

from bokeh.models.glyphs import Circle

from bokeh.models import ColumnDataSource, CustomJS, HoverTool

from bokeh.plotting import Figure, show, output_notebook

from bokeh.models.widgets.groups import RadioGroup

from bokeh.layouts import row

output_notebook()

source1 = ColumnDataSource(dict(

x=[1,2,3],

y=[1,2,3],

names=[‘John’, ‘Paul’, ‘Patrick’]))

source2 = ColumnDataSource(dict(

x=[3,2,1],

y=[1,1,2],

names=[‘Sophie’, ‘Leslie’, ‘Hannah’]))

plot = Figure()

l0 = Circle(x=‘x’, y=‘y’, size=30, fill_color=‘blue’)

r_l0 = plot.add_glyph(source1, l0)

l1 = Circle(x=‘x’, y=‘y’, size=30, fill_color=‘pink’)

r_l1 = plot.add_glyph(source2, l1)

code = “”"

if (radio.active == 0) {

hover.renderers = [r_l0]

} else {

hover.renderers = [r_l1]

}

“”"

callback = CustomJS(code=code, args={})

radio = RadioGroup(labels=[‘Boys’, ‘Girls’], active=0, callback=callback, width=200)

hover = HoverTool(tooltips=[

(“Name”, “@names”)], renderers=[r_l0, r_l1])

plot.add_tools(hover)

callback.args = dict(radio=radio, r_l0=r_l0, r_l1=r_l1, hover=hover)

layout = row(radio, plot)

show(layout)

Thanks very much.

Thanks Samuel, this works wonderfully. I used MouseEnter to have the event trigger less and hopefully prevent slow load times.

For posterity, the correct code:


from bokeh.models.glyphs import Circle

from bokeh.models import ColumnDataSource, CustomJS, HoverTool

from bokeh.plotting import Figure, show, output_notebook

from bokeh.models.widgets.groups import RadioGroup

from bokeh.events import MouseEnter

from bokeh.layouts import row

output_notebook()

source1 = ColumnDataSource(dict(

x=[1,2,3],

y=[1,2,3],

names=['John', 'Paul', 'Patrick']))

source2 = ColumnDataSource(dict(

x=[3,2,1],

y=[1,1,2],

names=['Sophie', 'Leslie', 'Hannah']))

plot = Figure()

l0 = Circle(x='x', y='y', size=30, fill_color='blue')

r_l0 = plot.add_glyph(source1, l0)

l1 = Circle(x='x', y='y', size=30, fill_color='pink')

r_l1 = plot.add_glyph(source2, l1)

code = """

if (radio.active == 0) {

hover.renderers = [r_l0]

} else {

hover.renderers = [r_l1]

}

"""

callback = CustomJS(code=code, args={})

radio = RadioGroup(labels=['Boys', 'Girls'], active=0, callback=callback, width=200)

hover = HoverTool(tooltips=[

("Name", "@names")], renderers=[r_l0, r_l1])

plot.add_tools(hover)

callback.args = dict(radio=radio, r_l0=r_l0, r_l1=r_l1, hover=hover)

plot.js_on_event(MouseEnter, callback)

layout = row(radio, plot)

show(layout)

···

On Thursday, January 18, 2018 at 10:49:02 PM UTC-6, Paige McKenzie wrote:

I have multiple glyphs that interact with the same hovertool, but I only want one to display at a time (due to overlapping data points and other reasons). The user should be able to toggle between them, using the radiogroup.

In the toy example below I have the desired behavior with the CustomJS successfully toggling the hovertool, when initialized as I have here. However, I have found this only works when I initialize the HoverTool with renderers=[r_l0, r_l1], which means until the user uses the radiogroup for the first time, both renderers produce the hovertool.

How can I edit this toy example to initialize the plot with hover for r_l0, and the ABILITY to toggle from r_l0 to r_l1 and back?

from bokeh.models.glyphs import Circle

from bokeh.models import ColumnDataSource, CustomJS, HoverTool

from bokeh.plotting import Figure, show, output_notebook

from bokeh.models.widgets.groups import RadioGroup

from bokeh.layouts import row

output_notebook()

source1 = ColumnDataSource(dict(

x=[1,2,3],

y=[1,2,3],

names=[‘John’, ‘Paul’, ‘Patrick’]))

source2 = ColumnDataSource(dict(

x=[3,2,1],

y=[1,1,2],

names=[‘Sophie’, ‘Leslie’, ‘Hannah’]))

plot = Figure()

l0 = Circle(x=‘x’, y=‘y’, size=30, fill_color=‘blue’)

r_l0 = plot.add_glyph(source1, l0)

l1 = Circle(x=‘x’, y=‘y’, size=30, fill_color=‘pink’)

r_l1 = plot.add_glyph(source2, l1)

code = “”"

if (radio.active == 0) {

hover.renderers = [r_l0]

} else {

hover.renderers = [r_l1]

}

“”"

callback = CustomJS(code=code, args={})

radio = RadioGroup(labels=[‘Boys’, ‘Girls’], active=0, callback=callback, width=200)

hover = HoverTool(tooltips=[

(“Name”, “@names”)], renderers=[r_l0, r_l1])

plot.add_tools(hover)

callback.args = dict(radio=radio, r_l0=r_l0, r_l1=r_l1, hover=hover)

layout = row(radio, plot)

show(layout)

Thanks very much.