In a plotly dash app, how to show a default text value in a div upon each click (on a dropdown, or button, etc) until the div is populated?

I am making a dash app where there’s a dropdown. Depending on the value selected in the dropdown, I do some calculations in the backend and display a figure.

import dash
from dash.dependencies import Output, Event, Input
import dash_core_components as dcc
import dash_html_components as html

app.layout = html.Div([
    dcc.Dropdown(
        id='dataset-dropdown',
        options=[
            {'label': 'Option A', 'value': 'A'},
            {'label': 'Option B', 'value': 'B'},
            {'label': 'Option C', 'value': 'C'}
        ],
        placeholder="Select an option",
    ),

    html.Div(id='output-container'),
])



def function_to_draw(input_csv_file):
	# make the figure
	return plotly_fig_object



@app.callback(
    dash.dependencies.Output('output-container', 'children'),
    [dash.dependencies.Input('dataset-dropdown', 'value')])
def update_output(value):
    if value=='A':
        plotly_fig = function_to_draw('Data_of_A.csv')
        return plotly_fig
    elif value=='B':
        plotly_fig = function_to_draw('Data_of_B.csv')
        return plotly_fig
    elif value=='C':
        plotly_fig = function_to_draw('Data_of_C.csv')
        return plotly_fig

    return dcc.Graph(
        id='plot_graph',
        figure={
            'data': plotly_fig
        }
    )



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

Now, it takes some time to do these backend calculations (5-10 seconds). So, what I want is, every time a value is clicked on the dropdown menu, it should show something like “Calculating…” or “Loading…” for those 5-10 seconds until the figure is displayed.

How do I do that?

I have tried adding a “placeholder” div to store the “Loading…” and added a callback function to display it upon click:

import dash
from dash.dependencies import Output, Event, Input
import dash_core_components as dcc
import dash_html_components as html

app.layout = html.Div([
    dcc.Dropdown(
        id='dataset-dropdown',
        options=[
            {'label': 'Option A', 'value': 'A'},
            {'label': 'Option B', 'value': 'B'},
            {'label': 'Option C', 'value': 'C'}
        ],
        placeholder="Select an option",
    ),

    html.Div(id='output-container'),

    html.P(id="placeholder",children=["placeholder"]),
    dcc.Interval(
        id='clear-div',
        interval=1*1000
    ),

])




@app.callback(
    Output('placeholder', 'children'),
    [Input(component_id='dataset-dropdown', component_property='value')],
    events=[Event('clear-div', 'interval')]
)
def reset_div(input_data):
    return "Calculating..."




@app.callback(
    dash.dependencies.Output('output-container', 'children'),
    [dash.dependencies.Input('dataset-dropdown', 'value')],
    events=[Event('dataset-dropdown', 'click')])
def update_output(value):
    if value=='A':
        return "You selected A"
    elif value=='B':
        plotly_fig = function_to_draw('B')
        return plotly_fig
    elif value=='C':
        plotly_fig = function_to_draw('C')
        return plotly_fig

    return dcc.Graph(
        id='plot_graph',
        figure={
            'data': plotly_fig
        }
    )



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

But it doesn’t work as intended - it only shows the “Loading” on the initial dropdown selection. After that, it does not clear the output div to show “Loading…” in the subsequent dropdown selections; the existing figure in the output div persists.

This isn’t really possible right now. You can global level loading state with 📣 Dash Loading States, but we’re looking to add support for per-component loading states in https://github.com/plotly/dash/issues/267

I think I found a really simple solution.
First add a div in app.layout, for example html.Div(id='load')
Then add a function like this:

@app.callback(Output('load', 'children'),
              [Input('dataset-dropdown', 'value')])
def prepare_data(input_data):
        return html.Div([dcc.Markdown(
                         '''Loading ...''')], id='output-container')

And it works for me!
The trick is to specify id='output-container'. So the output would be refreshed when your update_output() function is finished.

1 Like

Oh interesting, so input :arrow_right: loading div (which also contains the computed data as a hidden store?) :arrow_right: output control?

Could you share a full, complete example? To simulate an expensive computation, you could just do a time.sleep

from dash.dependencies import Input, Output
import dash
import dash_core_components as dcc
import dash_html_components as html
import time

app = dash.Dash()

app.layout = html.Div(children=[

    html.Div([dcc.Dropdown(id='category', placeholder='Select category', multi=False,
                           options=[{'label': 'giraffes', 'value': 'giraffes'},
                                    {'label': 'orangutans', 'value': 'orangutans'},
                                    {'label': 'monkeys', 'value': 'monkeys'}]
                           )
              ], style={'max-width': 260}),

    html.Div(id='load'),
    html.Div(id='output'),
])

@app.callback(Output('load', 'children'),
              [Input('category', 'value')])
def prepare_data(categ):
    if categ:
        return html.Div([dcc.Markdown(
                         '''Loading ...''')], id='output')

@app.callback(Output('output', 'children'),
              [Input('category', 'value')])
def prepare_data(categ):
    if categ:
        time.sleep(2)
        return html.Div([dcc.Markdown(
                         '''Output for {}'''.format(categ))])

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

Hi @chriddyp, this is the code for a complete example.

6 Likes

Very clever! Thank you very much for sharing.

One interesting thing about this is that you need the html.Div(id='output') inside the app.layout. This is sort of a limitation with Dash where all of the inputs and outputs need to be present on the page at once, so you couldn’t just have id='output' returned from the callback, you needed to have it initialized in the app.layout. That ended up working out for this example since you want the content of output to be empty anyway.

Very cool, thanks again for sharing!

Thanks a lot @caiyij for that solution. It was exactly what I was looking for.

A post was split to a new topic: Mpl_to_plotly limitations

I’m trying to get this to work with storing data in a hidden Div, but seems that the “Loaded!” text immediately gets overwritten with “Loading…” again. Anybody have an idea why?

from dash.dependencies import Input, Output, State
import dash
import dash_core_components as dcc
import dash_html_components as html
import time

app = dash.Dash()

app.layout = html.Div(children=[

    html.Div([dcc.Dropdown(id='category', placeholder='Select category', multi=False,
                           options=[{'label': 'giraffes', 'value': 'giraffes'},
                                    {'label': 'orangutans', 'value': 'orangutans'},
                                    {'label': 'monkeys', 'value': 'monkeys'}]
                           )
              ], style={'max-width': 260}),

    html.Div(id='load'),
    html.Div(id='load_dummy'),
    html.Div(id='output'),
    html.Div(id='hidden-div', children=None, style={'display': 'none'}),
])

@app.callback(Output('load', 'children'),
              [Input('category', 'value')])
def prepare_data(categ):
    if categ:
        return html.Div([dcc.Markdown('''Loading ...''')])
    else:
        return html.Div()

@app.callback(Output('hidden-div', 'children'),
              [Input('category', 'value')])
def prepare_data(categ):
    if categ:
        time.sleep(2)
        return categ

@app.callback(Output('output', 'children'),
              [Input('hidden-div', 'children')])
def prepare_data(hidden_div):
    if hidden_div:
        return html.Div([dcc.Markdown(
                         '''selected: {}'''.format(hidden_div))])

@app.callback(Output('load_dummy', 'children'),
              [Input('hidden-div', 'children')])
def prepare_data(output):
    return html.Div([dcc.Markdown('''loaded!''')], id = 'load')

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

In my code I was using

return html.Div([dcc.Markdown(
                         '''Loading ...''')], id='output')

Not

return html.Div([dcc.Markdown('''Loading ...''')])

which is in your code.

But this behaviour can be bypassed with app.config[‘suppress_callback_exceptions’]=True, can’t it?

Nice! thank you for sharing. This is clever indeed

I’m finding odd behavior in your example code (which is awesome) - it seems a the second prepare_data routine is called twice for each select from the drop down list. It doesn’t made sense from the event model but I see the behavior with drop downs and buttons.
I added a sleep in the second prepare_data and a couple of print statements.

Do you see double events ?

Thanks for the great example !

Of course, it’s a workaround solution since dash doesn’t support multiple callbacks to one output…

I did find an interesting work around - have a look at the code below - inspired by your code

from dash.dependencies import Input, Output, State
import dash
import dash_core_components as dcc
import dash_html_components as html
import time

app = dash.Dash()

app.layout = html.Div(children=[

    html.Div([dcc.Dropdown(id='category', placeholder='Select category', multi=False,
                           options=[{'label': 'giraffes', 'value': 'giraffes'},
                                    {'label': 'orangutans', 'value': 'orangutans'},
                                    {'label': 'monkeys', 'value': 'monkeys'}]
                           )
              ], style={'max-width': 260}),
    html.Div(id='output'),
])


@app.callback(Output('output', 'children'),
              [Input('category', 'value')],
              [State('output', 'children')])
def prepare_data(categ, current):
    if categ and current:
        print('prepare_data - generating data')
        time.sleep(10)
        return html.Div([dcc.Markdown('''Output for {}'''.format(categ))])
    elif categ and not current:
        print('prepare_data - current was null')
        return html.Div([dcc.Markdown('''Loading ...''')])


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

I don’t think your code work. Have you seen my example? It should show loading and then the output for every input. But yours only work for first selection, and when selecting another dropdown it does not show the loading text.

Strange since it works for me. I’m worried about my set up.

Using your code I get the following an animal is selected

Call to first prepare_data
Call to second prepare_data
Call to first prepare_data

I’m checking again.

Thanks for being willing to discuss.

I only looked at the first selection … you are of course absolutely correct.