Create graph callback Input for when dash Loading component finishes

Hi All,

I’ve been playing around with dash/plotly for the last week and really enjoying the platform.

I have built a web app that has a scattermapbox that displays dots that represent buildings, the user can query through clicking these dots and bring up additional information, all of this is data is stored in one large dataframe.

The end goal of the webapp is to allow users to update values within the dataframe based on the selection of additional attributes and running a function that performs some calculations.

I’ve written a function that is wrapped around a button callback input. When the users clicks a button, the function runs and updates the global dataframe, this is output to a dash Loading component and runs when the Input n_clicks is > 0. The callback returns the timestamp of when the function was run.

This all works perfectly.

What I’m having trouble with is connecting the map callback to accept a change in the loading_state of the dash Loading component. Ideally, once the function is complete the map will re-render with the updated dataframe results. If i adjust another input on the graph callback after the function is run then it correctly renders the updated information.

I’ve written some example code that recreates the scenario.

import dash
import dash_html_components as html
import dash_core_components as dcc
import time
import datetime
import pandas as pd
import plotly.graph_objs as go

from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

some_data = [[1, 10], [2, 20], [3, 30], [4, 40]]

df = pd.DataFrame(some_data, columns=['X', 'Y'])

app.layout = html.Div(
    children=[
        html.H3("Some graph"),
        dcc.Input(id="input-1", value="x-axis title..."),
        dcc.Graph(id="graph-1"),
        dcc.Loading(id="loading-1", children=[html.Div(id="loading-output-1")], type="default"),
        html.Div(
            [
                html.Button(id="button-input", children='Run Function', n_clicks=0),
                dcc.Loading(
                    id="loading-2",
                    children=[html.Div([html.Div(id="loading-output-2")])],
                    type="circle",
                )
            ]
        )
    ],
)


@app.callback(Output("loading-output-1", "children"), [Input("button-input", "n_clicks")])
def input_triggers_spinner(n_clicks):

    if n_clicks > 0:

        update_df = pd.DataFrame([10, 10, 10, 10], columns=['Y'])
        global df

        df.update(update_df)
        time.sleep(3)

        last_run = 'Function Last Run: ' + str(datetime.datetime.now().strftime("%H:%M:%S"))

        return last_run


@app.callback(Output('graph-1', 'figure'),
              [Input('input-1', 'value'), Input('loading-1', 'loading_state')])
def update_graph(textBox, loadingState):

        global df

        # Code here so that the graph callback also fires when the dcc.Loading object changes??

        return {
            'data': [go.Scatter(
                x=df['X'],
                y=df['Y'],
                mode='markers',
                marker={
                    'size': 10,
                    'opacity': 0.5,
                    'line': {'width': 0.5, 'color': 'red'}
                }
            )],
            'layout': go.Layout(
                xaxis={
                    'title': textBox
                }
            )
        }


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

In this scenario, the graph initially displays the some_data values. If the user clicks the ‘Run Function’ button the global df is updated to adjust the values in the Y column of the df. The app callback that defines the graph has an input ‘loading_state’ from the Loading component but I can’t get it to ever actually update the graph…

If the user then adjusts the x-axis title text box, which is also an input for the graph, then the graph callback works and the updated Y values are now displayed.

How can I get this to work as intended?

Or am I even on the right track?

I could obviously wrap the whole function inside the map callback but the function takes a long time to run and is only meant to be run on a subset of the data, the user creates this subset through manipulating other map inputs (sliders, dropdowns, etc).

Thanks!