Bokeh tap to enlarger a plot within a grid of plot

Hello,
I am trying to create a general function that would help me plot a grid of figures (rgb images in the future), and when I click on a specific figure of the grid it would enlarge it and set it in front of the screen, if I then would double click (or a leave button) it would return to the grid. I can accomplish that using matplolib but with the new jupyter version it cannot be done on the notebook itself. So far I have only been able to do the following:

from bokeh.io import output_notebook, show
from bokeh import events
from bokeh.layouts import gridplot, column, layout
from bokeh.plotting import figure
from bokeh.models import CustomJS

output_notebook()

import numpy as np

def create_random_image():
    return np.random.rand(150, 150)

images = [create_random_image() for _ in range(4)]

plots = []
for i, image in enumerate(images):
    p = figure(title=f'Image {i+1}', tools='tap')
    p.image(image=[image], x=0, y=0, dw=10, dh=10, palette="Greys256")
    p.axis.visible = False
    p.grid.visible = False
    p.width = image.shape[0]
    p.height = image.shape[1]
    p.toolbar.logo = None  
    plots.append(p)

enlarge = CustomJS(args=dict(plots=plots), code="""
    for (let i = 0; i < plots.length; i++) {
        let p = plots[i];
        // Reset all plots to their original size
        p.width = 2 * p.width;
        p.height = 2 * p.height;
    }
""")

enlarge_bis = CustomJS(args=dict(plots=plots), code="""
    // Enlarge the clicked plot
    const selected_plot = cb_obj;
    show(selected_plot)
    selected_plot.width = 2 * selected_plot.width;
    selected_plot.height = 2 * selected_plot.height;""")

for plot in plots:
    plot.js_on_event(events.DoubleTap, enlarge)

grid = gridplot([[plots[0], plots[1]], [plots[2], plots[3]]])

show(grid)

when I double click it doubles the width and height of every figures. I have tried to enlarge the clicked plot only but nothing happends when I try it. How much wrong am I?

show is only a Python API function, it cannot be called from JS code (i.e. from CustomJS callbacks) so that is never going to work. Additionally changing the size of one plot inside a grid layout is going to affect the entire existing layout, which you also probably do not want.

To be honest, I am not really sure that there is a good or simple way to achieve what you are looking for. I’d encourage you to open a GitHub Issue to start a discussion about how something like this might be supported by future development. Other core devs might also chime in with any potential workarounds I haven’t thought of. But in the immediate term, you might need to consider other libraries if this is a hard requirement.

This is not exactly what you want but might get you closer to your goal:

from bokeh.io import output_notebook, show
from bokeh import events
from bokeh.layouts import gridplot, column, layout, row
from bokeh.plotting import figure
from bokeh.models import CustomJS, ColumnDataSource

output_notebook()

import numpy as np

def create_random_image():
    return np.random.rand(150, 150)

images = [create_random_image() for _ in range(4)]
sources = {}

plots = []
for i, image in enumerate(images):
    p = figure(title=f'Image {i+1}', tools='tap')
    p.image(image=[image], x=0, y=0, dw=10, dh=10, palette="Greys256")
    p.axis.visible = False
    p.grid.visible = False
    p.width = image.shape[0]
    p.height = image.shape[1]
    p.toolbar.logo = None  
    plots.append(p)
    sources[f'Image {i+1}'] = ColumnDataSource(
        data={
            'image': [image],
            'x': [0],
            'y': [0],
            'dw': [10],
            'dh': [10],
        }
    )

source_main = ColumnDataSource(
    data={
        'image': [images[0]],
        'x': [0],
        'y': [0],
        'dw': [10],
        'dh': [10],
    }
)

main_plot = figure(width=2*image.shape[0], height=2*image.shape[1], title='Image 1')
main_plot.image(image='image', x='x', y='y', dh='dh', dw='dw', palette="Greys256", source=source_main)
main_plot.axis.visible = False
main_plot.grid.visible = False
main_plot.toolbar.logo = None

enlarge = CustomJS(
    args=dict(sources=sources, source_main=source_main, main_plot=main_plot),
    code="""
    const selected_plot = cb_obj.origin.title.text;
    console.log(main_plot.title.text)
    main_plot.title.text = selected_plot
    source_main.data = sources[selected_plot].data    
    """
)

for plot in plots:
    plot.js_on_event(events.Tap, enlarge)

grid = row([
    column([
        row(plots[0], plots[1]),
        row(plots[2], plots[3]),
    ]),
    row(main_plot)]
)

show(grid)

1 Like

@Bobokeh There is an old example on StackOverflow. It is a somewhat advanced since it uses JS libraries. Here if one clicks on the figure title the figure enlarges.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.