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:

  • Python
  • Dash
  • HTML

Basic Dash Callbacks

See part 2 of the tutorial at https://dash.plotly.com/basic-callbacks

Inputs and Outputs

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.

Component Id and Property

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')
)

Text box

A simple text box where you just change the value of the text box and the output changes as a result

  • the Layout
# here the input is the value property of the component with id "my-input"
dcc.Input(id='my-input', value='initial value', type='text')
  • The Callback function

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)

Slider and Graph

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
In [5]:
# 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)

Loading the pandas DataFrame

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.transition

  • layout.transistion can be used to show how a dataset evolves over time.
  • allows a chart to update from one state to the next smoothly.

Multiple Inputs and Outputs

  • A dash app can have multiple inputs.
  • Any "Output" can have multiple "Input" components.

  • More than one "Output" components can be updated at once.

  • Avoid combining outputs:
    • if the Outputs depend on some but not all of the same Inputs to avoid unnecessary updates
    • if independent computations are performed on these inputs - keep them separate allows them to be run in parallel.

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.

In [8]:
#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)

Chaining Callbacks

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.

In [18]:
# -*- 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)

Dash app with State

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.

In [42]:
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.