Callback with variable number of Inputs

Hi everyone,

I am working on a callback using a variable number of inputs. I tried this, but it doesn’t work :

@app.callback(
    Output('datatable-s', 'data'),
    [
        [Input(id_component, 'value') for id_component  in list_id_components]
        + [Input('datatable-r', 'selected_rows')]
    ],
)
def disclose_selected_r(*args):
    name = tab.iloc[args[-1]]['Name']
    tab_columns=pd.DataFrame(tab.columns, index=tab_reactors.columns)
    tab_int = tab.copy()
    tab_int.loc[tab_int['Name'] == name,:]= [values for values in args[0:len(list_id_components)]]
    tab_r = pd.concat([tab_columns,tab_int.loc[tab_int['Name'] == name,:].T], axis=1)
    tab_r = tab_r.to_dict('records')
    return (tab_r)

Can anyone help me to manage the variable number of inputs ?
Thank you very much !!

Hi!

From what I see on you question I understand that what you are trying to do is show the data of a specific person (input id 'datatable-r') regarding one or more components (thus the variable number op inputs). If that is the case, I think the best solution would be to use pattern-matching callbacks. Here are two links that may be helpful in learning how to use them:
https://dash.plotly.com/pattern-matching-callbacks
https://community.plotly.com/t/how-to-elegantly-handle-a-very-large-number-of-input-state-in-callbacks/19228/3

I think the callback code would end up being something similar to this:

@app.callback(
Output(‘datatable-s’, ‘data’),
Input(‘datatable-r’, ‘selected_rows’),
Input({‘type’: ‘id_component’, ‘index’: ALL}, ‘value’),
)
def disclose_selected_r(selected_rows, *args):
name = tab.iloc[selected_rows][‘Name’]

tab_columns=pd.DataFrame(tab.columns, index=tab_reactors.columns)
tab_int = tab.copy()
tab_int.query('Name' == name) = [values for values in [args]]

tab_r = pd.concat([tab_columns,tab_int.query('Name' == name).T], axis=1)
tab_r = tab_r.to_dict('records')
return tab_r

However, if I had more information about the app.layout and the characteristics of the inputs I think I could help you better to reach a solution!

(btw, I changed the .loc pieces of code for .query since I find that function to be more readable/cleaner. You can find more information about query here. But that’s just my personal opinion and it should not affect the output of the code, so feel free to keep using .loc for this case)

I hope this helped you! :slight_smile:

2 Likes

Hello Celia,

Thank you very much for your answer, it was very insightful !
I think Pattern-matching callbacks could be a good solution for my problem !

However, it still doesn’t work, to give you more context, these are my components :

def generate_components(tab, col_name):
    output_title = html.H3(id="change_{}".format(col_name),children = "Change {} :".format(col_name))
    if tab[col_name].dtypes== object:
        list_options=[]
        for k in tab[col_name].unique():
            list_options.append({'label':k,'value':k})
        output_component = html.Div([dcc.Dropdown(options = list_options,
                                      id='dropdown_{}'.format(col_name),
                                      value= tab.at[0,col_name]
                                     )])
    else :
        output_component = html.Div([dcc.Input(
            id="input_{}".format(col_name), 
            type="number", 
            value= tab.at[0,col_name])
                           ])
    return (dbc.Row([
                dbc.Col(children=output_title, width={'size': 4, 'offset': 1}),
                dbc.Col(children=output_component, width={'size': 4, 'offset': 1})
        ])
    )```

#Creation list id components
list_id_components=[]
for col_name in tab.columns:
    if tab[col_name].dtypes== object:
        list_id_components.append('dropdown_{}'.format(col_name))
    else :
        list_id_components.append('input_{}'.format(col_name))
list_element=['value']*len(tab.columns)


And I also use that callback on them : 
# CALLBACK 1
```@app.callback(
    [Output(id_component, 'value') for id_component  in list_id_components],
    Input('datatable-r', 'selected_rows'),
)
def maj_values_dropdowns(selected_rows):
    if len(selected_rows) !=0:
        name = tab.iloc[selected_rows[0]]['Name']
        tab_values = tab.loc[tab['Name'] == name,:].iloc[0,:]
    return ([value for value in tab_values])

I want these components then to be the inputs of the next callback (which I sent to you).

With the pattern-matching callbacks approach, I have to change the components’ id into a dict which block callback 1 and therefore callback 2.
Is there a way to keep the id = ‘dropdown_{}’.format(col_name), but adding a type so that I can use the pattern-matching callback method as well on my callback 2?

Thank you very much for your help,

Have a nice day :slight_smile:

Changing the ids to dicts shouldn’t be a problem. You could do it like this:

# Instead of this
id='dropdown_{}'.format(col_name)
 
# Write this
id = {'type':'component', 'index': "dropdown_{}".format(col_name)}

I have specified ‘type’:‘component’ instead of ‘type’:‘dropdown’ because as I see in your code, you do the same things in the callbacks for both dropdowns and inputs so they can be treated as the same to keep it simple. If you wanted to do different thinks it would be more appropriate to specify ‘type’:‘dropdown’ and ‘type’:‘input’. The names don’t really matter as long as they match through the code.

Anyway, the resulting (full) code would look something like this:

def generate_components(tab, col_name):
    output_title = html.H3(id="change_{}".format(col_name),children = "Change {} :".format(col_name))
    if tab[col_name].dtypes== object:
        list_options=[]
        for k in tab[col_name].unique():
            list_options.append({'label':k,'value':k})
        output_component = html.Div([dcc.Dropdown(options = list_options,
                                      #id='dropdown_{}'.format(col_name),
                                      id = {'type':'component', 'index': "dropdown_{}".format(col_name)},            
                                      value= tab.at[0,col_name]
                                     )])
    else :
        output_component = html.Div([dcc.Input(
            #id="input_{}".format(col_name),
            id = {'type':'component', 'index': "input_{}".format(col_name)},
            type="number", 
            value= tab.at[0,col_name])
                           ])
    return (dbc.Row([
                dbc.Col(children=output_title, width={'size': 4, 'offset': 1}),
                dbc.Col(children=output_component, width={'size': 4, 'offset': 1})
        ])
    )


#list_id_components=[]
#for col_name in tab.columns:
#    if tab[col_name].dtypes== object:
#        list_id_components.append('dropdown_{}'.format(col_name))
#    else :
#        list_id_components.append('input_{}'.format(col_name))

list_element=['value']*len(tab.columns)

@app.callback(
    Output({'type':'component', 'index':ALL}, 'value'),
    Input('datatable-r', 'selected_rows')
)
def maj_values_dropdowns(selected_rows):
    if len(selected_rows) !=0:
        name = tab.iloc[selected_rows[0]]['Name']
        tab_values = tab.loc[tab['Name'] == name,:].iloc[0,:]
    return ([value for value in tab_values])


@app.callback(
    Output('datatable-s', 'data'),
    Input('datatable-r', 'selected_rows'),
    Input({'type': 'component', 'index': ALL}, 'value')
)
def disclose_selected_r(selected_rows, *args):
    name = tab.iloc[selected_rows]['Name']

    tab_columns=pd.DataFrame(tab.columns, index=tab_reactors.columns)
    tab_int = tab.copy()
    tab_int.query('Name' == name) = [values for values in [args]]

    tab_r = pd.concat([tab_columns,tab_int.query('Name' == name).T], axis=1)
    tab_r = tab_r.to_dict('records')
    return (tab_r)

I have commented the list_id_components loop because with the pattern-matching is not necessary anymore.

1 Like

It works ! Thank you very much Celia !!! :smiley:

2 Likes