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