Can't get spinning loader to work. What am I doing wrong?


I have a script that is reading from a database and saving to a csv file. I am running this on a bokeh server. I have created an html file with a loader added to the style part.
I tried a version of the example Bryan is showing here: Show loading sign during calculations

The code I have is as following:

# -*- coding: utf-8 -*-
Created on Thu Jan 21 11:55:35 2021

@author: zana.kajolli

from os.path import dirname, join
import app.routes.hmtool.zapeutils as zape
from app.sql.updatecsvfiles import initilizer
from bokeh.models import Div

d = Div(text="start")

def update_app(doc):
    ## Run initializer to get requested data:
    mc_data, field_data = initilizer()

    ## Run calc loop    
    field_data = zape.getFieldData(field_data)
    mc_data = zape.getMCdata(mc_data)
    ## Save to datafolder as _full.csv
    print('Saving data to csv files..')
    mc_data.to_csv(join(dirname(__file__), "..","..", "data", 'mc_data_full.csv'), sep = ',', encoding='utf-8')
    field_data.to_csv(join(dirname(__file__), "..","..", "data", 'field_data_full.csv'), sep = ',', encoding='utf-8')
    d.text = "Finished Uploading Data"

I can see the loader spinning. But it never stops. Here is the html part:

<!doctype html>

<html lang="en">
  <meta charset="utf-8">
/* Center the loader */
#loader {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 1;
  width: 120px;
  height: 120px;
  margin: -76px 0 0 -76px;
  border: 16px solid #f3f3f3;
  border-radius: 50%;
  border-top: 16px solid #3498db;
  -webkit-animation: spin 2s linear infinite;
  animation: spin 2s linear infinite;

@-webkit-keyframes spin {
  0% { -webkit-transform: rotate(0deg); }
  100% { -webkit-transform: rotate(360deg); }

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }

/* Add animation to "page content" */
.animate-bottom {
  position: relative;
  -webkit-animation-name: animatebottom;
  -webkit-animation-duration: 1s;
  animation-name: animatebottom;
  animation-duration: 1s

@-webkit-keyframes animatebottom {
  from { bottom:-100px; opacity:0 } 
  to { bottom:0px; opacity:1 }

@keyframes animatebottom { 
  from{ bottom:-100px; opacity:0 } 
  to{ bottom:0; opacity:1 }

#myDiv {
  display: none;
  text-align: center;

<title>Update Database files</title>

<body style="margin:0;">
<div id="loader" onload="myFunction()"s>
{{ script|safe }}
<div style="display:none;" id="myDiv" class="animate-bottom">
  <p>Data finished uploading..</p>
var myVar;

function myFunction() {
  myVar = setTimeout(showPage, 1);

function showPage() {
  document.getElementById("loader").style.display = "none";
  document.getElementById("myDiv").style.display = "block";

Once it if finished I can see the text come up, but its still spinning and is a part of the loader (see image below). My first time trying to create a loader with bokeh server. Not entirely sure how to do it, anyone can see real quick what I am doing wrong?

The .animate-bottom class applies to every thing in the div, and you never remove that class, so things keep rotating forever. You could use css_classes to add/remove classes from the div.

However, you have another problem to sort out: All of the app code is run before anything is sent to the browser. So if you add the class, load the data, remove the class, then call doc.add_root all the browser will ever see is the the final state (i.e. it will never see the spinner).

Your best option is to use the recently added DocumentReady event, and structure your code like:

  • add a div with the spinner and animate-bottom
  • add a DocumentReady callback
  • call add_root

Then in the callback:

  • load the data
  • remove the spinner and animate-bottom from the div

Another option is to use Holoviz panel which includes a LoadingSpinner indicator which you can manipulate from your python server code. You essentially change its Boolean value property to control whether it is spinning or not.

The Holoviz panel server is built on the bokeh server. You can use all of your existing bokeh server code with minimal additional logic to the top-level layout and invoke via β€œpanel serve ...” rather than β€œbokeh serve ...”, but the syntax is substantially the same.

It is certainly a matter of preference, but I have found the low-level control of bokeh combined with some of the nice convenient extensions of panel to be an ideal solution for me.


Hi Bryan,

You have a small example of this method?

I don’t have any examples at hand.