clstaudt
clstaudt

Reputation: 22458

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?

Upvotes: 6

Views: 26932

Answers (4)

Shovalt
Shovalt

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:

  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.

Upvotes: 12

Alfred Rodenboog
Alfred Rodenboog

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

Anon30
Anon30

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

gustave toison
gustave toison

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

Related Questions