Dynamically bind callbacks to Graphs(dynamically generated)

Good day all.

I would like to bind the callback function dynamically to dynamically generated graphs(from a python Dictionary(from JSON from MQTT))

I managed to generate the UI(Visuals) but was unable to bind it with the right callbacks. UI generation(a MWE):

def generate_sensing_ui(sensing_json):
        
    graphing_list = []
    
    for thingName in list(sensing_json.keys()):
        graphID = "graph-"+str(thingName)
        graphing_list.append(html.Div(
            children=[
            dcc.Graph(id=graphID),
         ]))
        
    return graphing_list # returns to a html.Div

All good at this point. Follow is the codeblock for Callbacks Binding (which will be evoked at start of script):

def function_123(sensing_dict):
    for sensor in list(sensing_dict.keys()):
        
        graphID = "graph-"+str(sensor)
        @app.callback(
            Output(graphID, 'figure'),
            Input('update', 'n_intervals')
        )
        def sensing_ui_callbacks_attaching(n_intervals):
            thingName = graphID[6:]
            if dictionary_with_sensor:
                graphDatastream = dictionary_with_sensor[thingName]["Datastream"]
                x_values = []
                y_values = []
        
                for j in graphDatastream:
                    x_values.append(list(j.keys())[0])
                    y_values.append(list(j.values())[0])
      
                figure = go.Figure(
                    data=[go.Scatter(x=x_values, y=y_values)],
                    
                    )
                figure.update_layout(
                    title=thingName,
                    xaxis_title= list((dictionary_with_sensor[thingName]["Format"]).keys())[0],
                    yaxis_title= list((dictionary_with_sensor[thingName]["Format"]).values())[0],
                    template="plotly_dark",
                )
                return figure                          
            else:
                pass

Problem: It will generate x number of graphs but all have the same content, which is the last entry of the Dictionary. The for loop works(tested with printing inside the block.

I think I pinpointed the problem. Potential cause: This function binds the UI and the callback but it does not “tell” or “set” the current variable into the respective functions. Hence the variable, “graphID” will take the last value of the list and generate the graph with the last value of the list as the key(in the dictionary).

So I am thinking of the following potential solution:

  1. Set/Pass in a variable into the “function_123” during the for loop and make sure it doesn’t get updated.
  2. Takes in 2 inputs, namely “n_intervals” and the id of the graph(in HTML), then processes the ID into a dictionary key and plots the graphs. As every n_intervals it will fire the function, so having the ID as a string along with the n_intervals, might work.

However, I tried(I really did), not sure how to get around this.
Well, thanks for your time reading up to this point, sorry for my bad command of language. Let me know if word it wrongly or so.

Do share if you have any ideas. Much appreciated.

Cheers,
Mocha.

P.S. I use the following as reference/inspiration: Dynamically declaration of dash callback

Hi,

Welcome to the community!

I have a few suggestions regarding your app and the callbacks:

  1. I would discourage you to define callbacks in a loop like this. You can instead use a Pattern-Matching Callback with the wildcard ALL and update all figures at once.

  2. If all you want in the callback is to update the points in dcc.Graph, then I would recommenf you to take a look on extendData. It allows you to just “append” points to the trace without rewriting the entire figure. There are some threads around discussing how to use it (like this one) and it is not trivial in your case where multiple graphs are updated, but this is the fastest way to accomplish what you want.

I can find you an example of what I just mentioned if you need (I am not on my laptop right now unfortunately).

Hope this helps!