Swapping two figures in a gridbox at runtime in bokeh server

Hello gentlemen,

is it possible to update the layout of figures arranged in a gridplot at runtime? Basically I want to reorder figures by swapping positions of two figures using a button callback. But whatever I try to do it does not work as expected.

Here is a minimal example of what I do consider one of my best tries:

from bokeh.application.handlers.function import FunctionHandler
from bokeh.application import Application
from bokeh.document import Document
from bokeh.server.server import Server
from bokeh.plotting import figure, output_file, show, gridplot
from bokeh.models import GridBox
from bokeh.layouts import column, grid
from bokeh.models.widgets import Button

model = None


def on_button():
    f1, f2, b = model.children[1].children
    
    # swap items - but no effect
    model.children[1].children = [f2, f1, b]


def make_document(doc: Document):
    global model
    x = [1, 2, 3, 4, 5]
    y = [6, 7, 2, 4, 5]

    p = figure(title="simple line example", x_axis_label='x', y_axis_label='y')
    p.line(x, y, legend="Temp.", line_width=2)

    y2 = [3, 5, 2, 11, 9]
    p2 = figure(title="simple line example", x_axis_label='x', y_axis_label='y')
    p2.line(x, y2, legend="Temp.", line_width=2)

    b = Button(label="Rearrange", button_type="success")
    b.on_click(on_button)

    model = gridplot(children=[[p], [p2], [b]])
    doc.add_root(model)


handler = FunctionHandler(make_document)
app = Application(handler, )

apps = {'/': app}

server = Server(apps, port=80)
server.run_until_shutdown()

But it has no visible effect. I suspect it is quite easy to do probably but I just can’t manage to do the right thing. A bit of help would be very appreciated.

I am using Bokeh 1.4.0 from Miniconda on Windows 10 x64.

Thanks alot!

FYI for the future: half the core team, and a great many Bokeh users, are women. Please be inclusive and respectful of the entire Bokeh community here.

is it possible to update the layout of figures arranged in a gridplot at runtime?

Yes, but it will be pretty tedious, because the actual stored format for grid children (i.e. what you need to update) is more complicated than what you pass in initially. It looks like this, where the actual position in the grid is determined by the numbers in the tuples (not by the top level order):

[(Figure(id=...), 0, 0), (Figure(id=...), 1, 0), (Button(id=...), 2, 0)]

Could you use a column here instead? That would be much easier to update.

Oh I am sorry, I didn’t mean to be disrespectful to anyone. So “Hello” also to all ladies and also to people of all other genders also.

[(Figure(id=...), 0, 0), (Figure(id=...), 1, 0), (Button(id=...), 2, 0)]

Actually I think I already tried that in the past but could not get it to work. I again tried it using your proposal changing that one line to this:

#    model.children[1].children = [f2, f1, b]
    model.children[1].children = [(f2, 0, 0), (f1, 1, 0), (b, 2, 0)]

but then I get:

  File "C:\Users\Robin\Miniconda3\envs\qrawler\lib\site-packages\bokeh\core\property\container.py", line 90, in validate
    raise ValueError(msg)
ValueError: expected an element of List(Either(Tuple(Instance(LayoutDOM), Int, Int), Tuple(Instance(LayoutDOM), Int, Int, Int, Int))), got seq with invalid items [((Figure(id='1048', ...), 1, 0), 0, 0), ((Figure(id='1001', ...), 0, 0), 1, 0), ((Button(id='1095', ...), 2, 0), 2, 0)]

Yes I already tried with column and managed to rearrange it out-of-the-box. The selling point for gridplot for me was the toolbar collecting feature:

gridplot() also collects all tools into a single toolbar, and the currently active tool is the same for all plots in the grid.

I guess this would also be possible to achieve also using a column? I might give it a try, thank you.

Regarding the ValueError - f1, f2, and b in the on_button function are not instances of LayoutDOM but rather tuples themselves. The first item is an instance of LayoutDOM, the second is the row of the element, the third one is the column.

With that being said, all you need to do is swap the row values. Swapping the whole tuples doesn’t do anything because, well, the tuples also have the row number.

Isn’t that what I did with this line already?

model.children[1].children = [(f2, 0, 0), (f1, 1, 0), (b, 2, 0)]

I think f1 had “0,0” before and has now “1,0”.

Please carefully read my very first sentence in the previous post. You can check it yourself by just printing the values out.

Sorry, now I understand. You are right. Silly me! Works now, thanks alot!