Update : version 2.11.0 has been released since this was posted.
We’re excited to announce that Dash 2.9.2 has been released
Register for the upcoming webinar to learn how to use the open-source feature.
pip install dash==2.9.2
Official Changelog Dash v2.9.2
Highlights 
Partial Property Updates with Patch()
Partial Property Updates can improve the performance of your Dash apps by preventing unnecessary computation, data fetching, and reducing the amount of data that travels over the network between the browser and the server.
Consider a simple example with a dropdown and a graph: Currently, Dash callbacks update the entire figure object and send that data over the network. However in many cases, only certain parts of the graph change. For example, the x-axis data might stay the same while the y-axis data changes or the color of the graph will change but the data will remain the same.
With Partial Property Updates, you can update your callbacks to send over the new y-axis data on its own without the rest of the figure. As a result, it reduces the size of the network payload by 50% (1 array of x
data instead of 2 arrays of x
and y
data), and there can be a noticeable performance improvement to charts or tables with >10K points.
The code change is minimal. In previous Dash versions (previous to Dash v2.9.0), you would have:
app.layout = html.Div([
dcc.Dropdown(df.columns, id="column-selected"),
dcc.Graph(id=="my-graph")
])
@app.callback(Output("my-graph", "figure"), Input("column-selected", "value"))
def update(value):
return px.scatter(df, x=df.Index, y=df.columns[1])
Notice how the whole figure is recreated inside the callback and sent back to the browser.
With Partial Property Updates (Dash v.2.9 or higher), you can return a Patch() object that tells Dash’s frontend to only change the “y” data in the first trace instead of returning the entire figure.
app.layout = html.Div([
dcc.Dropdown(df.columns, id="column-selected"),
dcc.Graph(id="my-graph", figure=px.scatter(df, x=df.index, y=df.columns[1]))
])
@app.callback(Output("my-graph", "figure"), Input("column-selected", "value"),
prevent_initial_call=True)
def update(value):
patch_figure = Patch()
patch_figure["data"][0]["y"] = df[value]
return patch_figure
Extending, Appending, Merging, and More
Patch() defines operations that can be applied to the property in the browser client. In the example above, we did a nested assignment to the data in the figure located at [“data”][0][“y”]
.
In addition to assignment, Patch() supports extending, appending, merging, and more. Here’s the full list. The “Result” assumes that ["data"][0]["x"]
is initialized to["B", "C", "D"]
.
Method | Works On | Example | Result |
---|---|---|---|
1.prepend | Lists | Patch()[“data”][0][“x”].prepend(“A”) | ["A","B","C","D"] |
2. append | Lists | Patch()[“data”][0][“x”].append(“E”) | ["B","C","D","E"] |
3. extend | Lists | Patch()[“data”][0][“x”].extend([“E”,“F”]) | ["B","C","D","E","F"] |
4. reverse | Lists | Patch()[“data”][0][“x”].reverse() | ["D","C","B"] |
5. insert | Lists | Patch()[“data”][0][“x”].insert(2, “X”) | ["B","C","X","D"] |
6. clear | Lists | Patch()[“data”][0][“x”].clear() | [ ] |
7. remove | Lists | Patch()[“data”][0][“x”].remove(“C”) | ["B","D"] |
8. del | Lists | del Patch()[“data”][0][“x”][0] | ["C","D"] |
9. update | Dictionaries | Patch()[“data”][0][“marker”].update({“color”: “red”}) | {“color”: “red”} instead of {“color”: “#636efa”} |
10. = | List, Dict, Str, Int | Patch()[“layout”][“title”] = “New Title of App” | New Title of App |
The most important thing to keep in mind is the type of objects you’re attempting to update.
[“data”][0][“x”]
is of type list, which is why we could use methods 1 through 8. Theupdate
method will only work on objects that are of dictionary type, such as["data"][0]["marker"]
.
The new Dash Docs chapter on the Partial Property Updates is very informative and full of helpful examples; we highly recommend you check it out. That said, below we’ll highlight a few of our favorite examples.
Adding Data
Adding data to an existing component is a really common use case for Patch(). Here are 3 examples of adding data to graphs, tables, and network graphs.
Graph:
from dash import Dash, html, dcc, Input, Output, Patch
import plotly.graph_objects as go
import datetime
import random
app = Dash(__name__)
fig = go.Figure()
app.layout = html.Div([
html.Button("Append", id="append-new-val"),
dcc.Graph(figure=fig, id="append-example-graph"),
])
@app.callback(
Output("append-example-graph", "figure"),
Input("append-new-val", "n_clicks"),
prevent_initial_call=True,
)
def add_data_to_fig(n_clicks):
current_time = datetime.datetime.now()
random_value = random.randrange(1, 30, 1)
patched_figure = Patch()
patched_figure["data"][0]["x"].append(current_time)
patched_figure["data"][0]["y"].append(random_value)
return patched_figure
if __name__ == "__main__":
app.run_server(debug=True)
AG Grid Table
from dash import Dash, html, Input, Output, Patch, no_update
import plotly.express as px
import dash_ag_grid as dag
app = Dash(__name__)
df = px.data.iris()
app.layout = html.Div([
html.Button("Add Rows", id="add-data-rows"),
dag.AgGrid(
id="ag-table",
className="ag-theme-alpine-dark",
columnDefs=[{"headerName": x, "field": x} for x in df.columns],
rowData=df.to_dict("records"),
columnSize="sizeToFit",
dashGridOptions={"pagination": True},
)
])
@app.callback(
Output("ag-table", "rowData"),
Input("add-data-rows", "n_clicks"),
prevent_initial_call=True,
)
def add_data_to_fig(n_clicks):
patched_table = Patch()
patched_table.extend(df.to_dict("records"))
return patched_table
if __name__ == "__main__":
app.run_server(debug=True)
Network Graph (Cytoscape):
from dash import Dash, html, Output, Input, Patch, State
import dash_cytoscape as cyto
app = Dash(__name__)
original_elements = [
# The nodes elements
{'data': {'id': 'id_1', 'label': 'Node 1'},
'position': {'x': 50, 'y': 50}},
{'data': {'id': 'id_2', 'label': 'Node 2'},
'position': {'x': 200, 'y': 200}},
# The edge elements
{'data': {'source': 'id_1', 'target': 'id_2', 'label': 'Node 1 to 2'}},
]
app.layout = html.Div([
html.Button("Update Cytoscape", id="add-data", n_clicks=2),
cyto.Cytoscape(
id='my-cytoscape',
layout={'name': 'preset'},
style={'width': '100%', 'height': '400px'},
elements=original_elements
)
])
@app.callback(
Output("my-cytoscape", "elements"),
Input("add-data", "n_clicks"),
prevent_initial_call=True,
)
def add_data_to_fig(n_clicks):
patched_cyto = Patch()
patched_cyto.append({'data': {'id': f'id_{n_clicks}', 'label': f'Node {n_clicks}'},
'position': {'x': 10*n_clicks, 'y': 10*n_clicks}})
patched_cyto.append({'data': {'source': f'id_{n_clicks-1}', 'target': f'id_{n_clicks}', 'label': f'Node {n_clicks-1} to {n_clicks}'}})
return patched_cyto
if __name__ == '__main__':
app.run_server(debug=True)
Updating Charts
Another common class of use cases is updating a chart without changing the data.
This example demonstrates how to highlight scatter points within a graph:
from dash import Dash, dcc, html, Input, Output, Patch
import plotly.express as px
app = Dash(__name__)
# Getting our data
df = px.data.gapminder()
df = df.loc[df.year == 2002].reset_index()
df = df.loc[:10,:]
# Creating our figure
fig = px.scatter(x=df.lifeExp, y=df.gdpPercap, hover_name=df.country)
fig.update_traces(marker=dict(color="blue"))
app.layout = html.Div([
html.H4("Updating Point Colors"),
dcc.Dropdown(id="dropdown", options=df.country.unique(), multi=True),
dcc.Graph(id="graph-update-example", figure=fig),
])
@app.callback(
Output("graph-update-example", "figure"), Input("dropdown", "value"), prevent_initial_call=True
)
def update_markers(countries):
country_count = list(df[df.country.isin(countries)].index)
patched_figure = Patch()
updated_markers = [
"Crimson" if i in country_count else "blue" for i in range(len(df) + 1)
]
patched_figure['data'][0]['marker']['color'] = updated_markers
return patched_figure
if __name__ == '__main__':
app.run_server(debug=True)
Or adding annotations:
from dash import Dash, html, dcc, Input, Output, Patch
import plotly.graph_objects as go
app = Dash(__name__)
fig = go.Figure(
[
go.Scatter(x=[0, 1, 2, 3, 4, 5, 6, 7, 8], y=[0, 1, 3, 2, 4, 3, 4, 6, 5]),
go.Scatter(x=[0, 1, 2, 3, 4, 5, 6, 7, 8], y=[0, 4, 5, 1, 2, 2, 3, 4, 2]),
],
go.Layout(
dict(
annotations=[
dict(
x=2,
y=5,
text="Text annotation with arrow",
showarrow=True,
arrowhead=1,
),
dict(
x=4,
y=4,
text="Text annotation without arrow",
showarrow=False,
yshift=10,
),
]
),
showlegend=False,
),
)
app.layout = html.Div([
html.Button("Show/Clear Annotations", id="clear-button"),
dcc.Graph(id="clear-example", figure=fig),
])
@app.callback(Output("clear-example", "figure"), Input("clear-button", "n_clicks"))
def add_data_to_fig(n_clicks):
patched_figure = Patch()
if n_clicks and n_clicks % 2 != 0:
patched_figure["layout"]["annotations"].clear()
else:
patched_figure["layout"]["annotations"].extend(
[
dict(
x=2,
y=5,
text="Text annotation with arrow",
showarrow=True,
arrowhead=1,
),
dict(
x=4,
y=4,
text="Text annotation without arrow",
showarrow=False,
yshift=10,
),
]
)
return patched_figure
if __name__ == "__main__":
app.run_server(debug=True)
Pattern Matching Callback Support
This even works with appending components with the children component. Callbacks can be defined on dynamic components with pattern matching callbacks.
from dash import Dash, dcc, html, Input, Output, ALL, Patch
app = Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.Button("Add Filter", id="add-filter", n_clicks=0),
html.Div(id="dropdown-container", children=[]),
html.Div(id="dropdown-container-output"),
])
@app.callback(
Output("dropdown-container", "children"),
Input("add-filter", "n_clicks"),
)
def display_dropdowns(n_clicks):
patched_children = Patch()
new_dropdown = dcc.Dropdown(
["NYC", "MTL", "LA", "TOKYO"],
id={"type": "filter-dropdown", "index": n_clicks},
)
patched_children.append(new_dropdown)
return patched_children
@app.callback(
Output("dropdown-container-output", "children"),
Input({"type": "filter-dropdown", "index": ALL}, "value"),
)
def display_output(values):
return html.Div(
[
html.Div("Dropdown {} = {}".format(i + 1, value))
for (i, value) in enumerate(values)
]
)
if __name__ == "__main__":
app.run_server(debug=True)
Allowing Duplicate Callback Outputs
Multiple callbacks can now target the same output! Previously, you had to use ctx.triggered
within a single callback. Now you can just write separate callback functions. This feature was discussed in length by the community in the topic “Duplicate Callback Outputs - Solution & API Discussion” - Many thanks to everyone who has contributed to the discussion over the years
To target the same component-property from multiple callbacks, set allow_duplicate=True
on the duplicate callback Output:
@app.callback(
Output("id_name", "compnt_prop", allow_duplicate=True),
Input("id_name", "compnt_prop"),
prevent_initial_call=True
)
Duplicate callback outputs appear frequently with Patch() & partial property outputs where different callbacks will perform different operations. Here is the earlier example rewritten to allow duplicates. In this example clicking one button adds rows to AgGrid and clicking the second button refreshes the entire dataset.
from dash import Dash, html, Input, Output, Patch, no_update
import plotly.express as px
import dash_ag_grid as dag
app = Dash(__name__)
df = px.data.iris()
app.layout = html.Div(
[
html.Button("Add Rows", id="add-data-rows"),
html.Button("Reload data", id="reload-button"),
dag.AgGrid(
id="ag-table",
className="ag-theme-alpine-dark",
columnDefs=[{"headerName": x, "field": x} for x in df.columns],
rowData=df.to_dict("records"),
columnSize="sizeToFit",
dashGridOptions={"pagination":True}
)
]
)
@app.callback(
Output("ag-table", "rowData", allow_duplicate=True),
Input("add-data-rows", "n_clicks"),
prevent_initial_call=True,
)
def add_data_to_fig(n_clicks):
patched_table = Patch()
patched_table.extend(df.to_dict("records"))
return patched_table
# Returning all records from the dataframe to the component when the reload button is clicked
@app.callback(
Output("ag-table", "rowData"),
Input("reload-button", "n_clicks")
)
def reload_data(n_clicks):
return df.to_dict("records")
if __name__ == "__main__":
app.run_server(debug=False)
When setting
allow_duplicate=True
on a callback output, you’ll need to either setprevent_initial_call=True
on the callback, or setapp = Dash(prevent_initial_callback="initial_duplicate")
on your app. This prevents callbacks that target the same output running at the same time when the page initially loads.
Curious why we didn’t make allow_duplicate=True the default? It’s because of this ambiguity around initial calls when the page loads. The order is not guaranteed if multiple callbacks target the same output and are fired at the same time without prevent_initial_call=True. This can be the source of subtle bugs! So we decided that it would be better if we could warn users that they need to set prevent_initial_call=True in addition to allow_duplicate=True when using multiple outputs.
We encourage you to read further about the duplicate callback feature in its new documentation page or in the partial property updates chapter.
New dcc.Geolocation component
The dcc.Geolocation component can be used to access location data from viewers of the Dash app (with their permission!). It uses the browser’s built-in Geolocation API. Thank you @AnnMarieW for this contribution.
In the example below, the first callback updates the location data when the button is selected. The second callback then triggers with the local_date
and position
data and outputs it to a div.
from dash import Dash, dcc, html, Input, Output
app = Dash(__name__)
app.layout = html.Div(
[
html.Button("Update Position", id="update_btn"),
dcc.Geolocation(id="geolocation"),
html.Div(id="text_position"),
]
)
@app.callback(Output("geolocation", "update_now"), Input("update_btn", "n_clicks"))
def update_now(click):
return True if click and click > 0 else False
@app.callback(
Output("text_position", "children"),
Input("geolocation", "local_date"),
Input("geolocation", "position"),
)
def display_output(date, pos):
if pos:
return html.P(
f"As of {date} your location was: lat {pos['lat']},lon {pos['lon']}, accuracy {pos['accuracy']} meters",
)
return "No position data available"
if __name__ == "__main__":
app.run_server(debug=True)
Another example where we use the dcc.Geolocation component to access location and display it on a map:
from dash import Dash, dcc, html, Input, Output
import plotly.express as px
app = Dash(__name__)
app.layout = html.Div(
[
html.Button("Update Position", id="update_btn"),
dcc.Geolocation(id="geolocation"),
html.Div(id="map-location"),
]
)
@app.callback(Output("geolocation", "update_now"), Input("update_btn", "n_clicks"))
def update_now(click):
return True if click and click > 0 else False
@app.callback(
Output("map-location", "children"),
Input("geolocation", "position"),
)
def display_output(pos):
if pos:
fig = px.scatter_mapbox(lat=[pos['lat']], lon=[pos['lon']])
fig.update_layout(mapbox_style="open-street-map")
fig.update_traces(marker_size=30)
return dcc.Graph(figure=fig)
return "No position data available"
if __name__ == "__main__":
app.run_server(debug=True)
You can read more about the Geolocation properties in the docs.
Plotly.py & dcc.Graph Updates
Updated Plotly.js to from version 2.18.2 to version 2.20.0.
The version of Plotly.js that is built in here is the same one that is bundled with the recently released Plotly.py 5.14.0, so we recommend that you upgrade to Plotly 5.14.0 to get the full benefit of all of these libraries working together.
pip install plotly==5.14.0
Official Changelog Plotly v5.14.0
There have been many developments in the world of Plotly.py and dcc.Graph recently. To highlight just a few:
dcc.Graph - Adding Text Labels to Shapes
You can now add labels directly to shapes (lines, rectangles, and circles) within a dcc.Graph using the new shape label property. Previously, adding text to shapes required adding a custom annotation or a scatter plot with text and both options were a huge pain . Special thanks to our customer at the Volkswagen Center of Excellence for Battery Systems for sponsoring development.
import plotly.graph_objects as go
fig = go.Figure()
fig.add_shape(
type="rect",
fillcolor='turquoise',
x0=1,
y0=1,
x1=2,
y1=3,
label=dict(text="Text in rectangle")
)
fig.add_shape(
type="line",
x0=3,
y0=0.5,
x1=5,
y1=0.8,
line_width=3,
label=dict(text="Text above line")
)
fig.show()
You can use other dictionary keys inside the label
property to further customize the label. For example:
import plotly.graph_objects as go
fig = go.Figure()
fig.add_shape(
type="line",
x0=3,
y0=0,
x1=4,
y1=2,
line_width=3,
label=dict(text="Text at the center of the line",
textposition='middle', # for lines: [start | middle | end], for other shapes: [ top | middle | bottom ] [ left | center | right ]
textangle=-65, # Number between -180 and 180
padding=45,
yanchor="top", # [top | middle | bottom]
xanchor="auto", # [ auto | left | center | right ]
font=dict(family="Courier New, monospace", size=15, color='red')
)
)
fig.show()
For a few more examples see the section on Adding Text Labels to Shapes. Or, for greater detail, see the Figure Reference docs on layout shapes.
Specifying Label Aliases
With labelalias
, you can specify replacement text for specific tick and hover labels without needing to change the labels in your underlying data.
In this example, the dataset has the values of “Sat” and “Sun” in the day column. By setting labelalias=dict(Sat="Saturday", Sun="Sunday")
, we swap these out for “Saturday” and “Sunday”.
import plotly.express as px
import pandas as pd
df = px.data.tips()
df = df[df.day.isin(['Sat', 'Sun'])].groupby(by='day', as_index=False).sum(numeric_only=True)
fig = px.bar(df, x="day", y="total_bill")
fig.update_xaxes(labelalias=dict(Sat="Saturday", Sun="Sunday"))
fig.show()
Grouped Scatter Support
You can now overlay scatter points over grouped bars. In the following example, we use the new attributes scattermode
and offsetgroup
on scatter to create a figure that shows individual points on a grouped scatter and averages with grouped bars. Previously, all of the scatter points would be positioned over the Male and Female label instead of over their respective bars.
For the full code for this example, see the multiple chart types page in the Plotly.py docs.
There’s also a new scattergap
attribute that you can use to adjust the gap between the different scatter groups. These examples show grouped scatter points with 1) a default gap and with 2) a scattergap of 0.75.
import plotly.express as px
df = px.data.medals_long()
fig = px.scatter(df, y="count", x="nation", color="medal")
fig.update_traces(marker_size=10)
fig.update_layout(scattermode="group")
fig.show()
import plotly.express as px
df = px.data.medals_long()
fig = px.scatter(df, y="count", x="nation", color="medal")
fig.update_traces(marker_size=10)
fig.update_layout(scattermode="group", scattergap=0.75)
fig.show()
This feature was anonymously sponsored. Thank you to our sponsor!
Syncing Axes Ticks
With overlayed axes, each axis by default has its own number of ticks. This can lead to graphs like this with odd spacing on ticks and gridlines (see how the axes on both sides don’t align)
You can now sync the number of ticks on overlayed axes, by setting "tickmode=sync"
on an axis that is overlaying another:
The full code for this example is available on the multiple axes page.
Notable Bug Fixes, Additions & Minor Changes
Dash:
- #2479 Fix
KeyError
“Callback function not found for output […], , perhaps you forgot to prepend the ‘@’?” issue when using duplicate callbacks targeting the same output. This issue would occur when the app is restarted or when running with multiplegunicorn
workers. - #2471 Fix #2467.
allow_duplicate
output with clientside callback. - #2473 Fix #2221. Fix background callbacks with different outputs but same function.
- #2461 Fix #2460. Fixes Pytest plugin make report when testing not installed.
- #2450 Fix #2063. Set label style
display: block
ifinline
is false in RadioItems & Checklist components. To keep previous behavior, setinline=True
. - #2400 Added
disable_n_clicks=True
to thehtml.Div
components inpage_container
. You can read more about it in the docs. Thank you @AnnMarieW - #2388 Fix #2368 ordering of Pattern Matching ALL after update to the subtree.
- #2367 Updated the default
favicon.ico
to the current Plotly logo - #2068 Added
refresh="callback-nav"
indcc.Location
. This allows for navigation without refreshing the page when url is updated in a callback. With thanks to @AnnMarieW - #2417 Add wait_timeout property to customize the behavior of the default wait timeout used for by wait_for_page, fix #1595
- #2417 Add the element target text for wait_for_text* error message, fix #945
- #2425 Add
add_log_handler=True
to Dash init, if you don’t want a log stream handler at all. - #2260 Experimental support for React 18. The default is still React v16.14.0, but to use React 18 you can either set the environment variable
REACT_VERSION=18.2.0
before running your app, or inside the app calldash._dash_renderer._set_react_version("18.2.0")
. THIS FEATURE IS EXPERIMENTAL. It has not been tested with component suites outside the Dash core, and we may add or remove available React versions in any future release. - #2429 Fix side effect on updating possible array children triggering callbacks, fix #2411.
- #2415 Fix background callbacks progress not deleted after fetch.
- #2426 Set default interval to 1 second for app.long_callback, restoring the behavior it had before v2.6.0 when we introduced
backround=True
callbacks.
Plotly:
- Fixed an issue with characters displaying incorrectly, by adding
charset="utf-8"
to scripts into_html
[#4114] - Added
packaging
to install requirements, fixing aNo module named 'packaging'
error on Python 3.6 [#4113] - Ensure slider range stays in bounds during the drag [#4448], with thanks to @jay-bis for the contribution!
write_html()
now explicitly encodes output as UTF-8 because Plotly.js’ bundle contains such characters [#4021] and [#4022]- fixed
iframe
renderer regression from 5.12 and also fixed error when this renderer was used in the very first cell in a notebook [#4036] - Fixed bug for trendlines with datetime axes [#3683]
- Disable slider interactions when
staticPlot
is set to true [#6393] - Fix auto
backoff
when marker symbols and sizes are arrays [#6414] - Avoid displaying resize cursor on static sliders [#6397]
- Change bundler from browserify to webpack [#6355]
- Add
shift
andautoshift
to cartesian y axes to help avoid overlapping of multiple axes [#6334], with thanks to Gamma Technologies for sponsoring the related development! - Introduce group attributes for
scatter
trace i.e.alignmentgroup
,offsetgroup
,scattermode
andscattergap
[#6381], this feature was anonymously sponsored: thank you to our sponsor! - Add
marker.cornerradius
attribute totreemap
trace [#6351] - Fix line redraw (regression introduced in 2.15.0) [#6429]
- Fix library’s imported name using
requirejs
AMD loader (regression introduced in 2.17.0) [#6440] - Improve detection of mobile & tablet devices for WebGL rendering by upgrading
is-mobile
[#6432] - Add
sync
tickmode option [#6356, #6443], with thanks to @filipesantiagoAM and @VictorBezak for the contribution!
Previous Releases
Dash 2.7 Released - Directional Arrows Feature, Map Bounds, and DataTable Filter Text
Dash 2.6 Released - Background Callbacks, Unit Testing, Persistent Selections, Dropdown Features
Dash 2.5 Released - Easier Multi-Page Apps, Component Properties
Dash 2.4 Released - Improved Callback Context, Clientside Promises, Typescript Components, Minor Ticks
Dash 2.3.0 Release - MathJax and fillpattern option in scatter trace
Dash 2.2.0 Release - Adds
ticklabelstep
to axes, and added dash.get_asset_url
Dash 2.1.0 Release - Autogenerated IDs and reärranged keyword arguments in Dash components