Interval graph refresh rate delay and zoom

Hi,
I’m developing a dash app that control a parallel subprocess, the objetive of the dash app is start; stop; configure; read queue and plot the data, and the subprocess generate the data and put there in a queue. This is a demo version that generates data with numpy for simplicity when press start button.

I have two problems:
First, I use dash.interval to refresh the dash.Graph continuosly one times per second, but I can’t zoom in over the plot because every time the dash.Graph is updated the figure resets, Is there a way to prevent the figure from being reset?

Second, When I press the app start button it takes approximately 3 seconds to show the first graph. I was printed in terminal and discovered that the callback send plots, but in app have a delay in displaying them. Is there a way to solve this?

The code:

from dash import Dash, dcc, html, Input, Output, State
from dash.exceptions import PreventUpdate
import dash_bootstrap_components as dbc
from multiprocessing import Process, Value, Array, Event, Queue, Semaphore
from time import sleep
import numpy as np
import plotly.express as px
import logging

def run(config_q,dataout_q,event,start_smp):
    """
    Function to run in a subprocess in parallel with dash app
    
    Parameters
    ----------
    - config_q : [multiprocessing.queues.Queue]
                 Queue with the setings for the process
    - dataout_q : [multiprocessing.queues.Queue]
                  Queue with the generated data
    - event : [multiprocessing.synchronize.Event] 
              Multiprocess variable to safetly stop the process
    - start_smp : [multiprocessing.synchronize.Semaphore]
                  Sempahore to start stop the process data generation
    """
    #Process start
    #clean the deafult values in queue
    config=config_q.get()
    while(True):
        #Process Main-loop
        if start_smp.acquire(block=False) == True:
            start_smp.release()
            config=config_q.get()
            try:
                #Use of configuration info
                datos=np.ones((1,config["samps"]))
                while True:
                    #Output secuence
                    if event.is_set():
                        print("\nProcess-End proccess")
                        break
                    #inputs change update
                    if start_smp.acquire(block=False) == True:
                        start_smp.release()
                        #Data generation
                        datos=np.sin(2*np.pi*config["frec"]*np.linspace(0,(1/config["frec"])*config["samps"],config["samps"]))
                        datos=datos.reshape((1,config["samps"]))
                        #write data in queue
                        #clean the queue
                        try:
                            dataout_q.get(block=False,timeout=0)
                        except:
                            pass
                        dataout_q.put(datos)
                    else:
                        #Stop the data generation
                        break
            except Exception as e:
                print("task fail")
                print(e)

if __name__ == '__main__':
    #default config
    config_d={
                "samps":10000,
                "frec":10000,
                "range":10,
                "dev":"Dev1/"
        }
    #Multiprocessing definitions
    dataout_q = Queue(maxsize=1)
    config_q = Queue(maxsize=1)
    start_smp = Semaphore(2)
    start_smp.acquire(block=False)
    start_smp.acquire(block=False)
    config_q.put(config_d)
    event = Event()
    process1 = Process(target=run,     
                       args=(config_q,dataout_q,event,start_smp))
    process1.start()

    app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME])
 
    #layout
    header=html.H4("Minimal measurement dash app",
                   style={'textAlign': 'center'})
    boton=dbc.Button("Start",
                    color="primary",
                    outline=True,
                    id="start-but",
                    className="m-3")
    inputs=dbc.Row([
            dbc.Label(
                "Sampling frecuency [Hz]", 
                width="auto",
                className="m-3"),
            dbc.Col(
                dbc.Input(
                    type="float",
                    placeholder="10000",
                    value=10000,
                    id="frec-in"),
                    className="me-3"),
            dbc.Label(
                "Buffer samples [#samples]", 
                width="auto",
                className="m-3"),
            dbc.Col(
                dbc.Input(
                    type="int",
                    placeholder="10000",
                    value=10000,
                    id="samps-in"),
                    className="me-3"),
            dbc.Label(
                "Range [V]", 
                width="auto",
                className="m-3"),
            dbc.Col(
                dbc.Input(
                    type="int",
                    placeholder="10",
                    value=10,
                    id="range-in"),
                    className="me-3"),
            dbc.Label(
                "Device", 
                width="auto",
                className="m-3"),
            dbc.Col(
                dbc.Input(
                    type="str",
                    placeholder="Dev1/",
                    value="Dev1/",
                    id="dev-in"),
                    className="me-3"),
        ],align='center',justify="evenly",className="g-2")
    grafico=dcc.Graph(id="plot")
    interval = dcc.Interval(id="interval", interval=1000, n_intervals=0, disabled=True)

    app.layout = dbc.Container([
        dbc.Card([header,inputs,boton]),
        dbc.Card([grafico,interval])
        ])
    
    #Callbacks
    @app.callback(
        Output("interval","disabled"),
        Input("start-but", "n_clicks"),
        [State("frec-in","value"),
         State("samps-in","value"),
         State("range-in","value"),
         State("dev-in","value")]
    )
    def start_measure(n_clicks,frec,samps,range,dev):
        """
        Start or stop the data generation
        """
        print("start_measure callback")
        config_d={
                "samps":int(samps),
                "frec":float(frec),
                "range":float(range),
                "dev":dev
        }
        if n_clicks == None:
            return True
        if int(n_clicks)%2 == 0:
            #stop the interval
            print("Dash-stop-F")
            start_smp.acquire(block=False)
            result=True
        else:
            #Start the interval
            print("Dash-start-T")
            config_q.put(config_d)
            start_smp.release()
            result=False
        return result
 
    @app.callback(
        Output("plot","figure"),
        Input("interval", "n_intervals"),
        Input("start-but", "n_clicks"),
        State("frec-in","value"),
        prevent_initial_call=True,
    )
    def plot_data(n_intervals,n_click,frec_in):
        """
        Read data and generate the graph 
        """
        if n_intervals >=0:
            data=dataout_q.get()
            t=np.arange(len(data[0]))/float(frec_in)
            n=float(n_intervals)
            fig = px.scatter(x=t,y=data[0]+n)
        if n_click == None:
            fig = px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16])
            return fig
        if int(n_click)%2 == 0:
            fig = px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16])
        fig.update_traces(marker_color='#2b8cbe')
        fig.update_layout(
        plot_bgcolor='white',
        xaxis_title="Time [s]",
        yaxis_title="Voltage CH0 [V]",
        )
        fig.update_xaxes(
            mirror=True,
            ticks='outside',
            showline=True,
            linecolor='#2d3323',
            linewidth=0.3,
            gridcolor='lightgrey',
            zerolinecolor="black",
            zerolinewidth=0.3
        )
        fig.update_yaxes(
            mirror=True,
            ticks='outside',
            showline=True,
            linecolor='#2d3323',
            linewidth=0.3,
            gridcolor='lightgrey',
            zerolinecolor="black",
            zerolinewidth=0.3
        )
        #fig.write_html("file.html")
        print("Dash-plot_data send plot")
        return fig       
    
    print("start the app")
    #To avoid dash server output in terminal
    log = logging.getLogger('werkzeug')
    log.setLevel(logging.ERROR)
    app.run_server()

Versions:

  • w10
  • python 3.7.9 32 bits
  • dash 2.15.0
  • dash-bootstrap-components 1.5.0
  • plotly 5.18.0

Expected answer

A modification of my code to solve my 2 questions or any help.

Thanks for your time.