Display Loading Symbol While Waiting for a Result with Plot.Ly Dash

Display loading symbol while waiting for a result with plot.ly Dash

I had the same problem last week, and even tried to achieve the disabled button behavior using Javascript, but eventually gave up. I've seen seen this discussed on the plotly forums, and there is clearly a need for this type of functionality, but I don't think it can be achieved easily in the current version.

One thing that is possible though, and is mentioned as a temporary solution by the Dash developer, is adding a global loading screen. In short, you need to add the following CSS to your stylesheet:

@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 0.5;
}
}

._dash-loading-callback {
font-family: sans-serif;
padding-top: 50px;
color: rgb(90, 90, 90);

/* The banner */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
cursor: progress;

opacity: 0;
background-color: rgb(250, 250, 250);
/* Delay animation by 1s to prevent flashing
and smoothly transition the animation over 0.5s
*/
-moz-animation: fadein 0.5s ease-in 1s forwards; /* Firefox */
-webkit-animation: fadein 0.5s ease-in 1s forwards; /* Safari and Chrome */
-o-animation: fadein 0.5s ease-in 1s forwards; /* Opera */
animation: fadein 0.5s ease-in 1s forwards;
}

A few clarifications:

  1. The _dash-loading-callback selector selects a div which is added at the end of the body element each time a callback is made, and is removed when it is finished.
  2. You can add a loading animation by defining a background GIF for _dash-loading-callback.
  3. The animations defined in the above CSS are meant to prevent the loading screen from flickering upon short callbacks. It is set to fade in after one second, so it will only appear for long loading operations. You can of course play with these settings.

Updates 2022

  1. It is mentioned in the comments that the class name has changed from _dash-loading-callback to _dash-loading in newer versions. I didn't get the chance to test this yet.
  2. There seem to be easier and more modern ways of achieving this behavior now, without custom CSS. See for example Dash Loading Spinners.

dcc.Loading on First Load Only (Python)

There might not be a way with dcc.Loading

This is a solution that worked for me.

Give your map a style of {"display":"none"}

dcc.Graph(id="us_map", style={"display": "none"})

In the callback add the us-map style as an output and return {"display":"inline"}

@app.callback(
[Output("us_map", "figure"), Output("us_map", "style")], Input("dropdown1", "value")
)
def update_map(county_select):
new_df = df[df["fips"] == county_select]
fig = px.choropleth_mapbox(
new_df,
geojson=counties,
locations="fips",
color="unemp",
color_continuous_scale="Viridis",
range_color=(0, 12),
mapbox_style="carto-positron",
zoom=3,
center={"lat": 37.0902, "lon": -95.7129},
opacity=0.5,
)
fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

return fig, {"display": "inline"}

If you want a loading bar instead of the area being blank, place the loading bar in a div above the map, add the loading bar as an output to the div, add a a return of {"display":"false"} so that when the map loads the loading animation disappeares. You cannot get this to work with dcc.Loading so I just used a custom CSS loading bar.

Python Plotly: display temporary message during long callback

You can look into loading states. You can display a spinner or text telling the user that a component is loading.

Long loading time (dash table without all dropdowns fields display)

The trick to charge all the dropdowns option was to add the following code in dash_table.DataTable() :

css=[{"selector": ".Select-menu-outer", "rule": "display: block !important"}],

My page still take a long time to charge but has all the labels inside my dropdowns !

How to add/create a custom loader with Dash plotly?

The type property specifies what the spinner looks like, but is limited to a set of values dash core components provides:

type (a value equal to: 'graph', 'cube', 'circle', 'dot', 'default'; default 'default'): Property that determines which spinner to show one of 'graph', 'cube', 'circle', 'dot', or 'default'.

So your approach is currently not possible, but there is a workaround.

The html structure of the default spinner looks something like this:

<div class="dash-spinner dash-default-spinner">
<div class="dash-default-spinner-rect1"></div>
<div class="dash-default-spinner-rect2"></div>
<div class="dash-default-spinner-rect3"></div>
<div class="dash-default-spinner-rect4"></div>
<div class="dash-default-spinner-rect5"></div>
</div>

Each of these rectangle divs inside .dash-spinner are used for the default type animation. We don't care about these rectangles or anything that's inside of .dash-spinner, because we only want the gif animation.

So what we could do here is to hide all .dash-spinner children and add an animated background to .dash-spinner.

We can do so with css

.dash-spinner {
background-image: url("http://i.stack.imgur.com/SBv4T.gif");
background-size: contain;
background-repeat: no-repeat;
}

.dash-spinner * {
display: none !important;
}

For including css see the documentation here.

Minimal and reproducible example based on the example given in the documentation here:

from dash import Dash
import time
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

app = Dash(__name__)
app.layout = html.Div(
children=[
html.H3("Edit text input to see loading state"),
dcc.Input(id="loading-input-1", value="Input triggers local spinner"),
dcc.Loading(
id="loading-1", type="default", children=html.Div(id="loading-output-1")
),
],
)

@app.callback(
Output("loading-output-1", "children"),
Input("loading-input-1", "value"),
prevent_initial_call=True,
)
def input_triggers_spinner(value):
time.sleep(2)
return value

if __name__ == "__main__":
app.run_server()

Gif was taken from this related answer here.

How to change the default Loading... message when the Dash component page is in loading state?

By adding the below CSS file under /assets/style.css directory, issue was resolved for me.

/* assets/style.css */
._dash-loading {
margin: auto;
color: transparent;
width: 0;
height: 0;
text-align: center;
}

._dash-loading::after {
content: '';
display: inline-block;
width: 2rem;
height: 2rem;
color: black;
vertical-align: text-bottom;
border: 0.25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border 0.75s linear infinite;
animation: spinner-border 0.75s linear infinite;
margin-top: 2rem;
}

How to show Spinning Wheel or Busy Icon while waiting in Shiny

There is wonderful shinycssloaders package https://github.com/andrewsali/shinycssloaders, now maintained here https://github.com/daattali/shinycssloaders, which does what you want:

library(shiny)
library(dplyr)
library(shinycssloaders)

ui <- fluidPage(

actionButton("plot","plot"),
plotOutput("Test") %>% withSpinner(color="#0dc5c1")
)

server <- function(input, output, session) {


data <- eventReactive(input$plot,{
rnorm(1:100000)
})

output$Test <- renderPlot({
plot(data())
})
}

shinyApp(ui = ui, server = server)

Sample Image

Dash/Plotly - long_callback fails with celery/redis backend

Re-posting the solution from the plotly community forum:

https://community.plotly.com/t/long-callback-with-celery-redis-how-to-get-the-example-app-work/57663

Summary

In order for the long callback to work, I needed to start 3 separate processes that work in conjunction:

  1. the Redis server: redis-server
  2. the Celery app: celery -A app.celery worker --loglevel=INFO
  3. the Dash app: python app.py

The commands listed above are the simplest version. The full commands used are given further down with appropriate modifications.

Details

I moved the declaration of the celery app from src/website/long_callback_manager.py to src/app.py for easier external access:

import dash
import dash_bootstrap_components as dbc
from celery import Celery
from dash.long_callback import CeleryLongCallbackManager

from website.layout_main import define_callbacks, layout

celery_app = Celery(
__name__,
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1"
)
LONG_CALLBACK_MANAGER = CeleryLongCallbackManager(celery_app)

app = dash.Dash(
__name__,
update_title="Loading...",
external_stylesheets=[
dbc.themes.BOOTSTRAP,
"https://codepen.io/chriddyp/pen/bWLwgP.css"
],
long_callback_manager=LONG_CALLBACK_MANAGER
)

app.title = "CS 236 | Project Submissions"
app.layout = layout
define_callbacks(app)
server = app.server # expose for gunicorn

if __name__ == "__main__":
app.run_server(debug=True, host="0.0.0.0")

Then I used the following bash script to simplify the process of starting everything up:

#!/bin/bash
set -e # quit on any error

# make sure the redis server is running
if ! redis-cli ping > /dev/null 2>&1; then
redis-server --daemonize yes --bind 127.0.0.1
redis-cli ping > /dev/null 2>&1 # the script halts if redis is not now running (failed to start)
fi

# activate the venv that has our things installed with pip
. venv/bin/activate

# make sure it can find the python modules, but still run from this directory
export PYTHONPATH=src

# make sure we have a log directory
mkdir -p Log

# start the celery thing
celery -A app.celery_app worker --loglevel=INFO >> Log/celery_info.log 2>&1 &

# start the server
gunicorn --workers=4 --name=passoff_website_server --bind=127.0.0.1:8050 app:server >> Log/gunicorn.log 2>&1

This script's process is then parent to the celery and gunicorn subprocesses and all can be terminated as a bundle by terminating the parent process.



Related Topics



Leave a reply



Submit