Update layout via CustomJS callback

I’m trying to write a customJS callback to trigger a layout change when clicking on a specific plot.

I had managed to write a script doing just so about 6 months ago (I still have the html files with the expected behavior), based on this answer but for some reason I can’t get it to work today. I can’t even get this MWE to work :

from bokeh.layouts import gridplot
from bokeh.models import CustomJS, TapTool
from bokeh.plotting import figure, show
import numpy as np

# First layout which is shown when html is first shown
im0 = np.random.randint(low = 0, high = 255, size= (500,500), dtype = np.uint32)
p_im0 = figure(width= 500, height= 500, tools = "tap")
p_im0.image(image=[im0], x=0, y=0, dw=1, dh=1, origin="top_left")
layout_0 = gridplot([[p_im0]], merge_tools=False, toolbar_options=dict(logo=None))

layout_tot = layout_0

# Second layout I want to show when I click on the image in first layout
im1 = np.zeros((500,500), dtype = np.uint32)
im1[0,0] = 255
p_im1 = figure(width= 500, height= 500, tools = "tap")
p_im1.image(image=[im1], x=0, y=0, dw=1, dh=1, origin="top_left")
layout_1 = gridplot([[p_im1]], merge_tools=False, toolbar_options=dict(logo=None))


custom_callback = CustomJS(args=dict(
    layout_1 = layout_1,
    layout_tot=layout_tot),
    code=
    """
    console.log("calling callback")
    layout_tot.children = layout_1.children
    """)

taptool = p_im0.select(type=TapTool)
taptool.callback = custom_callback

show(layout_tot)

When opening the resulting html, the first layout is showed. When I click on the image, the callback is called as the console.log() statement shows in the browser console. In my actual script (not this MWE), the new children is a gridplot with many different elements, and adding console.log(layout_tot.children) before and after the change confirms that the the layout_tot.children variable indeed contains these new children. However, nothing happens in the browser, which still shows the first layout.

Python version : 3.10.5
Bokeh : 3.3.4 (but previous working script was possibly coded with earlier version).

Any help would be appreciated. Thanks !

I’m not sure offhand, it’s possible there is some bug or maybe just a usage gotcha to be aware of. Regardless, if your goal is to change the visibility of plots, then there is a visible property that can be set and that is much simpler:

# First layout which is shown when html is first shown
im0 = np.random.randint(low = 0, high = 255, size= (500,500), dtype = np.uint32)
p_im0 = figure(width= 500, height= 500, tools = "tap")
p_im0.image(image=[im0], x=0, y=0, dw=1, dh=1, origin="top_left")

# Second layout I want to show when I click on the image in first layout
im1 = np.zeros((500,500), dtype = np.uint32)
im1[0,0] = 255
p_im1 = figure(width= 500, height= 500, tools = "tap")
p_im1.image(image=[im1], x=0, y=0, dw=1, dh=1, origin="top_left")
p_im1.visible = False

layout = gridplot([[p_im0, p_im1]], merge_tools=False, toolbar_options=dict(logo=None))

custom_callback = CustomJS(args=dict(p_im0=p_im0, p_im1=p_im1), code="""
    p_im0.visible = false
    p_im1.visible = true
""")

taptool = p_im0.select(type=TapTool)
taptool.callback = custom_callback

This works, thanks !

FWIW, some extra info :

  • in my use case, each group of figures I want to show / hide (which I regrouped in individual layouts) is pretty large (around 50 figures). With your solution, eschewing the different layouts and going through each figure to set the visible attribute true or false accordingly does work as intended, but seems much slower than the “switch the whole layout” solution I was trying to implement.
  • As I mentioned, I’m pretty sure this “switch the whole layout” solution used to work at some point in the past (not 100%, it’s also possible I changed stuff and don’t remember).

Thanks again.

Updating layouts works in many cases, I have seen examples recently with column etc. I am not sure why it’s not working above with gridplot. It would require some actual investigation, so all I can suggest is making a GitHub Issue with full details that someone can eventually take a look at.