Build a Stop Button to exit a while loop within a Dash App

Hey,

I’m creating an app that has to perform a certain action for a specific set of seconds. This all works but to build in some safety I want to include a stop button, when the user presses this button the system has to stop performing its action as stated in the while loop. I’ve tried several things but the main problem that I encounter is that while in the loop, the value for the stop button does not get updated and therefore I cannot exit. Do you guys have any ideas?

Here is a basic example of the code:

@app.callback(
Output(‘setPoint’, ‘data’),
[Input(‘start-button’, ‘n_clicks’),
Input(‘stop-button’, ‘n_clicks’),
Input(‘inputCOM’, ‘value’)]
)
def set_flow(n_start, n_stop, port):
setpoint = {“SetPoint”: 0}

if n_start:
      current_time = time.time()
      starting = time.time()
      while current_time <= starting_time + 10:
          current_time = time.time()
          setpoint = {"SetPoint": 200}
          print(dt.now(), "Performing Action")
          time.sleep(1.0)
          if n_stop > 0:
                 break

Hello @Danny1,

It is really hard to get access to that without using a function. When you use a function, that say, queries a db for ease of use, and then you have your stop-button insert a row into that db that needs to get a response from the while loop.

However… this is something that is built in to the background callbacks:

I feel like this is more what you are after. :slight_smile:

2 Likes

Hey @jinnyzor ,

thanks for the quick reply. This indeed looks like the thing I need, I will try to implement it and will then come back to hopefully accept the answer as the solution :slight_smile:

2 Likes

I’ve tried your solution and indeed it works, I can now exit the while loop. However, I did run into another problem. In my script I basically want to do 2 things:

  • Get live updates of the current state of the machine
  • Send a signal to the machine to perform an action for x seconds

Because the signal occurs via a COM-port and I can only acces it via one function at the time, I wanted to combine the signal of action together with call for updates. The problem is that when I click the start button to perform the action for 10 seconds, my graph and performer block doesn’t get an update any more for 10 seconds. The whole function block of the graph waits till the other function block has exited the while loop.

I’ve added (the most important parts of the) code below, with set_flow being a function block controlling the setPoint over time (always zero except when the start button gets clicked) and graph being the visualizer of the data and (eventually) being the function that actually calls the machine and writes and reades parameters.

t_test = deque(maxlen=86400)     # Store Time
t_test.append(-1)                             

sp_test = deque(maxlen=86400)         # Store Setpoint
sp_test.append(-1)                    

@app.callback(
    output=Output('setPoint', 'data'),
    inputs=[Input('button_id', 'n_clicks'),
            Input('inputCOM_SP', 'value')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    cancel=[Input("cancel_button_id", "n_clicks")],
)
def set_flow(n_clicks, port):
    setpoint = {"SetPoint": 0}
    current_time = time.time()
    if n_clicks:
        starting = time.time()

        while current_time < starting + 10:
            current_time = time.time()
            setpoint = {"SetPoint": 200}
            time.sleep(1.0)

        setpoint = {"SetPoint": 0}
        return setpoint 
    else:
        setpoint = {"SetPoint": 0}
        return setpoint 

@app.callback(
    Output('test_graph', 'figure'),
    [Input('graph-update', 'n_intervals'),
     Input('setPoint', 'data')]
)
def graph(n, value):
    t_test.append(t_test[-1] + 1)
    sp_test.append(value['SetPoint'])

    data = plotly.graph_objs.Scatter(
        x=list(t_test),
        y=list(sp_test),
        name='Scatter',
        mode='lines+markers'
    )

    return {'data': [data],
            'layout': go.Layout(xaxis=dict(range=[t_test[0], max(t_test)]), yaxis=dict(range=[0, 200]), )}

Does anyone have a solution or work-around for this?

Hello @Danny1,

You may be able to use the set progress in order to add stuff during the whole loop.

For example, instead of having it output to the data, it appends to the data during every loop with a set progress update to the data. This continues to run during the while. And would then add the points to the graphs.

Hello @jinnyzor , thanks for the quick reply. I tried to include a set_progress, but even the simplified example that I added below can’t seem to work together with a start/stop button running on the background. Am I missing something or is it just not possible?

import dash
from dash.dependencies import Output, Input, State
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager
import dash_bootstrap_components as dbc
import plotly
import plotly.graph_objs as go
from plotly.subplots import make_subplots
from collections import deque
from flask import request
import serial.tools.list_ports
import pandas as pd
import time
from datetime import datetime
import os

xxx = deque(maxlen=21)
xxx.append(-1)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),

        dcc.Interval(id="progress-interval", n_intervals=0, interval=1000),
        dbc.Progress(id="progress"),
    ]
)

@app.callback(
    output=[Output("progress", "value"), Output("progress", "label")],
    inputs=[Input("progress-interval", "n_intervals"),
            Input('button_id', 'n_clicks')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    cancel=[Input("cancel_button_id", "n_clicks")],
)
def update_progress(n, n_clicks):
    if n_clicks:
        progress = min(n % 110, 100)
        xxx.append(progress)
        return progress, f"{progress} %"
    else:
        return 0, f"{0} %"

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

Hello @Danny1,

This is more along the lines of what I had in mind. :wink:

import dash
from dash.dependencies import Output, Input, State
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager
import dash_bootstrap_components as dbc
# import plotly
# import plotly.graph_objs as go
# from plotly.subplots import make_subplots
from collections import deque
# from flask import request
# import serial.tools.list_ports
# import pandas as pd
# import time
# from datetime import datetime
import time
import os

xxx = deque(maxlen=21)
xxx.append(-1)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        html.Div(id="progress"),
    ]
)

@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children")],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks):
    if n_clicks > 0:
        n = 0
        while n <= 100:
            progress = n / 100 * 100
            xxx.append(progress)
            set_progress(f"{progress} %")
            n += 10
            time.sleep(1)
    return dash.no_update, 'completed'

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

Hey @jinnyzor ,

now I see what you mean, thanks. I’ve included an example of a live updating graph and now the process indeed happen simultaniously. However, the values that are appended to xxx are not visable in the graph when I simply print the deque. Can this also be fixed?

import dash
from dash.dependencies import Output, Input, State
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager
import dash_bootstrap_components as dbc
import plotly
import plotly.graph_objs as go
from collections import deque
import time
import os
import random
import propar

xxx = deque(maxlen=21)
xxx.append(-1)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
    ]
)

### Tester
@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children")],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks):
    if n_clicks > 0:
        n = 0
        while n <= 100:
            progress = n / 100 * 100
            xxx.append(progress)
            set_progress(f"{progress} %")
            n += 10
            time.sleep(1)
    return dash.no_update, 'completed'

### Figure
X = deque(maxlen=86400)     # Max Length for X Component Array (Currently a day)
X.append(-1)                # Append how many values

Y = deque(maxlen=86400)         # Max Length for X Component Array (Currently a day)
Y.append(-1)                    # Append how many values

@app.callback(
    Output('test_graph', 'figure'),
    Input('graph-update', 'n_intervals')
)
def tester(n):
    print(xxx)

    X.append(X[-1] + 1)
    Y.append(Y[-1] + Y[-1] * random.uniform(-0.1, 0.1))

    data = plotly.graph_objs.Scatter(
        x=list(X),
        y=list(Y),
        name='Scatter',
        mode='lines+markers'
    )

    return {'data': [data], 'layout': go.Layout(xaxis=dict(range=[min(X), max(X)]),
                                                yaxis=dict(range=[min(Y), max(Y)]), )}

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

Hello @Danny1,

Take a look at this:

import dash
from dash.dependencies import Output, Input, State
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager
import dash_bootstrap_components as dbc
import plotly
import plotly.graph_objs as go
from collections import deque
import time
import os
import random
from json import JSONEncoder
import json
from collections import deque

class DequeEncoder(JSONEncoder):
    def default(self, obj):
       if isinstance(obj, deque):
          return list(obj)
       return JSONEncoder.default(self, obj)
#import propar

xxx = deque(maxlen=21)
xxx.append(-1)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

print(xxx)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        dcc.Store(id='myData', data=json.dumps(xxx, cls=DequeEncoder)),
        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
    ]
)

### Tester
@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('myData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx):
    if n_clicks > 0:
        n = 0
        xxx = json.loads(xxx)
        while n <= 100:
            progress = n / 100 * 100
            xxx.append(progress)
            set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
            n += 10
            time.sleep(1)
    return dash.no_update, 'completed'

### Figure
X = deque(maxlen=86400)     # Max Length for X Component Array (Currently a day)
X.append(-1)                # Append how many values

Y = deque(maxlen=86400)         # Max Length for X Component Array (Currently a day)
Y.append(-1)                    # Append how many values

@app.callback(
    Output('test_graph', 'figure'),
    Input('graph-update', 'n_intervals'),
    State('myData', 'data')
)
def tester(n, xxx):
    print(json.loads(xxx))

    X.append(X[-1] + 1)
    Y.append(Y[-1] + Y[-1] * random.uniform(-0.1, 0.1))

    data = plotly.graph_objs.Scatter(
        x=list(X),
        y=list(Y),
        name='Scatter',
        mode='lines+markers'
    )

    return {'data': [data], 'layout': go.Layout(xaxis=dict(range=[min(X), max(X)]),
                                                yaxis=dict(range=[min(Y), max(Y)]), )}

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

I tend to shy away from using global variables, as this doesnt really work with multiple users, so this uses a dcc.Store to update and pass the info back and forth. :slight_smile:

Hello @jinnyzor , thanks that is exactly what I needed, I didn’t even know that it was possible like this. Just one more question, do you know if it is possible to append a final value if the cancel button is pressed? Because now if the button gets pressed no values will be appended anymore and the last value from the while loop will be send to the machine. However, I would like to append a final value of zero to assure that the machine stops. Should I include a new callback (I failed in trying to do this option) or is there a way to include it in the same callback? So that when the Stop button is being pushed, a final value of zero will be written.

@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('myData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx):
    xxx = json.loads(xxx)
    xxx.append(0)
    if n_clicks:
        n = 0
        xxx.append(0)
        while n <= 10:
            progress = n / 10 * 100
            value = 20                 
            xxx.append(value)
            set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
            n += 1
            time.sleep(1)
    # And close by setting to zero
    xxx.append(0)
    progress = ""       # Just to be sure
    set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])

    return dash.no_update, 'completed'
1 Like

Yes.

But, it would need to have two dcc.Stores. One is the output from the background callback.

You take the output one and use it and the cancel button as inputs to append to the second one. And the second one is what you use in the intervals.

See if you can make it work from the description. :grin:

Hello @jinnyzor ,

I tried it in a slightly different way and indeed it works, but if I push the stop button one time, then I can’t restart any more as than I always meet the following condition:

@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data'), Input('cancel_button_id', 'n_clicks'), State('stopData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('myData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx, s_click, stop):
    xxx = json.loads(xxx)
    xxx.append(0)
    if n_clicks:
        n = 0
        xxx.append(0)
        while n <= 10:
            if s_click:
                print("Stop", s_click)
                stop.append(0)
                break
            progress = n / 10 * 100
            value = 20                 
            xxx.append(value)
            set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
            n += 1
            time.sleep(1)
    # And close by setting to zero
    xxx.append(0)
    progress = ""       # Just to be sure
    set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
    return dash.no_update, 'completed'

When I tried the way you suggested, I get the error:

dash.exceptions.LongCallbackError: An error occurred inside a long callback: the JSON object must be str, bytes or bytearray, not list
stop = deque(maxlen=21)
stop.append(0)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),

        dcc.Store(id='myData', data=json.dumps(xxx, cls=DequeEncoder)),
        dcc.Store(id='stopData', data=json.dumps(stop,cls=DequeEncoder)),

        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
    ]
)

@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data'), Input('cancel_button_id', 'n_clicks'), State('stopData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('myData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx, s_click, stop):
    xxx = json.loads(xxx)
    xxx.append(0)
    if n_clicks:
        n = 0
        xxx.append(0)
        while n <= 10:
            if s_click:
                stop = json.loads(stop)
                stop.append(10)
                json.dumps(stop, cls=DequeEncoder)
            progress = n / 10 * 100
            puls = 20                   # [mL/min]
            xxx.append(puls)
            set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
            n += 1
            time.sleep(1)
    # And close by setting to zero
    xxx.append(0)
    progress = ""       # Just to be sure
    set_progress([f"{progress} %", json.dumps(xxx, cls=DequeEncoder)])
    return dash.no_update, 'completed'

1 Like

It would have been two different callbacks as well.

Hey @jinnyzor , I tried multiple things but keep running into the same error, even if I try to simplify it as much as possible. If I try to place it in an independent callback and include a progress to update the queue regularly I get:

Traceback (most recent call last):
TypeError: stop_adjuster() missing 1 required positional argument: 'run'

However, I defined it? Am I missing something or am I defining it wrong?

xxx = deque(maxlen=21)
xxx.append(0)

run = deque(maxlen=21)
run.append(0)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        html.Div(id='runShow'),

        dcc.Store(id='myData', data=json.dumps(xxx, cls=DequeEncoder)),

        dcc.Store(id='runData', data=json.dumps(run, cls=DequeEncoder)),

        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
    ]
)

@app.callback(
    output=Output('runShow', 'children'),
    inputs=[Input('cancel_button_id', 'n_clicks'),
            State('runData', 'data')],
    progress=Output('runData', 'data')
)
def stop_adjuster(set_progress, n_clicks, run):
    run = json.loads(run)
    if n_clicks:
        print("STOPPED")
        run.append(0)
    else:
        print("Running")
        run.append(10)
    set_progress(json.dumps(run, cls=DequeEncoder))
    return 'Value = '+str(run[-1])

Sure.

I think it will work better with two callbacks.

Where xxx is the data you want to reference and the other is just data to be added, does that sound right?

Yes, that sounds right. Basically it is like this:

if n_clicks:
       append a 0 to the xxx
else:
       add nothing

But during the background callback you also want to append to xxx, correct?

Yes, that is correct.

Here, give this a try:

import dash
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager, Output, Input, State, ctx
import dash_bootstrap_components as dbc
import plotly
import plotly.graph_objs as go
from collections import deque
import time
import os
import random
from json import JSONEncoder
import json
from collections import deque

class DequeEncoder(JSONEncoder):
    def default(self, obj):
       if isinstance(obj, deque):
          return list(obj)
       return JSONEncoder.default(self, obj)
#import propar

xxx = deque(maxlen=21)
xxx.append(-1)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        dcc.Store(id='myData', data=json.dumps(xxx, cls=DequeEncoder)),
        dcc.Store(id='runData', data=''),
        dcc.Store(id='stopData', data=''),
        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
    ]
)

### Tester
@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('runData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx):
    if n_clicks > 0:
        n = 0
        xxx = json.loads(xxx)
        while n <= 100:
            progress = n / 100 * 100
            xxx.append(progress)
            set_progress([f"{progress} %", json.dumps(xxx[-1], cls=DequeEncoder)])
            n += 10
            time.sleep(1)
    return dash.no_update, 'completed'

@app.callback(
    Output('stopData', 'data'),
    Input("cancel_button_id", 'n_clicks'),
    prevent_initial_call=True
)
def stopData(n1):
    if n1 > 0:
        return 0
    return dash.no_update

@app.callback(
    Output('myData', 'data'),
    Input('stopData','data'),
    Input('runData','data'),
    State('myData', 'data'),
    prevent_initial_call=True
)
def updateData(d1, d2, xxx):
    if d1 or d2:
        xxx = json.loads(xxx)
        print(xxx)
        xxx.append(ctx.triggered[0]['value'])
        return json.dumps(xxx, cls=DequeEncoder)

### Figure
X = deque(maxlen=86400)     # Max Length for X Component Array (Currently a day)
X.append(-1)                # Append how many values

Y = deque(maxlen=86400)         # Max Length for X Component Array (Currently a day)
Y.append(-1)                    # Append how many values

@app.callback(
    Output('test_graph', 'figure'),
    Input('graph-update', 'n_intervals'),
    State('myData', 'data')
)
def tester(n, xxx):
    print(json.loads(xxx))

    X.append(X[-1] + 1)
    Y.append(Y[-1] + Y[-1] * random.uniform(-0.1, 0.1))

    data = plotly.graph_objs.Scatter(
        x=list(X),
        y=list(Y),
        name='Scatter',
        mode='lines+markers'
    )

    return {'data': [data], 'layout': go.Layout(xaxis=dict(range=[min(X), max(X)]),
                                                yaxis=dict(range=[min(Y), max(Y)]), )}

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

So, during the running of the process, it sends the last data to the runData, and if you click stop, it sends data to the stopData. Both are triggers for the myData.

Hey @jinnyzor, indeed this works, thanks! I’ve never heard of this option before. I’m quite new to Dash so sorry if some of the questions seem a bit dumb.

If you still would like to help me, then I’ve got one final problem before I’ve all the building blocks to make the final app. As eventually, I want to create a feedback where data is being checked before I write a new value in the function update_progress(). To do this, I need to write data to a queue in the function tester() and read it in the function update_progress(). At first I thought that it would be very similar to the reverse problem that you already helped me solve (store in update_progress() and read in tester()). However, I tried and I don’t get it to work. It only appends one value and keeps updating this, for instance: [0] → [0, 1] → [0, 2] → [0, 3] instead of: [0] → [0, 1] → [0, 1, 2] → [0, 1, 2, 3]. (Even using the newly learned ctx.triggered returns the same result)

I’ve also tried to again include a progress according to the code sniplet below, but that continuously returns the error that an argument is missing although all arguments are defined and fed to the function.

I’ve included the code below that returns the values as described earlier. Is this impossible due to the n_interval over this function? This is what I thought after trying and therefore decided to put it into an independent callback:

import dash
from dash import dcc, html, dash_table, DiskcacheManager, CeleryManager, Output, Input, State, ctx
import dash_bootstrap_components as dbc
import plotly
import plotly.graph_objs as go
from collections import deque
import time
import os
import random
from json import JSONEncoder
import json
from collections import deque

class DequeEncoder(JSONEncoder):
    def default(self, obj):
       if isinstance(obj, deque):
          return list(obj)
       return JSONEncoder.default(self, obj)
#import propar

xxx = deque(maxlen=21)
xxx.append(0)

fb = deque(maxlen=5)
fb.append(0)

### Create Necessities for Background Proces
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)

### Assign and Call App
app = dash.Dash(__name__, background_callback_manager=background_callback_manager)

app.layout = html.Div(
    [
        dbc.Button(id="button_id", children="Start Pulsing"),
        dbc.Button(id="cancel_button_id", children="STOP", style={'backgroundColor': 'red', 'color':'white'}),
        dcc.Store(id='myData', data=json.dumps(xxx, cls=DequeEncoder)),
        dcc.Store(id='runData', data=''),
        dcc.Store(id='stopData', data=''),
        html.Div(id="progress"),
        dcc.Graph(id='test_graph', animate=True),
        dcc.Interval(
            id='graph-update',
            interval=1000,
            n_intervals=0
        ),
        dcc.Store(id='test_value'),
        html.Div(id='show_test_value'),
        dcc.Store(id='fbData', data=json.dumps(fb, cls=DequeEncoder)),
    ]
)

### Tester
@app.callback(
    output=[Output("button_id", "id"), Output("progress", "children")],
    inputs=[Input('button_id', 'n_clicks'), State('myData', 'data')],
    background=True,
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
    ],
    progress=[Output("progress", "children"), Output('runData', 'data')],
    cancel=[Input("cancel_button_id", "n_clicks")],
    prevent_initial_call=True
)
def update_progress(set_progress, n_clicks, xxx):
    if n_clicks > 0:
        n = 0
        xxx = json.loads(xxx)
        while n <= 100:
            progress = n / 100 * 100
            xxx.append(20)
            set_progress([f"{progress} %", json.dumps(xxx[-1], cls=DequeEncoder)])
            n += 10
            time.sleep(1)
    return dash.no_update, 'completed'

@app.callback(
    Output('stopData', 'data'),
    Input("cancel_button_id", 'n_clicks'),
    prevent_initial_call=True
)
def stopData(n1):
    if n1 > 0:
        return 0
    return dash.no_update

## Show Last Value
@app.callback(
    Output('show_test_value', 'children'),
    Input('test_value', 'data'),
    State('fbData', 'data')
)
def test_shower(value, fb):
    fb = json.loads(fb)
    fb.append(value['Value'])
    json.dumps(fb, cls=DequeEncoder)
    print(fb)
    return value['Value']

@app.callback(
    Output('myData', 'data'),
    Input('stopData','data'),
    Input('runData','data'),
    State('myData', 'data'),
    prevent_initial_call=True
)
def updateData(d1, d2, xxx):
    if d1 or d2:
        xxx = json.loads(xxx)
        xxx.append(ctx.triggered[0]['value'])
        return json.dumps(xxx, cls=DequeEncoder)

### Figure
X = deque(maxlen=86400)     # Max Length for X Component Array (Currently a day)
X.append(0)                # Append how many values

Y = deque(maxlen=86400)         # Max Length for X Component Array (Currently a day)
Y.append(0)                    # Append how many values

@app.callback(
    [Output('test_graph', 'figure'),
    Output('test_value', 'data')],
    Input('graph-update', 'n_intervals'),
    State('myData', 'data')
)
def tester(n, xxx):
    q = json.loads(xxx)

    X.append(X[-1] + 1)
    Y.append(float(q[-1]))

    test_value = {"Value" : X[-1]}

    data = plotly.graph_objs.Scatter(
        x=list(X),
        y=list(Y),
        name='Scatter',
        mode='lines+markers'
    )

    return {'data': [data], 'layout': go.Layout(xaxis=dict(range=[min(X), max(X)]),
                                                yaxis=dict(range=[float(min(Y)), float(max(Y))]), )}, test_value

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