facultyai / dash-bootstrap-components

Bootstrap components for Plotly Dash
https://dash-bootstrap-components.opensource.faculty.ai/
Apache License 2.0
1.11k stars 220 forks source link

Help with using the spinner #198

Closed aditya00j closed 5 years ago

aditya00j commented 5 years ago

Hi, Sorry for using GitHub issues to ask a newbie question, but I couldn't find another contact info to get in touch. First of all thanks to all the developers for dbc, they have been immensely useful for me to provide a consistent user experience for our Dash apps.

I am a novice in HTML/CSS/Javascript programming, so I tend to focus on finding pythonic ways to implement all the functionality in my Dash apps. This might be the reason I am not able to figure out how to use the Spinner component. Can somebody give me a simple example of how to replace a specific dbc component by a spinner while the component is loading or performing an action? Here is my toy code:

# -*- coding: utf-8 -*-
import dash
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
import time

from dash.dependencies import Input, Output, State

app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.scripts.config.serve_locally = True

app.layout = html.Div(
    children=[
        html.Div("Clicking the button should replace the button with spanner while the callback is active"),
        dbc.Button("Click Here", id="button-1", color="primary", className="mr-1"),
        html.Div(id="output-1")
    ]
)

@app.callback(Output("output-1", "children"), [Input("button-1", "n_clicks")])
def input_triggers_spinner(value):
    if value is None:
        return "Ready"
    else:
        time.sleep(2)
        return "Success #{}".format(value)

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

If instead of replacing the button, it's easier to show the spinner somewhere else (on its side for example), that would be fine for me as well. I just need to give the user a visual indication that the function is computing.

Sorry again for putting this in an issue, I know that's not what the issues are for :) Cheers!

tcbegley commented 5 years ago

Hi @aditya00j, really glad you're enjoying dash-bootstrap-components

At the moment it is a bit hard to use the Spinner. I would recommend you look at the Loading component in dash-core-components, see the docs here. We are planning to provide our own version of this that uses Bootstrap spinners.

You can do something manually with our Spinner component, but it is harder because you have to implement things asynchronously. As a starting point I would suggest looking at this example.

More generally using some kind of task queue like Celery or Redis Queue to run tasks in the background would let you add spinners to the layout while tasks are running. That's quite a lot of overhead for just adding a spinner though, so I would recommend using the Loading component for now, and we'll work on making it Bootstrap specific like I mentioned.

aditya00j commented 5 years ago

Hi, Thanks for your reply! The Loading component was my backup, I just wanted to use the bootstrap components as much as possible. A similar functionality with spinner would be a very useful feature to add!

GitHunter0 commented 1 year ago

Hi, @tcbegley , any news on this regard?

Edit: For example, I would like an "apply" button that gets an spinner inside of it while the calculations are not done.

tcbegley commented 1 year ago

Hi @GitHunter0,

What you're describing can be achieved with a couple of dcc.Store components and some careful callback definitions, see the example below.

This is only really suitable if the computation takes a few seconds. For anything that takes longer I would recommend you look into running that computation asynchronously in the backend. This can be achieved manually (see for example this example) or these days you can make use of background callbacks.

Anyway, here's the simple example. When the button is clicked, we log an id in the start store and disable the button. The start store triggers the computation. The computation callback updates the value of the button text which means the spinner will spin while the callback is running. It also updates the complete store which reenables the button.

import time
from uuid import uuid4

import dash_bootstrap_components as dbc
import numpy as np
import plotly.graph_objs as go
from dash import Dash, Input, Output, State, dcc, html
from dash.exceptions import PreventUpdate

app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP], prevent_initial_callbacks='initial_duplicate')

app.layout = dbc.Container(
    [
        dcc.Store(id="start", data=""),
        dcc.Store(id="complete", data=""),
        dbc.Button(dbc.Spinner(html.Span("Compute", id="button-label")), id="button"),
        dcc.Graph(id="graph"),
    ],
    className="p-5",
)

@app.callback( 
    Output("button", "disabled", allow_duplicate=True), 
    Output("start", "data"), 
    Input("button", "n_clicks"),
)
def register_start(n):
    if n:
        return True, str(uuid4())
    raise PreventUpdate

@app.callback(Output("button", "disabled", allow_duplicate=True), Input("complete", "data"), State("start", "data"))
def enable_button(complete_value, start_value):
    return complete_value != start_value

@app.callback(
    Output("graph", "figure"),
    Output("button-label", "children"),
    Output("complete", "data"),
    Input("start", "data")
)
def make_graph(data):
    # fake slow computation
    time.sleep(2)
    x = np.random.random(10)
    y = np.random.random(10)
    return go.Figure(data=go.Scatter(x=x, y=y, mode="markers")), "Compute", data

if __name__ == "__main__":
    app.run_server(debug=True)
GitHunter0 commented 1 year ago

Hey @tcbegley , thanks a lot for the detailed feedback.