Basic Dash Callbacks
This is the 2nd chapter of the Dash Fundamentals. The previous chapter covered the Dash app
layout
and the next chapter covers interactive graphing. Just getting started? Make sure to install the necessary dependencies.
In the previous chapter we learned that app.layout
describes what the app looks like and is a hierarchical tree of components.
The DashHtmlComponents
library provides classes for all of the HTML tags, and the keyword arguments describe the HTML attributes like style
, className
, and id
.
The DashCoreComponents
library generates higher-level components like controls and graphs.
This chapter describes how to make your Dash apps using callback functions: functions that are automatically called by Dash whenever an input component's property changes, in order to update some property in another component (the output).
For optimum user-interaction and chart loading performance, production Dash apps should consider the Job Queue, HPC, Datashader, and horizontal scaling capabilities of Dash Enterprise.
Let's get started with a simple example of an interactive Dash app.
Simple Interactive Dash App
If you're using Dash Enterprise's Data Science Workspaces, copy & paste the below code into your Workspace (see video).
using Dash
app = dash()
app.layout = html_div() do
html_h6("Change the value in the text box to see callbacks in action!"),
html_div(
children = [
"Input: ",
dcc_input(id = "my-input", value = "initial value", type = "text")
],
),
html_br(),
html_div(id = "my-output")
end
callback!(app, Output("my-output", "children"), Input("my-input", "value")) do input_value
"Output: $(input_value)"
end
run_server(app, "0.0.0.0", debug=true)
Change the value in the text box to see callbacks in action!
Let's break down this example:
- The "inputs" and "outputs" of our application are described
as the arguments of the
callback!
function definition.
- In Dash, the inputs and outputs of our application are simply the
properties of a particular component. In this example,
our input is the "
value
" property of the component that has the ID "my-input
". Our output is the "children
" property of the component with the ID "my-output
". - Whenever an input property changes, the function that the callback decorator wraps will get called automatically. Dash provides this callback function with the new value of the input property as its argument, and Dash updates the property of the output component with whatever was returned by the function.
- The
component_id
andcomponent_property
keywords are optional (there are only two arguments for each of those objects). They are included in this example for clarity but will be omitted in the rest of the documentation for the sake of brevity and readability. - Don't confuse the
Input
object and thedcc_input
object. The former is just used in these callback definitions and the latter is an actual component. - Notice how we don't set a value for the
children
property of themy-output
component in thelayout
. When the Dash app starts, it automatically calls all of the callbacks with the initial values of the input components in order to populate the initial state of the output components. In this example, if you specified the div component ashtml_div(id='my-output', children='Hello world')
, it would get overwritten when the app starts.
It's sort of like programming with Microsoft Excel: whenever a cell changes (the input), all the cells that depend on that cell (the outputs) will get updated automatically. This is called "Reactive Programming" because the outputs react to changes in the inputs automatically.
Remember how every component is described entirely through its
set of keyword arguments? Those arguments that we set in
Julia become properties of the component,
and these properties are important now.
With Dash's interactivity, we can dynamically update any of those properties
using callbacks. Often we'll update the children
property of HTML
components to display new text (remember that children
is responsible for the contents of a component) or the figure
property of a dcc_graph
component to display new data. We could also update the style
of a
component or even the available options
of a dcc_dropdown
component!
Let's take a look at another example where a dcc_slider
updates
a dcc_graph
.
Dash App Layout With Figure and Slider
using Dash
using DataFrames, PlotlyJS, CSV
csv_data = download("https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv")
df = CSV.read(csv_data, DataFrame)
years = unique(df[!, :year])
app = dash()
app.layout = html_div() do
dcc_graph(id = "graph"),
dcc_slider(
id = "year-slider-1",
min = minimum(years),
max = maximum(years),
marks = Dict([Symbol(v) => Symbol(v) for v in years]),
value = minimum(years),
step = nothing,
)
end
callback!(
app,
Output("graph", "figure"),
Input("year-slider-1", "value"),
) do selected_year
return Plot(
df[df.year .== selected_year, :],
Layout(
xaxis_type = "log",
xaxis_title = "GDP Per Capita",
yaxis_title = "Life Expectancy",
legend_x = 0,
legend_y = 1,
hovermode = "closest",
transition_duration = 500
),
x = :gdpPercap,
y = :lifeExp,
text = :country,
group = :continent,
mode = "markers",
marker_size = 15,
marker_line_color = "white",
)
end
run_server(app, "0.0.0.0", debug = true)
Theming with Dash Enterprise Design Kit
Theming with Dash Enterprise Design Kit
Default Theme
Mars Theme
Neptune Theme
Miller Theme
Extrasolar Theme
Preset Themes
In this example, the "value"
property of the dcc_slider
is the
input of the app, and the output of the app is the "figure"
property of the
dcc_graph
.
Whenever the value
of the dcc_slider
changes, Dash calls the
callback function update_figure
with the new value. The function filters the
dataframe with this new value, constructs a figure
object,
and returns it to the Dash application.
There are a few nice patterns in this example:
- We
load our dataframe at the start of the app:
df = CSV.read(csv_data, DataFrame)
. This dataframedf
is in the global state of the app and can be read inside the callback functions. - Loading data into memory can be expensive. By loading querying data at
the start of the app instead of inside the callback functions, we ensure
that this operation is only done once -- when the app server starts. When a user
visits the app or interacts with the app, that data (
df
) is already in memory. If possible, expensive initialization (like downloading or querying data) should be done in the global scope of the app instead of within the callback functions. - The callback does not modify the original data, it only creates copies of the dataframe by filtering . This is important: your callbacks should never modify variables outside of their scope. If your callbacks modify global state, then one user's session might affect the next user's session and when the app is deployed on multiple processes or threads, those modifications will not be shared across sessions.
- We are turning on transitions with
layout.transition
to give an idea of how the dataset evolves with time: transitions allow the chart to update from one state to the next smoothly, as if it were animated.
Sign up for Dash Club → Two free cheat sheets plus updates from Chris Parmer and Adam Schroeder delivered to your inbox every two months. Includes tips and tricks, community apps, and deep dives into the Dash architecture. Join now.
Dash App With Multiple Inputs
In Dash, any "output" can have multiple "input" components.
Here's a simple example that binds five inputs
(the value
property of two dcc_dropdown
components,
two dcc_radioitems
components, and one dcc_slider
component)
to one output component (the figure
property of the dcc_graph
component).
Notice how callback!
lists all five Input
items after the Output
.
using Dash
using DataFrames, PlotlyJS, CSV
csv_data = download("https://raw.githubusercontent.com/plotly/datasets/master/country_indicators.csv")
df2 = CSV.read(csv_data, DataFrame)
dropmissing!(df2)
rename!(df2, Dict(:"Year" => "year"))
available_indicators = unique(df2[!, "Indicator Name"])
years = unique(df2[!, "year"])
app = dash()
app.layout = html_div() do
html_div(
children = [
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",
),
],
style = (width = "48%", display = "inline-block"),
),
html_div(
children = [
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",
),
],
style = (width = "48%", display = "inline-block", float = "right"),
),
dcc_graph(id = "indicator-graphic"),
dcc_slider(
id = "year-slider-2",
min = minimum(years),
max = maximum(years),
marks = Dict([Symbol(v) => Symbol(v) for v in years]),
value = minimum(years),
step = nothing,
)
end
callback!(
app,
Output("indicator-graphic", "figure"),
Input("xaxis-column", "value"),
Input("yaxis-column", "value"),
Input("xaxis-type", "value"),
Input("yaxis-type", "value"),
Input("year-slider-2", "value"),
) do xaxis_column_name, yaxis_column_name, xaxis_type, yaxis_type, year_value
df2f = df2[df2.year .== year_value, :]
return Plot(
df2f[df2f[!, Symbol("Indicator Name")] .== xaxis_column_name, :Value],
df2f[df2f[!, Symbol("Indicator Name")] .== yaxis_column_name, :Value],
Layout(
xaxis_type = xaxis_type == "Linear" ? "linear" : "log",
xaxis_title = xaxis_column_name,
yaxis_title = yaxis_column_name,
yaxis_type = yaxis_type == "Linear" ? "linear" : "log",
hovermode = "closest",
),
kind = "scatter",
text = df2f[
df2f[!, Symbol("Indicator Name")] .== yaxis_column_name,
Symbol("Country Name"),
],
mode = "markers",
marker_size = 15,
marker_opacity = 0.5,
marker_line_width = 0.5,
marker_line_color = "white"
)
end
run_server(app, "0.0.0.0", debug = true)
Theming with Dash Enterprise Design Kit
Theming with Dash Enterprise Design Kit
Default Theme
Mars Theme
Neptune Theme
Miller Theme
Extrasolar Theme
Design Kit Theme Editor
In this example, the callback executes whenever the value
property of any of the
dcc_dropdown
, dcc_slider
,
or dcc_radioitems
components change.
The input arguments of the callback are the current value of each of the "input" properties, in the order that they were specified.
Even though only a single Input
changes at a time (i.e. a user can only change
the value of a single Dropdown in a given moment), Dash collects the
current state of all the specified Input
properties and passes them
into the callback function. These callback functions are always guaranteed
to receive the updated state of the app.
Let's extend our example to include multiple outputs.
Dash App With Multiple Outputs
So far all the callbacks we've written only update a single Output
property.
We can also update several outputs at once: list all the properties you want to update
in callback!
,
and return that many items from the callback. This is particularly useful if
two outputs depend on the same computationally intensive intermediate result,
such as a slow database query.
using Dash
app = dash()
app.layout = html_div() do
dcc_input(id = "input-4", value = "1", type = "text"),
html_tr((html_td("x^2 ="), html_td(id = "square"))),
html_tr((html_td("x^3 ="), html_td(id = "cube"))),
html_tr((html_td("2^x ="), html_td(id = "twos"))),
html_tr((html_td("3^x ="), html_td(id = "threes"))),
html_tr((html_td("x^x ="), html_td(id = "xx")))
end
callback!(
app,
Output("square", "children"),
Output("cube", "children"),
Output("twos", "children"),
Output("threes", "children"),
Output("xx", "children"),
Input("input-4", "value"),
) do x
if x == "" || x == nothing
return ("", "", "", "", "")
end
x = parse(Int64, x)
return (x^2, x^3, 2^x, 3^x, x^x)
end
run_server(app, "0.0.0.0", debug=true)
x2 | |
x3 | |
2x | |
3x | |
xx |
A word of caution: it's not always a good idea to combine outputs, even if you can:
- If the outputs depend on some, but not all, of the same inputs, then keeping them separate can avoid unnecessary updates.
- If the outputs have the same inputs but they perform very different computations with these inputs, keeping the callbacks separate can allow them to run in parallel.
Dash App With Chained Callbacks
You can also chain outputs and inputs together: the output of one callback function could be the input of another callback function.
This pattern can be used to create dynamic UIs where, for example, one input component updates the available options of another input component. Here's a simple example.
using Dash
using CSV, DataFrames
app = dash()
all_options = Dict(
"America" => ["New York City", "San Francisco", "Cincinnati"],
"Canada" => ["Montreal", "Toronto", "Ottawa"],
)
app.layout = html_div() do
html_div(
children = [
dcc_radioitems(
id = "countries-radio",
options = [(label = i, value = i) for i in keys(all_options)],
value = "America",
),
html_hr(),
dcc_radioitems(id = "cities-radio"),
html_hr(),
html_div(id = "display-selected-values"),
],
)
end
callback!(
app,
Output("cities-radio", "options"),
Input("countries-radio", "value"),
) do selected_country
return [(label = i, value = i) for i in all_options[selected_country]]
end
callback!(
app,
Output("cities-radio", "value"),
Input("cities-radio", "options"),
) do available_options
return available_options[1][:value]
end
callback!(
app,
Output("display-selected-values", "children"),
Input("countries-radio", "value"),
Input("cities-radio", "value"),
) do selected_country, selected_city
return "$(selected_city) is a city in $(selected_country) "
end
run_server(app, "0.0.0.0", debug=true)
The first callback updates the available options in the second
dcc_radioitems
component based off of the selected value in the
first dcc_radioitems
component.
The second callback sets an initial value when the options
property
changes: it sets it to the first value in that options
array.
The final callback displays the selected value
of each component.
If you change the value
of the countries dcc_radioitems
component, Dash will wait until the value
of the cities component is updated
before calling the final callback. This prevents your callbacks from being
called with inconsistent state like with "America"
and "Montréal"
.
Dash App With State
In some cases, you might have a "form"-like pattern in your application. In such a situation, you may want to read the value of an input component, but only when the user is finished entering all of their information in the form rather than immediately after it changes.
Attaching a callback to the input values directly can look like this:
using Dash
app = dash()
app.layout = html_div() do
dcc_input(id = "input-1", type = "text", value = "Montreal"),
dcc_input(id = "input-2", type = "text", value = "Canada"),
html_div(id = "output-keywords")
end
callback!(
app,
Output("output-keywords", "children"),
Input("input-1", "value"),
Input("input-2", "value"),
) do input_1, input_2
return "Input 1 is \"$input_1\" and Input 2 is \"$input_2\""
end
run_server(app, "0.0.0.0", debug=true)
In this example, the callback function is fired whenever any of the
attributes described by the Input
change.
Try it for yourself by entering data in the inputs above.
State
allows you to pass along extra values without
firing the callbacks. Here's the same example as above but with the two
dcc_input
components as State
and a new button component as an Input
.
using Dash
app = dash()
app.layout = html_div() do
dcc_input(id = "input-1-state", type = "text", value = "Montreal"),
dcc_input(id = "input-2-state", type = "text", value = "Canada"),
html_button(id = "submit-button-state", children = "submit", n_clicks = 0),
html_div(id = "output-state")
end
callback!(
app,
Output("output-state", "children"),
Input("submit-button-state", "n_clicks"),
State("input-1-state", "value"),
State("input-2-state", "value"),
) do clicks, input_1, input_2
return "The Button has been pressed \"$clicks\" times, Input 1 is \"$input_1\" and Input 2 is \"$input_2\""
end
run_server(app, "0.0.0.0", debug=true)
In this example, changing text in the dcc_input
boxes won't fire
the callback, but clicking on the button will. The current values of the
dcc_input
values are still passed into the callback even though
they don't trigger the callback function itself.
Note that we're triggering the callback by listening to the n_clicks
property
of the html_button
component. n_clicks
is a property that gets
incremented every time the component has been clicked on.
It's available in every component in
the DashHtmlComponents
library, but most useful with buttons.
Summary
We've covered the fundamentals of callbacks in Dash.
Dash apps are built off of a set
of simple but powerful principles: UIs that are customizable
through reactive callbacks.
Every attribute/property of a component can be modified
as the output of a callback, while a subset of the attributes (such as the value
property of dcc_dropdown
component)
are editable by the user through interacting with the page.
The next part of the Dash Fundamentals covers interactive graphing. Dash Fundamentals Part 3: Interactive Graphing