title: "Basic Dash Callbacks" linktitle: "Notebook following Part 3 Basic Callbacks of the Dash tutorial " subtitle: "Making the app interactive" draft: false date: 2021-08-161T16:22:54+01:00
image: ../../dash_plotly/images/dash_callbacks.png alt_text: "Dash Callbacks screenshot" summary: "Summary of the Dash Callbacks project"
tech_used:
See part 2 of the tutorial at https://dash.plotly.com/basic-callbacks
from dash.dependencies import Input, Output
The "inputs" and "outputs" of the application's interface are described declaratively as the arguments of the @app.callback
decorator.
The "inputs" and "outputs" are properties of a particular component.
For example the input is the "value" property of a component with a specified id while the "output" is the "children" property of a component with a specified id.
Reactive programming: whenever an input changes, everything that depends on that input gets automatically updated.
Whenever an input property changes, the function that the callback decorator wraps is automatically called. The new value of the input property is provided as an input argument to the function. Dash updates the property of the output component with whatever was returned by the function.
component_id
and component_property
are optional keywords
There is no need to specify a value for the children
property of the output (html.Div(id='my-output')
in the app below) component in the layout
because when the app starts up, all of the callbacks are automatically called with the initial values of the input components to populate the initial state of the output components. Any value specified would be overwritten when the app starts.
The component_id
and component_property
are optional and are usually excluded.
@app.callback(
Output(component_id='my-output', component_property='children'),
Input(component_id='my-input', component_property='value')
)
A simple text box where you just change the value of the text box and the output changes as a result
# here the input is the value property of the component with id "my-input"
dcc.Input(id='my-input', value='initial value', type='text')
component_id
, component_property
# callback for the simple text box
@app.callback(
# the output is the "children" property of the component with id "my-output"
Output(component_id='my-output', component_property='children'),
# here the input is the value property of the component with id "my-input"
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
return 'Output: {}'.format(input_value)
The dcc.slider
here updates the dcc.Graph
.
the layout
html.Br(),
html.Div([
dcc.Graph(id='graph-with-slider'),
dcc.Slider(
id='year-slider',
min=df['year'].min(),
max=df['year'].max(),
value=df['year'].min(),
marks={str(year): str(year) for year in df['year'].unique()},
step=None
)
]),
The Callback function
The value property from the slider component (id 'year-slider') is the input of the app and used to update the output of the app - the 'figure' property of the graph component (with id 'graph-with-slider').
Whenever the value of the slider changes, the update_figure
callback function is called with this new value. The function filters the dataframe with the new value and creates a figure object which is returned to the app.
# callback for the graph with slider
@app.callback(
# `component_id`, `component_property`
Output('graph-with-slider', 'figure'),
Input('year-slider', 'value'))
def update_figure(selected_year):
filtered_df = df[df.year == selected_year]
fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
size="pop", color="continent", hover_name="country",
log_x=True, size_max=55)
fig.update_layout(transition_duration=500)
return fig
# First two app in dash plotly - basic callbacks tutorial - combined into one.
#import dash
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
import pandas as pd
# df is in the global state of the app so it can be called from within the callback function
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.H6("Change the value in the text box to see callbacks in action!"),
html.Div([
"Input: ",
# here the input is the value property of the component with id "my-input"
dcc.Input(id='my-input', value='initial value', type='text')
]),
html.Br(),
# the output is the "children" property of the component with id "my-output"
html.Div(id='my-output'),
# no children value specified here as it will be overwritten at app start up
html.Br(),
html.Div([
dcc.Graph(id='graph-with-slider'),
dcc.Slider(
id='year-slider',
min=df['year'].min(),
max=df['year'].max(),
value=df['year'].min(),
marks={str(year): str(year) for year in df['year'].unique()},
step=None
)
]),
])
#The "inputs" and "outputs" are described declaratively
# as the arguments of the @app.callback decorator
# callback for the simple text box
@app.callback(
# the output is the "children" property of the component with id "my-output"
Output(component_id='my-output', component_property='children'),
# here the input is the value property of the component with id "my-input"
Input(component_id='my-input', component_property='value')
)
def update_output_div(input_value):
return 'Output: {}'.format(input_value)
# callback for the graph with slider
@app.callback(
Output('graph-with-slider', 'figure'),
Input('year-slider', 'value'))
def update_figure(selected_year):
filtered_df = df[df.year == selected_year]
fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
size="pop", color="continent", hover_name="country",
log_x=True, size_max=55)
fig.update_layout(transition_duration=500)
return fig
if __name__ == '__main__':
app.run_server(debug=True, mode= 'inline', port = 8051)
The pandas DataFrame is loaded at the start of the app and is in the global state of the app, therefore it can be read inside the callback functions. The dataframe is only loaded at the start and therefore the data is already in memory when the user visits or interacts with the app. It is recommended to always download or query data (or any other expensive initialisation) in the global scope and not within the scope of the callback functions.
A copy of the dataframe is created by the callback function - the original data variable should never be mutated outside of it's scope. Otherwise one user's session might affect the next user's session.
layout.transistion
can be used to show how a dataset evolves over time.Any "Output" can have multiple "Input" components.
More than one "Output" components can be updated at once.
The app below from https://dash.plotly.com/basic-callbacks tutorial binds several inputs to a single Output component (the figure
property of the Graph
component.
The Input
's are the value
property of various dcc components (including dcc.Dropdown
, dcc.RadioItems
, dcc.Slider
etc).
@app.callback(
Output('indicator-graphic', 'figure'),
Input('xaxis-column', 'value'),
Input('yaxis-column', 'value'),
Input('xaxis-type', 'value'),
Input('yaxis-type', 'value'),
Input('year--slider', 'value'))
The callback here executes whenever the 'value' property of any of these components change. The input arguments of the callback are the new or current value of each of the Input properties in the order they are specified in the callback function. As you can only update one input in a particular moment, Dash will take the current state of all the specified Input properties and passes them into the function. This means if you just move the slider or select some other value from the dropdown or select a different radio item, the moment you change one value the output will be updated.
#import dash
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
from dash.dependencies import Input, Output
import pandas as pd
import pandas as pd
app = JupyterDash(__name__)
df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')
available_indicators = df['Indicator Name'].unique()
app.layout = html.Div([
html.Div([
html.Div([
dcc.Dropdown(
id='xaxis-column',
options=[{'label': i, 'value': i} for i in available_indicators],
value='Fertility rate, total (births per woman)'
),
dcc.RadioItems(
id='xaxis-type',
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '48%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
id='yaxis-column',
options=[{'label': i, 'value': i} for i in available_indicators],
value='Life expectancy at birth, total (years)'
),
dcc.RadioItems(
id='yaxis-type',
options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
value='Linear',
labelStyle={'display': 'inline-block'}
)
], style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
]),
dcc.Graph(id='indicator-graphic'),
dcc.Slider(
id='year--slider',
min=df['Year'].min(),
max=df['Year'].max(),
value=df['Year'].max(),
marks={str(year): str(year) for year in df['Year'].unique()},
step=None
)
])
@app.callback(
Output('indicator-graphic', 'figure'),
Input('xaxis-column', 'value'),
Input('yaxis-column', 'value'),
Input('xaxis-type', 'value'),
Input('yaxis-type', 'value'),
Input('year--slider', 'value'))
def update_graph(xaxis_column_name, yaxis_column_name,
xaxis_type, yaxis_type,
year_value):
dff = df[df['Year'] == year_value]
fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])
fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
fig.update_xaxes(title=xaxis_column_name,
type='linear' if xaxis_type == 'Linear' else 'log')
fig.update_yaxes(title=yaxis_column_name,
type='linear' if yaxis_type == 'Linear' else 'log')
return fig
if __name__ == '__main__':
app.run_server(debug=True, mode= 'inline', port = 8057)
Outputs and inputs can also be chained together with the output one callback function being the input of another callback function. This allows for dynamic user interfaces where one input component updates the available options of the next input component.
In the app below there are 3 callback functions. The first callback handles a user clicking on another country using a set of radio buttons. It then updates the available cities to choose from in the cities radio button. Dash waits for the value of the cities component to be updated before calling the final function to which outputs that the selected city is in the correct country. This prevents the callbacks from being updated with inconsistent state.
# -*- coding: utf-8 -*-
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
all_options = {
'America': ['New York City', 'San Francisco', 'Cincinnati'],
'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
dcc.RadioItems(
id='countries-radio',
options=[{'label': k, 'value': k} for k in all_options.keys()],
value='America'
),
html.Hr(),
dcc.RadioItems(id='cities-radio'),
html.Hr(),
html.Div(id='display-selected-values'),
])
# this first callback updates the available options in the 'cities-radio' RadioItems based on
# the input from the 'countries-radio' RadioItems.
@app.callback(
Output('cities-radio', 'options'),
Input('countries-radio', 'value'))
def set_cities_options(selected_country):
return [{'label': i, 'value': i} for i in all_options[selected_country]]
# this second callback sets an initial value when the 'options' property changes
# to the first value in the 'options' array
@app.callback(
Output('cities-radio', 'value'),
Input('cities-radio', 'options'))
def set_cities_value(available_options):
return available_options[0]['value']
# this next callback displays the selected 'value' of each component
# if the value of the countries radio component changes, dash waits until the value of the
# cities component is updated before calling the function.
@app.callback(
Output('display-selected-values', 'children'),
Input('countries-radio', 'value'),
Input('cities-radio', 'value'))
def set_display_children(selected_country, selected_city):
return u'{} is a city in {}'.format(
selected_city, selected_country,
)
if __name__ == '__main__':
app.run_server(debug=True, mode = "inline", port =8090)
Sometimes if you have something like a form in the app, you will only want the value of the input component to only be read when the user has finished entering all the information in the form.
The first callback below is fired whenever any of the attributes described by the Input
changes.
The second callback illustrates using State
which allows you to pass along extra values without firing the callbacks.
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.H3("Illustrating without using State"),
html.P("Here the callback function is fired whenever any of the attributes described by the Input change"),
dcc.Input(id="input-1", type="text", value="Montréal"),
dcc.Input(id="input-2", type="text", value="Canada"),
html.Div(id="number-output"),
html.Br(),
html.Div([
html.H3("Here State allows you to pass along extra values without firing the callbacks."),
html.P("Here is same as above but the `dcc.Input` is 'State' and a button is 'Input'"),
html.P("Changing text in the dcc.Input won't fire the callback but clicking the button will."),
html.P("The current value of the dcc.Input are passed into the callback but don't trigger the callback function itself."),
html.P("The callback is triggered here by listening to the `n_clicks` property of the button component."),
dcc.Input(id='input-1-state', type='text', value='Montréal'),
dcc.Input(id='input-2-state', type='text', value='Canada'),
html.Button(id='submit-button-state', n_clicks=0, children='Submit'),
html.Div(id='output-state'),
])
])
# here the callback is fired whenever any of the Input attributes change
@app.callback(
Output("number-output", "children"),
Input("input-1", "value"),
Input("input-2", "value"),
)
def update_output(input1, input2):
return u'Input 1 is "{}" and Input 2 is "{}"'.format(input1, input2)
# Here state is used. The`dcc.Input`s are State and a button is used as Input
@app.callback(
Output('output-state', 'children'),
Input('submit-button-state', 'n_clicks'),
State('input-1-state', 'value'),
State('input-2-state', 'value'))
def update_output(n_clicks, input1, input2):
return u'''
The Button has been pressed {} times,
Input 1 is "{}",
and Input 2 is "{}"
'''.format(n_clicks, input1, input2)
if __name__ == '__main__':
app.run_server(debug=True, mode="inline", port = 8091)
The callback is triggered in the above example by listening to the n_clicks
property of the button component.
n_clicks
is a property that get incremented everytime the component is clicked on and is available in every component in the dash_html_components
library.Declarative UI's are customisable through reactive and functional Python callbacks.
Every element attribute of the declarative components can be updated through a callback. A subset of the attributes such as the value
properties of a dropdown component can be edited by the users in the user interface.