How to return several values inside a for loop to update several traces with extendData?

For my dash app, in order to update some graphs dynamically, I have to use a function that I named update_graphs inside a for loop. Some of the graphs contain several traces while some others only have one. The update_graphs function is called inside a callback and returns a dict and an int to update the extendData property of the graph object. However, since I am using a return statement inside a for loop, I only get the first trace.

I am not familiar with the generators and the yield keyword, maybe this is an option. But I haven’t been able to make it work.

I have also tried to store the results of the update_graphs inside a list but it is not working.

Any help is appreciated!

Here is the code for the app:

import dash
from dash.dependencies import Output, Input, State, MATCH, ALL
from dash import dcc, html, ctx
import plotly
import plotly.express as px
import random
import plotly.graph_objs as go
import pandas as pd
  
# Initializing the data with the correct format
init_store = {}
n=3

init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int'), 'c':pd.Series(dtype='int'), 'd':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store['0'] = init_df

for i in range(n):
    init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int')}, index=range(50))
    init_df['a'] = init_df.index
    init_store[f'{i+1}'] = init_df

# Function to update the dataframes with the new observations
def get_data(json_data):
    df = pd.read_json(json_data)
    compteur = df['a'][len(df['a'])-1]
    if len(df.columns) > 2:
        new_row = {'a':compteur + 1, 'b':random.randint(13,26), 'c':random.randint(13,26), 'd':random.randint(13,26)}
    else:
        new_row = {'a':compteur + 1, 'b':random.randint(13,26)}
    df = df.shift(periods=-1)
    df.iloc[len(df)-1] = new_row

    return(df.to_json())

# Function to update the graphs based on the dataframes
def update_graphs(json_data, column, index=0):
    
    df = pd.read_json(json_data)
    nb_obs = df.shape[0]
    x_new = df['a'][len(df)-1]        
    y_new = df[column][nb_obs-1]

    return dict(x=[[x_new]], y=[[y_new]]), index

colors = px.colors.qualitative.G10

def generate_graph_containers(index, json_data):

    dataframe = pd.read_json(json_data)
    X = dataframe['a']
    Y = dataframe.loc[:, dataframe.columns != 'a']
    graph_id = {'type': 'graph-', 'index': index}

    
    return( 
        html.Div(
            html.Div(
            dcc.Graph(
                id=graph_id,
                style={"height": "8rem"},
                config={
                    "staticPlot": False,
                    "editable": False,
                    "displayModeBar": False,
                },
                figure=go.Figure(
                            {
                                "data": [
                                    {
                                        "x": list(X),
                                        "y": list(Y[Y.columns[i]]),
                                        "mode": "lines",
                                        "name": Y.columns[i],
                                        "line": {"color": colors[i+2]},
                                    }
                                    for i in range(len(Y.columns))
                                ],
                                "layout": {
                                    "uirevision": True,
                                    "margin": dict(l=0, r=0, t=4, b=4, pad=0),
                                    "xaxis": dict(
                                        showline=False,
                                        showgrid=False,
                                        zeroline=False,
                                        showticklabels=False,
                                    ),
                                    "yaxis": dict(
                                        showline=False,
                                        showgrid=False,
                                        zeroline=False,
                                        showticklabels=False,
                                    ),
                                    "paper_bgcolor": "rgba(0,0,0,0)",
                                    "plot_bgcolor": "rgba(0,0,0,0)",
                                }
                            }
                        )
            )
        )
        )
    )

app = dash.Dash(__name__)

store = [dcc.Store(id={'type':'store-', 'index':i}, data=init_store[str(i)].to_json()) for i in range(n)]

def make_layout(): 
    return(
            html.Div(
        [   
            html.Div(
                store
            ),

            dcc.Interval(
                id = 'interval',
                interval = 1000,
                n_intervals = 0
            ),

            html.Div(
                [
                    generate_graph_containers(str(i), store[i].data) for i in range(n)
                ] 
            )
            
        ]
    )
)

app.layout = make_layout

@app.callback(
    Output(component_id={'type':'store-', 'index':MATCH}, component_property='data'),
    [ 
        Input('interval', 'n_intervals'),
        State(component_id={'type':'store-', 'index':MATCH}, component_property='data') 
    ]
)
def update_data(time, data):
    return(get_data(data))


@app.callback(
    Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
    Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
    triggered_id = ctx.triggered_id
    print(triggered_id['index'])
    columns = ['b', 'c', 'd']

    if triggered_id['index'] == 0:
        for i in range(len(columns)):
            return(update_graphs(data, columns[i], i))
    else:
        return(update_graphs(data, 'b'))
  
if __name__ == '__main__':
    app.run_server(debug=True)

Hello @TheGovernor,

I’m a little confused as to why you are using match and then trying to update all of the graphs at the same time.

Since you are updating your different data stores, you should only need to worry about updating the matching graph.

But… it you want to update all at the same time, I’d recommend just using a single store, since the information looks the same, then update the graphs via an ALL also, building a list based upon the loop you have here.

Hello @jinnyzor

I have different data sources. Each data source is independant and therefore I would like to have one store and one graph for each. Most of the data sources give one value (time series) at time t. However, one of the datasources gives 3 values at time t. For this given data source, I would like to plot, on a single graph, one trace for each of the value.

Hope it makes more sense