Update! The limitations discussed in this topic are no longer, please see the new pattern-matching callbacks feature released in Dash 1.11.0: š£ Dash v1.11.0 Release - Introducing Pattern-Matching Callbacks - #3
Iām creating this thread to discuss how dynamic UIs work in Dash. That is, how to generate dynamic input components that update dynamic output components. For example, selecting an item in a dropdown might generate 2 or 3 other dropdowns or sliders and the combination of those controls might update a graph.
In Dash, all of the callback functions and decorators need to be defined up-front (before the app starts). This means that you must generate callbacks for every unique set of input components that could be present on the page.
In Dash, the callback functionās decoratorās arenāt dynamic. Each set of input components can only update a single output component. So, for each unique set of input components that are generated, you must generate a unique output component as well.
I recommend creating 2 containers: one to hold the dynamic controls and the other to generate the dynamic output container.
Hereās a simple example. In this example:
- 2 static dropdowns generate unique input components with unique IDs in the
control-container
Div - These dropdowns also create a unique output component with a unique ID thatās based off of their values.
- Callbacks are generated for every unique input set and are bound to the corresponding dynamic output component.
import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html
import itertools
app = dash.Dash()
app.layout = html.Div([
dcc.Dropdown(
id='datasource-1',
options=[
{'label': i, 'value': i} for i in ['A', 'B', 'C']
],
),
dcc.Dropdown(
id='datasource-2',
options=[
{'label': i, 'value': i} for i in ['X', 'Y', 'Z']
]
),
html.Hr(),
html.Div('Dynamic Controls'),
html.Div(
id='controls-container'
),
html.Hr(),
html.Div('Output'),
html.Div(
id='output-container'
)
])
def generate_control_id(value):
return 'Control {}'.format(value)
DYNAMIC_CONTROLS = {
'A': dcc.Dropdown(
id=generate_control_id('A'),
options=[{'label': 'Dropdown A: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
),
'B': dcc.Dropdown(
id=generate_control_id('B'),
options=[{'label': 'Dropdown B: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
),
'C': dcc.Dropdown(
id=generate_control_id('C'),
options=[{'label': 'Dropdown C: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
),
'X': dcc.Dropdown(
id=generate_control_id('X'),
options=[{'label': 'Dropdown X: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
),
'Y': dcc.Dropdown(
id=generate_control_id('Y'),
options=[{'label': 'Dropdown Y: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
),
'Z': dcc.Dropdown(
id=generate_control_id('Z'),
options=[{'label': 'Dropdown Z: {}'.format(i), 'value': i} for i in ['A', 'B', 'C']]
)
}
@app.callback(
Output('controls-container', 'children'),
[Input('datasource-1', 'value'),
Input('datasource-2', 'value')])
def display_controls(datasource_1_value, datasource_2_value):
# generate 2 dynamic controls based off of the datasource selections
return html.Div([
DYNAMIC_CONTROLS[datasource_1_value],
DYNAMIC_CONTROLS[datasource_2_value],
])
def generate_output_id(value1, value2):
return '{} {} container'.format(value1, value2)
@app.callback(
Output('output-container', 'children'),
[Input('datasource-1', 'value'),
Input('datasource-2', 'value')])
def display_controls(datasource_1_value, datasource_2_value):
# create a unique output container for each pair of dyanmic controls
return html.Div(id=generate_output_id(
datasource_1_value,
datasource_2_value
))
def generate_output_callback(datasource_1_value, datasource_2_value):
def output_callback(control_1_value, control_2_value):
# This function can display different outputs depending on
# the values of the dynamic controls
return '''
You have selected {} and {} which were
generated from {} (datasource 1) and and {} (datasource 2)
'''.format(
control_1_value,
control_2_value,
datasource_1_value,
datasource_2_value
)
return output_callback
app.config.supress_callback_exceptions = True
# create a callback for all possible combinations of dynamic controls
# each unique dynamic control pairing is linked to a dynamic output component
for value1, value2 in itertools.product(
[o['value'] for o in app.layout['datasource-1'].options],
[o['value'] for o in app.layout['datasource-2'].options]):
app.callback(
Output(generate_output_id(value1, value2), 'children'),
[Input(generate_control_id(value1), 'value'),
Input(generate_control_id(value2), 'value')])(
generate_output_callback(value1, value2)
)
if __name__ == '__main__':
app.run_server(debug=True)