Repetitive Callbacks

Hello, new learner here. I am looking for a way to make a callback repetitive. I am using plotly.subplots with a variable amount of subplots. Right now I have a callback which starts a for-loop that needs to crunch data incrementally, one graph at a time. Dash only updates at the end of the for-loop. This is the dumbed-down version of the code:

@app.callback(Output('graph', 'figure'), Input('trace', 'n_clicks'),  State('graph', 'figure')],
               running=[(Output('trace', 'disabled'), True, False)], prevent_initial_call=True) 
def update_figure(n_clicks,  fig):
        fig = go.Figure(fig) # Redefine figure
        subplot = subplots.make_subplots(figure=fig) # Redefine subplot
        for item in list:
             data = cruncher.crunch(item) # Data crunch w/ 'item' corr. to row/col
             subplot.add_trace(subplot,data, row, col) # trace added to bank
    return fig # Graph updates all at once

I want to give it some of that dynamic spice. :hot_pepper:
I’m looking for a way to see each graph as they come, not all at the end. I have tried using chained callbacks but keep getting circular dependency error which makes sense. Any ideas?

One way of doing step-by-step updates is to use dcc.Interval(), like in the simple example below.

However I think this may possibly hit problems if the next interval fires before the previous callback has completed - it could be someone has a better approach.

from dash import Dash, dcc, html, Input, Output, State
app = Dash(__name__)
app.layout = html.Div(children=[
        html.H1("Step updates"),
        html.Div(id="content-div", children=[]),
        dcc.Interval(id="step-interval",max_intervals=5)
    ])

@app.callback(
    Output("content-div", "children"),
    Input("step-interval", "n_intervals"),
    State("content-div", "children"),
    prevent_initial_call=True
)
def update_content(n_intervals, children):
    if children is None:
        children = []
    children.append(html.H3(str(n_intervals)))
    return children

if __name__ == "__main__":
    app.run_server()
1 Like

I have been fiddling with dcc.interval and yah I don’t think it will work. I set it as a kind of flag to update the graph every so often like this:

@app.callback(Output('graph', 'figure'), Input('interval-component', 'n_intervals'), 
              State('graph', 'figure'), prevent_initial_call=True)  
def update_figure(n_clicks, fig):
    fig = go.Figure(fig) # Redefine figure
    return fig

But now the update_figure callback function I described above won’t output anything. This method may work but I feel there has to be a better option.

Hello @GaNick,

I believe that in version 2.17, you can use set_props and a background callback and use it to dynamically set props as you go. :slight_smile:


Here is an example:

import time
import os

from dash import Dash, DiskcacheManager, CeleryManager, Input, Output, html, callback, set_props

if 'REDIS_URL' in os.environ:
    # Use Redis & Celery if REDIS_URL set as an env variable
    from celery import Celery
    celery_app = Celery(__name__, broker=os.environ['REDIS_URL'], backend=os.environ['REDIS_URL'])
    background_callback_manager = CeleryManager(celery_app)

else:
    # Diskcache for non-production apps when developing locally
    import diskcache
    cache = diskcache.Cache("./cache")
    background_callback_manager = DiskcacheManager(cache)

app = Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        html.Div(
            [
                html.P(id="paragraph_id", children=["Button not clicked"]),
                html.Progress(id="progress_bar", value="0"),
            ]
        ),
        html.Button(id="button_id", children="Run Job!"),
        html.Button(id="cancel_button_id", children="Cancel Running Job!"),
        *[html.Div(id=f'{x}') for x in range(6)]
    ]
)

@callback(
    output=Output("paragraph_id", "children"),
    inputs=Input("button_id", "n_clicks"),
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
        (
            Output("paragraph_id", "style"),
            {"visibility": "hidden"},
            {"visibility": "visible"},
        ),
        (
            Output("progress_bar", "style"),
            {"visibility": "visible"},
            {"visibility": "hidden"},
        ),
    ],
    cancel=Input("cancel_button_id", "n_clicks"),
    progress=[Output("progress_bar", "value"), Output("progress_bar", "max")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks):
    total = 5
    for i in range(total + 1):
        set_progress((str(i), str(total)))
        set_props(f'{i}', {'children': f"Clicked {n_clicks} times"})
        time.sleep(1)

    return f"Clicked {n_clicks} times"


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

This works thanks!