📣 Loading states API and a Loading component prerelease


#1

Hello Dash Community!

Lately I’ve been working on bringing loading states to Dash, by building a way for dash-renderer to let individual components it’s trying to render know that they’re being loaded via a loading_state prop. It injects this prop in all components, which is a JSON object holding an is_loading bool that determines if it’s being loaded, component_name that holds the name of the component that’s being loaded, and prop_name, which holds the name of the prop that’s being loaded. This injection allows component authors to determine what to render when a component is being loaded, for example when the component takes a couple of seconds to render, it could display a loading spinner until the loading_state.is_loading bool is false! This gives more control in what to display, and it’s available as a prerelease under dash-renderer==0.16.0rc1. Here’s the PR for it, if you want to keep up with it.

I’ve also been working on an accompanying Loading component, that uses this new loading_state prop to render some pre-made spinners. You can use it to wrap components in that you want to display a spinner for. Say you have a callback that takes a little while, and you want the output of that callback to display a spinner until it’s done. You could wrap the component in the new Loading component, and it would display a spinner until the callback is done. The Loading component also has props for displaying it fullscreen, and you can customize the colors, style, and className.

Here’s what it could look like:

I’m using a CSS framework called Tachyons here by the way, which I think works well with Dash.

The code looks like this:

# -*- coding: utf-8 -*-
import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.graph_objs as go
import time

from dash.dependencies import Input, Output, State

external_stylesheets = [
    "https://unpkg.com/tachyons@4.10.0/css/tachyons.min.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

card_style = {
    "box-shadow": "0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3)"
}

app.scripts.config.serve_locally = True

app.layout = dcc.Loading(
    children=[html.Div(
        className="sans-serif",
        children=[
            html.Div(
                [
                    html.H1(
                        className="tc f1 hot-pink mv6", children=["Dash Loading States API"]
                    )
                ]
            ),
            html.Div(
                className="w-60 center pt4",
                children=[
                    dcc.Tabs(
                        id="tabs",
                        value="tab-1",
                        children=[
                            dcc.Tab(label="Normal Graph", value="tab-1",
                                    style={'backgroundColor': '#f5f5f5'}),
                            dcc.Tab(label="Funky Graph", value="tab-2",
                                    style={'backgroundColor': '#f5f5f5'}),
                        ],
                        colors={
                            "primary": "white",
                            "background": "white",
                            "border": "#d2d2d2",
                        },
                        parent_style=card_style,
                    ),
                    html.Div(
                        children=[
                            dcc.Loading(id='tabs-content',
                                        type='graph', className='pv6')
                        ],
                        className='pa4'
                    ),
                ],
                style={},
            ),
            html.Div(
                className="w-80 center",
                children=[
                    dcc.Loading(id='output-1', color='gold')
                ],
            ),
            html.Div(
                className="w-80 center",
                children=[
                    dcc.Input(
                        className="db center mv4 ph2 pv1", id="input-1", value="Type here!"
                    )
                ],
            ),
        ],
    )], type='cube', fullscreen=True)


@app.callback(Output('output-1', 'children'), [Input('input-1', 'value')])
def render_text(value):
    time.sleep(2)
    return html.H1([value], className='tc gold')


@app.callback(Output('tabs-content', 'children'),
              [Input('tabs', 'value')])
def render_content(tab):
    time.sleep(2)
    if tab == 'tab-1':
        return html.Div(id='loading-1', children=[
            dcc.Graph(
                id='graph-2-tabs',
                figure=go.Figure(
                    data=[
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                               350, 430, 474, 526, 488, 537, 500, 439],
                            name='Rest of world',
                            marker=go.bar.Marker(
                                color='rgb(55, 83, 109)'
                            )
                        ),
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                               299, 340, 403, 549, 499],
                            name='China',
                            marker=go.bar.Marker(
                                color='rgb(26, 118, 255)'
                            )
                        )
                    ],
                    layout=go.Layout(
                        title='US Export of Plastic Scrap',
                        showlegend=True,
                        legend=go.layout.Legend(
                            x=0,
                            y=1.0
                        ),
                        margin=go.layout.Margin(l=40, r=0, t=40, b=30)
                    )
                ),
            )
        ])
    elif tab == 'tab-2':
        return html.Div(id='loading-2', children=[
            dcc.Graph(
                id='graph-1-tabs',
                figure=go.Figure(
                    data=[
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[219, 146, 112, 127, 124, 180, 236, 207, 236, 263,
                               350, 430, 474, 526, 488, 537, 500, 439],
                            name='Rest of world',
                            marker=go.bar.Marker(
                                color='hotpink'
                            )
                        ),
                        go.Bar(
                            x=[1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
                               2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012],
                            y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156, 270,
                               299, 340, 403, 549, 499],
                            name='China',
                            marker=go.bar.Marker(
                                color='gold'
                            )
                        )
                    ],
                    layout=go.Layout(
                        title='US Export of Plastic Scrap',
                        showlegend=True,
                        legend=go.layout.Legend(
                            x=0,
                            y=1.0
                        ),
                        margin=go.layout.Margin(l=40, r=0, t=40, b=30)
                    )
                ),
            )
        ])


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

The Loading component’s type prop can be default, dot, circle, graph, or cube, and the component is available as a prerelease under dash-core-components==0.41.0rc1, so if you want to use these prereleases in your own app, be sure to run:

pip install dash-renderer==0.16.0rc1
pip install dash-core-components==0.41.0rc4

first!

Lastly, here’s the PR for the Loading component if you want to follow along!

And of course, please let us know what you think!


:mega: Dash Loading States
#2

Works great! Was looking for something like this for some time.

Had to ‘pip install dash-core-components==0.41.0rc2’ which was mentioned in the PR for the Loading Component.

Keep up the great work.


#3

#4

Ah yes - forgot to update the rc version in this post (it’s now up to rc4 even!) thanks! Glad you like it!


#5

This is awesome and looking forward to it being released.

Is it possible though to accomplish this without requiring a new component (dcc.Loading) to wrap the Graph or other components? It would require less refactoring if it could be accomplished by adding a parameter to the component (say 'dcc.Graph(id=…, loading={‘type’:‘cube’, …}) ?


#6

Hey! I’m happy to see this moving forward!
I hope I can get rid of my own dash-renderer fork finally, which I keep only to have a custom loading react component: https://github.com/Grasia/dash-renderer/blob/master/src/components/core/Loading.react.js
Following that, I would suggest a way to provide a react component to be rendered inside the dcc.Loading component, so that we can all create our customized loading components and inject them into the wrapper one.

Also, I agree with @zoohair that it is a bit odd having to place the “real component” you want to display in the callbacks. It makes the code less readable and logical, but I don’t see another option to do it either.