We are very excited about Pattern-Matching callbacks! This is one of the biggest features & deepest overhauls to Dash’s core architecture that we’ve made since we originally released the framework in 2017.
In August 2017, one month after we released Dash, we opened up a forum topic about one of the framework’s core limitations: Dynamic Controls and Dynamic Output Components. From that thread, we said:
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.
This was one of the most viewed & linked-to topics in the forum… a roadblock that many of us ran into when creating more complex Dash applications
The new Pattern-Matching Callbacks feature removes this limitation. It allows you to create callbacks that match a group (or, a “pattern”) of component IDs, rather than just a single callback ID.
The documentation goes into much more detail, but this is just a quick example to give you an idea of what’s possible in less than 100 lines of code:
import dash
from dash.dependencies import Input, Output, State, ALL, MATCH
import dash_html_components as html
import dash_core_components as dcc
import plotly.express as px
df = px.data.gapminder()
app = dash.Dash(__name__)
app.layout = html.Div([
html.Div(children=[
dcc.Dropdown(
options=[{
'label': i,
'value': i
} for i in df.country.unique()],
value='Canada',
id='country',
style={'display': 'inline-block', 'width': 200}
),
html.Button(
'Add Chart', id='add-chart', n_clicks=0,
style={'display': 'inline-block'}
),
]),
html.Div(id='container', children=[])
])
def create_figure(column_x, column_y, country):
chart_type = px.line if column_x == 'year' else px.scatter
return chart_type(
df.query("country == '{}'".format(country)),
x=column_x,
y=column_y,
)\
.update_layout(
title='{} {} vs {}'.format(country, column_x, column_y),
margin_l=10, margin_r=0, margin_b=30)\
.update_xaxes(title_text='').update_yaxes(title_text='')
@app.callback(
Output('container', 'children'),
[Input('add-chart', 'n_clicks')],
[State('container', 'children'),
State('country', 'value')])
def display_dropdowns(n_clicks, children, country):
default_column_x = 'year'
default_column_y = 'gdpPercap'
new_element = html.Div(
style={'width': '23%', 'display': 'inline-block', 'outline': 'thin lightgrey solid', 'padding': 10},
children=[
dcc.Graph(
id={
'type': 'dynamic-output',
'index': n_clicks
},
style={'height': 300},
figure=create_figure(default_column_x, default_column_y, country)
),
dcc.Dropdown(
id={
'type': 'dynamic-dropdown-x',
'index': n_clicks
},
options=[{'label': i, 'value': i} for i in df.columns],
value=default_column_x
),
dcc.Dropdown(
id={
'type': 'dynamic-dropdown-y',
'index': n_clicks
},
options=[{'label': i, 'value': i} for i in df.columns],
value=default_column_y
),
]
)
children.append(new_element)
return children
@app.callback(
Output({'type': 'dynamic-output', 'index': MATCH}, 'figure'),
[Input({'type': 'dynamic-dropdown-x', 'index': MATCH}, 'value'),
Input({'type': 'dynamic-dropdown-y', 'index': MATCH}, 'value'),
Input('country', 'value')],
)
def display_output(column_x, column_y, country):
return create_figure(column_x, column_y, country)
if __name__ == '__main__':
app.run_server(debug=True)
We were able to do all of this without changing the stateless architecture that makes Dash so special.Multiple people can view this app at the same time and have independent sessions. Scaling this app to 10s, 100s, or thousands of viewers is as simple as adding more workers or nodes.
We’ll be talking a lot more about this feature in the coming weeks, so stay tuned for more announcements. In the meantime, try it out, let us know how it goes, and show us what you’ve made!