Reputation: 22458
In my Dash-based application, a button triggers a long-running computation. Wouldn't it be nice to display a loading animation while the result is not yet there, and make the button inactive so it is not clicked again before the computation finishes?
I am using Bulma for UI design and wanted to use the button is-loading
CSS class for that purpose.
My first idea was to have two callbacks: One triggered by the button click to set the button to is-loading
, and one triggered by a change in the output to set it back to normal.
@app.callback(
Output('enter-button', 'className'),
[
Input('graph', 'figure')
],
)
def set_trend_enter_button_loading(figure_changed):
return "button is-large is-primary is-outlined"
@app.callback(
Output('enter-button', 'className'),
[
Input('enter-button', 'n_clicks')
],
)
def set_trend_enter_button_loading(n_clicks):
return "button is-large is-primary is-outlined is-loading"
Apparently it doesn't work that way:
dash.exceptions.CantHaveMultipleOutputs:
You have already assigned a callback to the output
with ID "enter-button" and property "className". An output can only have
a single callback function. Try combining your inputs and
callback functions together into one function.
Any ideas how to make this work?
Upvotes: 6
Views: 26932
Reputation: 6776
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:
_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._dash-loading-callback
.Updates 2022
_dash-loading-callback
to _dash-loading
in newer versions. I didn't get the chance to test this yet.Upvotes: 12
Reputation: 403
To add to the answer of Gustave, there is now a Dash-option for loading animations that has come out of alpha and beta.
You simply declare a dcc.Loading
component and add it to the layout. Then add it as an Output
to the callback that is taking a long time and you want the loader to display.
Just adding this here, because that's what I went with after some research and testing the other options in this thread.
Upvotes: 3
Reputation: 606
Loader with custom GIF
Just create a folder named assets in the root of your app directory and include your CSS and JavaScript files in that folder. Dash will automatically serve all of the files that are included in this folder. By default the url to request the assets will be /assets but you can customize this with the assets_url_path argument to dash.Dash.
Create a customer css file under style folder
- app.py
- assets/
|-- loader.css
custom css
._dash-loading-callback {
position: fixed;
z-index: 100;
}
._dash-loading-callback::after {
content: 'Loading';
font-family: sans-serif;
padding-top: 50px;
color: #000;
-webkit-animation: fadein 0.5s ease-in 1s forwards; /* Safari, Chrome and Opera > 12.1 */
-moz-animation: fadein 0.5s ease-in 1s forwards; /* Firefox < 16 */
-ms-animation: fadein 0.5s ease-in 1s forwards; /* Internet Explorer */
-o-animation: fadein 0.5s ease-in 1s forwards; /* Opera < 12.1 */
animation: fadein 0.5s ease-in 1s forwards;
/* prevent flickering on every callback */
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
/* The banner */
opacity: 0;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
text-align: center;
cursor: progress;
z-index: 100000;
background-image: url(https://www.w3schools.com/html/programming.gif);
background-position: center center;
background-repeat: no-repeat;
}
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
/* Firefox < 16 */
@-moz-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
/* Safari, Chrome and Opera > 12.1 */
@-webkit-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
/* Internet Explorer */
@-ms-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
/* Opera < 12.1 */
@-o-keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
Upvotes: 1
Reputation: 63
Since dash-renderer==0.9.0, a div tag is added to your layout when your app is waiting for a callback. You can put a different css in it as suggested here by Chris: https://community.plot.ly/t/mega-dash-loading-states/5687
Also, a new feature is coming which fit your needs. It is currently in alpha version: https://community.plot.ly/t/loading-states-api-and-a-loading-component-prerelease/16406
Upvotes: 1