Show loading sign during calculations

Hello,
I would like to know if there are easy ways to show the user some icons during heavy calculations.

I have some old code which worked in Bokeh 1.0.2 with Python 2.7. There at the start of a callback function the layout was changed to show the loading sign and after the calculations at the end of this same function, the figure with the calculated plot was shown.
However, in Bokeh 1.4.0 with Python 3.7, this does not work any more. It seems like changes to the layout are only set at the end of a function.

Here is a minimal example, to show what I mean:

Click here to see the code

style.html

includes the CSS for the loading sign, taken from https://loading.io/css/

<style>
    .lds-dual-ring {
        display: block;
        width: 50%;
        margin: auto;
        padding: 10px;
    }

    .lds-dual-ring:after {
        content: " ";
        display: block;
        width: 46px;
        height: 46px;
        margin: auto;
        border-radius: 50%;
        border: 5px solid rgb(0, 101, 189);
        border-color: rgb(0, 101, 189) transparent rgb(0, 101, 189) transparent;
        animation: lds-dual-ring 1.2s linear infinite;
    }

    @keyframes lds-dual-ring {
        0% {
            transform: rotate(0deg);
        }

        100% {
            transform: rotate(360deg);
        }
    }
</style>

main.py

The upper button switches between the loading symbol and the figure. This works.
The lower button starts a “heavy calculation” (here the program just sleeps for 5 seconds, but it has the same effect). In the console you can see by the prints, that everything should be set at the correct time. But only when the function finishes, you can see the loading sign for the fraction of a second. Actually, when starting the callback, the loading sign should show and at the end switch back to the figure.

from bokeh.io import curdoc
from bokeh.models import Div, Button, ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import column, layout
from os.path import dirname, join
import time


def change_pic():
    if b.label=="change to load screen":
        My_Layout.children[0].children[2] = loading
        b.label = "change to figure"
    else:
        My_Layout.children[0].children[2] = f
        b.label = "change to load screen"

def fake_comp():
    My_Layout.children[0].children[2] = loading
    print("set loading")

    time.sleep(5) # mock calculation
    
    My_Layout.children[0].children[2] = f
    print("set figure")


style_file = join(dirname(__file__), "style.html")
d = Div(text=open(style_file).read(), render_as_text=False)

loading = column(Div(text="<div class=\"lds-dual-ring\"></div>", render_as_text=False, width=650, height=100))

f = figure(x_range=(0,5), y_range=(0,5))


b = Button(label="change to load screen")
b.on_click(change_pic)

c_b = Button(label="expensive comp")
c_b.on_click(fake_comp)


My_Layout = layout([column(d, b, f, c_b)])

curdoc().add_root(My_Layout)

Three ideas that I’ve already tired:

  • Using the visible attribute and setting it to False/True instead of manipulating the layout directly.
  • I have also tried the same with just a Label and changing text, but this yields the same result.
  • Another idea was to add a mock slider and change the layout in a different function if its value has changed, but this workaround doesn’t work either…

Regarding built-in spinners, there was already some discussion on Github:
#3393, #8823

1 Like

I’m not sure the behavior here has ever been defined clearly. It probably should be, and then maintained under test, some day. But as you mention, currently Bokeh state only synchronizes with the browser when the callback ends . If you want to do some update, then alot of blocking work, then another update, you will need to split things up so the first callback completes immediately then schedules the rest of the work to happen after the return. The simplest way is with add_next_tick_callback :

from time import sleep
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import Button, Div

d = Div(text="start")

b = Button()

def work():
    sleep(2)
    d.text = "end"

def cb():
    d.text = "middle"
    curdoc().add_next_tick_callback(work)

b.on_click(cb)

curdoc().add_root(column(d, b))
6 Likes

Thank you, this solves my problem and works very well :slight_smile:

1 Like

Hallo,

Firstly, thank you very much for the offered solution.

My issue was similar as Matthias’s. I wanted to disable some widgets and buttons during the heavy calculations, so that an user cannot change anything and as a sign that the calculation is happening in the background.

It worked perfectly yesterday, when I implemented the solution. However, today I added curdoc().add_next_tick_callback() for several other calculations/functions and it seems the problem returned. The changes to the layout (disabling of the widgets) are delayed and almost set at the end of a function.

Is there anything I’m missing? Why did the problem return?
I’m fairly new to the bokeh, so please tell me if you need more information from me.

Thanks a lot!

@Tamara it’s not possible to speculate without more information, e.g. versions and some idea of the actual code. Please feel free to open a new issue with details.

Thanks for the prompt response. I managed to solve it for me, but if I encounter more problems, I will open a new issue with details.

(Disabling certain widgets was delayed, because, at the same time, I wanted to set value to some TextInput widgets. Removing the part, where I set values to TextInput, has solved it.)

Cheers!

1 Like

Sorry for digging that back up after 2years+ but I am facing exactly this problem at the moment.

I tried something like this, directly manipulating a div from one of my document, to hide the work being done under the hood (asynchronously with add_next_tick_callback) but unfortunately ended up with:
raise RuntimeError("_pending_writes should be non-None when we have a document lock, and we should have the lock when the document changes")

If I use twice the add_next_tick_callback, then my div is unfortunately not updated until the second callback has finished

Something that I haven’t tried (not even sure if it is possible ?) is to define a bokeh.models.Text source for the div, and then streaming the text I want to update with source.stream method. Would that work any better ?

As I mentioned before it’s impossible to speculate without more information, specifically a complete Minimal Reproducible Example.