Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

enter image description here

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?

like image 432
clstaudt Avatar asked Dec 23 '22 01:12

clstaudt


1 Answers

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.
like image 168
Shovalt Avatar answered Dec 25 '22 16:12

Shovalt