so0p
so0p

Reputation: 33

Force update of bokeh widgets?

I am new to bokeh and writing a small bokeh server app, which has plot and a button. When the button is pressed, data is recalculated and plot updates. The idea is that as soon as the button pressed, it changes the color and label, also a text "calculating..." appears. When calculations are done, plot updates and the text disappears.

However, when button is pressed, it doesn't change color and the text does not appear before the calculations are done (takes several seconds). All this widget update happens after calculations. Question, is it possible to force a widget to update, like flush=True in case of print() or something similar may be?

I could not find anything in bokeh documetation. I have tried also to separate widget changes and calculations and execute them in two separate functions, but it didn't help. Setting a delay between button change and invoke of calculation function also did not help. Seems, like update on widgets only happens on exit from callback function or even later. The only thing which I did not check is CustomJS, but I don't know how to write js code for button update.

Thanks for any help!

Here is a code sample close to what I actually use:

from bokeh.plotting import figure
from bokeh.models import Button, PreText, ColumnDataSource
from bokeh.layouts import row

p = figure()
source = ColumnDataSource(data={"x":[0], "y":[0]})
p.line(x="x", y="y", source=source)
variable = False

# initialise widgets
switchButton = Button(label='Anticrossing OFF', button_type="default")
process_markup = PreText(text='Calculating...', visible=False)

def callback(arg):
    global variable
    global process_markup

    variable = not variable

    # change button style
    if variable:
        switchButton.update(label = 'Anticrossing ON',
                              button_type = 'success')
    else:
        switchButton.update(label = 'Anticrossing OFF',
                              button_type = 'default')
    # show "calculating..."
    process_markup.update(visible=True)

    # do long calculations
    x, y = calculate_data(variable)
    source.data = {"x":x, "y":y}

    # hide "calculating..."
    process_markup.update(visible=False)

switchButton.on_click(callback)
col = column(switchButton, process_markup)
curdoc().add_root(row(col, p))

Upvotes: 3

Views: 2167

Answers (1)

Eugene Pakhomov
Eugene Pakhomov

Reputation: 10652

Bokeh can send data updates only when the control is returned back to the server event loop. In your case, you run the computation without every yielding the control, so it sends all of the updates when the callback is already done.

The simplest thing to do in your case is to split the callback into blocks that require synchronization and run each block in a next tick callback:

def callback(arg):
    global variable
    global process_markup

    variable = not variable

    # change button style
    if variable:
        switchButton.update(label = 'Anticrossing ON',
                              button_type = 'success')
    else:
        switchButton.update(label = 'Anticrossing OFF',
                              button_type = 'default')
    # show "calculating..."
    process_markup.update(visible=True)

    def calc():
        # do long calculations
        x, y = calculate_data(variable)
        source.data = {"x":x, "y":y}

        # hide "calculating..."
        process_markup.update(visible=False)

    curdoc().add_next_tick_callback(calc)

Note however that such a solution is only suitable if you're the only user and you don't need to do anything while the computation is running. The reason is that the computation is blocking - you cannot communicate with Bokeh in any way while it's running. A proper solution would require some async, e.g. threads. For more details, check out the Updating From Threads section of the Bokeh User Guide.

Upvotes: 5

Related Questions