We’re excited to announce that Dash 2.17.0 has been released.
pip install dash==2.17.0
pip install plotly==5.22.0
Official Changelog Dash 2.17.0
Official Changelog Plotly 5.22.0
Highlights
TL;DR -
Dash:
- Callbacks with no Outputs: you can create a callback decorator without including Outputs, in which case the callback function will have no
return
statements. - set_pros- another way to update components: you can update component properties directly from inside a callback function – without there being callback outputs – by using
set_props
. - Update Component Properties while a Callback is Running: for example, you could disable a button that triggered a callback while the callback is still running, and then activate the button as soon as the callback ends.
- Enhancements to to dcc.Loading: among several new features, you can use your own custom spinner rather than the built-in spinners provided.
Plotly:
- Zorder: you can move a trace in front of or behind another trace by setting its
zorder
- Shape Layer: you can configure shapes to be drawn between traces and gridlines
- Hover on Subplots: with
hoversubplots=axis
, hover effects are included on stacked subplots using the same axis. - Rounded Corners on Bar Charts: assigning a number of pixels or a percentage to
barcornerradius
, you can round them corners.
Callbacks with No Outputs -
Prior to Dash version 2.17.0, all callbacks required inputs that trigger callbacks as well as outputs that are updated through the callbacks’ return statement.
With Dash v2.17.0 or higher, callbacks also support having no outputs. This can be incredibly useful for cases where you want an input to trigger a callback, but you don’t want to update any component property. For example, you may want to:
- fetch some data and save it
- send emails
- update an external database
- interact with an API
- interact with other external services outside of the Dash ecosystem
In the following example, we save the state of a Dash AG Grid displayed in the app as a CSV file in our computer when the button is clicked.
The following example has no return
statement because there are no outputs to update. If you include a return
statement, you’ll see an error.
Note: This data-saving example will not work in production because it will attempt to save the data to the server.
import pandas as pd
from dash import Dash, html, Input, State, callback
import dash_ag_grid as dag
df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv"
)
app = Dash(__name__)
columnDefs = [
{"field": "country", "filter": True},
{"field": "pop", "headerName": "Population"},
{"field": "lifeExp", "headerName": "Life Expectancy"},
]
app.layout = html.Div(
[
html.H1("No Output Example"),
html.Button("Save as CSV", id="save-as-csv"),
dag.AgGrid(
id="sample-grid-no-output",
rowData=df.to_dict("records"),
columnDefs=columnDefs,
),
]
)
@callback(
Input("save-as-csv", "n_clicks"),
State("sample-grid-no-output", "virtualRowData"),
)
def update_output_div(input_value, row_data):
filtered_df = pd.DataFrame(row_data)
filtered_df.to_csv("filtered_data.csv")
if __name__ == "__main__":
app.run(debug=True)
Callback Updates with set_props -
Prior to Dash 2.17.0, you could only update callbacks through callback outputs. With version 2.17.0 or higher, you can update component id-property pairs directly from inside a callback function – without there being callback outputs – by using set_props
. With this approach, conditionally updating different component id-property pairs is simpler because you don’t need to add them all as outputs and use dash.no_update
.
The code below builds on the example above, by adding the dbc.Alert
to the layout and displaying it using set_props
once the button is clicked to save the data:
import pandas as pd
from dash import Dash, html, Input, State, callback, set_props
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv"
)
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
columnDefs = [
{"field": "country", "filter": True},
{"field": "pop", "headerName": "Population"},
{"field": "lifeExp", "headerName": "Life Expectancy"},
]
app.layout = html.Div(
[
html.H1("Callback Updates with set_props"),
dbc.Alert("Data saved and downloaded successfully!",
id="alert",
color="success",
dismissable=True,
is_open=False,
style={'width':"50%"}),
html.Button("Save as CSV", id="save-as-csv"),
dag.AgGrid(
id="sample-grid-no-output",
rowData=df.to_dict("records"),
columnDefs=columnDefs,
),
]
)
@callback(
Input("save-as-csv", "n_clicks"),
State("sample-grid-no-output", "virtualRowData"),
)
def update_output_div(input_value, row_data):
filtered_df = pd.DataFrame(row_data)
filtered_df.to_csv("filtered_data.csv")
set_props("alert", {"is_open": True})
if __name__ == "__main__":
app.run(debug=True)
The usage of regular callback outputs are encouraged whenever possible. set_props is an advanced feature that requires a strong understanding of Dash and callbacks. It also has several limitations:
-
Component properties updated using
set_props
won’t appear in the callback for debugging -
Component properties updated using
set_props
won’t appear as loading when they are wrapped with adcc.Loading
component -
set_props
doesn’t validate theid
orproperty
names provided, so no error will be displayed if they contain typos. This can make apps that useset_props
harder to debug. -
Using
set_props
with chained callbacks may lead to unexpected results. -
it will not appear in the callback graph
Updating Component Properties while a Callback is Running -
You can use the running argument on a callback to update specific component id-property pairs when the callback is running. For example, you could disable a button that triggered a callback while the callback is still running.
running
accepts a list of three element tuples, where:
- The first element of each tuple must be an Output dependency object referencing a property of a component in the app layout.
- The second element is the value that the property should be set to while the callback is running.
- The third element is the value the property should be set to when the callback completes.
Building on the previous example with set_props
, let’s assume that the callback takes 3 seconds to fully execute. We’ll use the running argument to disable the button while the callback is running, and reactivate the button when the callback finished running.
import pandas as pd
from dash import Dash, html, Input, Output, State, callback, set_props
import dash_ag_grid as dag
import dash_bootstrap_components as dbc
import time
df = pd.read_csv(
"https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv"
)
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
columnDefs = [
{"field": "country", "filter": True},
{"field": "pop", "headerName": "Population"},
{"field": "lifeExp", "headerName": "Life Expectancy"},
]
app.layout = html.Div(
[
html.H1("Updating Component While Callback is Running"),
dbc.Alert("Data saved and downloaded successfully!",
id="alert",
color="success",
dismissable=True,
is_open=False,
style={'width':"50%"}),
html.Button("Save as CSV", id="save-as-csv"),
dag.AgGrid(
id="sample-grid-no-output",
rowData=df.to_dict("records"),
columnDefs=columnDefs,
),
]
)
@callback(
Input("save-as-csv", "n_clicks"),
State("sample-grid-no-output", "virtualRowData"),
running=[(Output("save-as-csv", "disabled"), True, False)]
)
def update_output_div(input_value, row_data):
filtered_df = pd.DataFrame(row_data)
filtered_df.to_csv("filtered_data.csv")
time.sleep(3)
set_props("alert", {"is_open": True})
if __name__ == "__main__":
app.run(debug=True)
For additional examples using the running argument, see the Advanced Callbacks chapter.
App Layout as List -
To make writing a Dash layout even simpler, as of Dash 2.17.0, you can wrap all your layout components inside of a list instead of using html.Div()
.
app.layout = [
html.H1(children='Title of Dash App'),
dcc.Dropdown(df.country.unique(), 'Canada', id='dropdown-selection'),
dcc.Graph(id='graph-content')
]
New additions to dcc.Loading -
It’s now possible to:
- Delay showing the spinner for a specified number of milliseconds - which eliminates the flickering when the loading time is very short.
- Make the background visible and customizable with CSS – like adding
blur
oropacity
. This makes the transition between loading and ready a lot smoother. - Use your own custom spinner rather than the built-in spinners provided.
- Target a specific component id-property combination that would trigger the loading spinner
You can find the new features in the dcc.Loading section of the dash docs.
One feature we’d like to highlight is the Custom Spinner Component.
Instead of using one of the built-in spinners, you can use a Dash component as the spinner. This example sets the custom_spinner
property to a component that includes a spinner from the Dash Bootstrap Components library, but you can use any components to set the custom_spinner
prop.
Note that the type
, fullscreen
, debug
, color
, style
and className
properties are specific to the built-in spinner types and do not work with custom spinner components.
import time
from dash import Dash, Input, Output, callback, html, dcc, no_update
import plotly.express as px
import dash_bootstrap_components as dbc
data_canada = px.data.gapminder().query("country == 'Canada'")
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = dbc.Container(
[
dbc.Button("Start", id="custom-spinner-button", n_clicks=0),
html.Hr(),
dcc.Loading(
[dcc.Graph(id="custom-spinner-output", figure=px.line(data_canada, x="year", y="pop"))],
overlay_style={"visibility":"visible", "opacity": .5, "backgroundColor": "white"},
custom_spinner=html.H2(["My Custom Spinner", dbc.Spinner(color="danger")]),
),
]
)
@callback(
Output("custom-spinner-output", "figure"),
Input("custom-spinner-button", "n_clicks"),
)
def load_output(n):
if n:
time.sleep(1)
return px.bar(data_canada, x="year", y="pop")
return no_update
if __name__ == "__main__":
app.run(debug=True)
Thank you @AnnMarieW for your contributions to this component.
Plotly.py & dcc.Graph Updates
Updated Plotly.js to version 2.32.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.22.0, so we recommend that you upgrade to Plotly 5.22.0 to get the full benefit of all of these libraries working together.
pip install plotly==5.22.0
Official Changelog Plotly v5.22.0
dcc.Graph
Trace Zorder -
We’re excited about this new feature because it has been the most popular feature request on Plotly.py, with 68 likes.
You can move a trace in front of or behind another trace by setting its zorder
. All traces have a default zorder
of 0
. Traces with a higher zorder
appear at the front, and traces with a lower zorder
at the back. In the following example, we set zorder
on the bar trace to 1
to move it in front of the scatter trace.
import plotly.graph_objects as go
from dash import Dash, dcc
x = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
y_bar = [10, 15, 7, 10, 17, 15, 14, 20, 16, 19, 15, 17]
y_area = [12, 13, 10, 14, 15, 13, 16, 18, 15, 17, 14, 16]
area_trace = go.Scatter(
x=x,
y=y_area,
fill="tozeroy",
mode="lines+markers",
name="Area Trace with default `zorder` of 0",
line=dict(color="lightsteelblue"),
)
bar_trace = go.Bar(
x=x,
y=y_bar,
name="Bar Trace with `zorder` of 1",
zorder=1,
marker=dict(color="lightslategray"),
)
fig = go.Figure(data=[area_trace, bar_trace])
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
Shape Layer -
By default, shapes are drawn above traces. You can also configure them to be drawn between traces and gridlines with layer="between"
or below gridlines with layer="below"
.
import plotly.express as px
from dash import Dash, dcc
df = px.data.stocks(indexed=True)
fig = px.line(df)
fig.add_shape(
type="rect",
x0="2018-03-01",
y0=0,
x1="2018-08-01",
y1=3,
line_width=0,
layer="above",
label=dict(text="Above", textposition="top center", font=dict(size=15)),
fillcolor="LightGreen",
opacity=0.80,
)
fig.add_shape(
type="rect",
x0="2018-10-01",
y0=0,
x1="2019-03-01",
y1=3,
line_width=0,
layer="between",
label=dict(text="Between", textposition="top center", font=dict(size=15)),
fillcolor="LightGreen",
opacity=0.80,
)
fig.add_shape(
type="rect",
x0="2019-05-01",
y0=0,
x1="2019-10-01",
y1=3,
line_width=0,
layer="below",
label=dict(text="Below", textposition="top center", font=dict(size=15)),
fillcolor="LightGreen",
opacity=0.80,
)
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
Hover on Subplots -
Use hoversubplots
to define how hover effects expand to additional subplots. With hoversubplots=axis
, hover effects are included on stacked subplots using the same axis when hovermode
is set to x
, x unified
, y
, or y unified
.
import plotly.graph_objects as go
from dash import Dash, dcc
import pandas as pd
from plotly import data
df = data.stocks()
layout = dict(
hoversubplots="axis",
title="Stock Price Changes",
hovermode="x",
grid=dict(rows=3, columns=1),
)
data = [
go.Scatter(x=df["date"], y=df["AAPL"], xaxis="x", yaxis="y", name="Apple"),
go.Scatter(x=df["date"], y=df["GOOG"], xaxis="x", yaxis="y2", name="Google"),
go.Scatter(x=df["date"], y=df["AMZN"], xaxis="x", yaxis="y3", name="Amazon"),
]
fig = go.Figure(data=data, layout=layout)
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
Indent Legend Entries -
To indent legend entries, set indenation
on layout.legend
to a number of pixels. In the following example, we indent legend entries by 10 pixels.
from dash import Dash, dcc
import plotly.graph_objects as go
from plotly import data
df = data.iris()
fig = go.Figure(
[
go.Scatter(
x=df[df["species"] == species]["sepal_width"],
y=df[df["species"] == species]["sepal_length"],
mode="markers",
name=species,
)
for species in df["species"].unique()
],
layout=dict(
legend=dict(
title="Species",
indentation=10
)
),
)
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
Rounded Corners on Bar Charts -
You can round the corners on all bar traces in a figure by setting barcornerradius
on the figure’s layout. barcornerradius
can be a number of pixels or a percentage of the bar width (using a string ending in %, for example “20%”).
In this example, we set all bars to have a radius of 15 pixels.
from dash import Dash, dcc
import plotly.graph_objects as go
from plotly import data
df = data.medals_wide()
fig = go.Figure(
data=[
go.Bar(x=df.nation, y=df.gold, name="Gold"),
go.Bar(x=df.nation, y=df.silver, name="Silver"),
go.Bar(x=df.nation, y=df.bronze, name="Bronze"),
],
layout=dict(
barcornerradius=15,
),
)
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
When you don’t want all bar traces in a figure to have the same rounded corners, you can instead configure rounded corners on each trace using marker.cornerradius
. In this example, which uses subplots, the first trace has a corner radius of 30 pixels, the second trace has a bar corner radius of 30% of the bar width, and the third trace has no rounded corners set.
from dash import Dash, dcc
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from plotly import data
df = data.medals_wide()
fig = make_subplots(rows=1, cols=3, shared_yaxes=True)
fig.add_trace(
go.Bar(x=df.nation, y=df.gold, name="Gold", marker=dict(cornerradius=30)),
1,
1
)
fig.add_trace(
go.Bar(x=df.nation, y=df.silver, name="Silver", marker=dict(cornerradius="30%")),
1,
2,
)
fig.add_trace(
go.Bar(x=df.nation, y=df.bronze, name="Bronze"),
1,
3,
)
app = Dash()
app.layout = [dcc.Graph(figure=fig)]
if __name__ == "__main__":
app.run()
Notable Bug Fixes, Additions & Minor Changes -
Dash:
Added
- #2832 Add dash startup route setup on Dash init.
- #2819 Add dash subcomponents receive additional parameters passed by the parent component. Fixes #2814. Thank you @insistence for the PR.
- #2826 When using Pages, allows for
app.title
and (new)app.description
to be used as defaults for the page title and description. Fixes #2811. Thank you @AnnMarieW for the PR. - #2795 Allow list of components to be passed as layout.
- #2760 New additions to dcc.Loading resolving multiple issues: Thank you @AnnMarieW for the PR.
delay_show
anddelay_hide
props to prevent flickering during brief loading periods (similar to Dash Bootstrap Components dbc.Spinner)overlay_style
for styling the loading overlay, such as setting visibility and opacity for childrentarget_components
specifies components/props triggering the loading spinnercustom_spinner
enables using a custom component for loading messages instead of built-in spinnersdisplay
overrides the loading status with options for “show,” “hide,” or “auto”
- #2822 Support no output callbacks. Fixes #1549
- #2822 Add global set_props. Fixes #2803
- #2762 Add dynamic loading of component libraries.
- Add
dynamic_loading=True
to dash init. - Add
preloaded_libraries=[]
to dash init, included libraries names will be loaded on the index like before.
- Add
- #2758
- Exposing
set_props
todash_clientside.set_props
to allow for JS code to interact directly with the dash eco-system. Thank you @jinnyzor for the PR.
- Exposing
- #2730 Load script files with
.mjs
ending as js modules - #2770 Add running to regular callbacks.
Changed
- #2734 Configure CI for Python 3.10 #1863.
- #2735 Configure CI for Python 3.8 and 3.12, drop support for Python 3.6 and Python 3.7 #2736
- Thank you @graingert-coef for the PRs.
Fixed
- #2362 Global namespace not polluted any more when loading clientside callbacks. Thank you @frnhr for PR.
- #2833 Allow data url in link props. Fixes #2764
- #2822 Fix side update (running/progress/cancel) dict ids. Fixes #2111
- #2817 Change hashing algorithm from md5 to sha256, Fixes #2697. Thank you @caplinje-NOAA for PR.
- #2816 Fix dcc.Dropdown value not updated when option is removed. Fixes #2733. Thank you @TillerBurr for the PR.
- #2823 Fix None in “wait” methods causing incorrectly passing tests. Fixes #2818. Thank you @TillerBurr for the PR.
- #2783 Remove dynamic loading.
- #2756 Prevent false dangerous link warning. Fixes #2743. Thank you @AnnMarieW for the PR.
- #2752 Fixed issue with Windows build, for first time build on Windows, the dev needs to use
npm run first-build
. Thank you @jinnyzor for the PR
Plotly:
Updated
- #6956 Add “bold” weight, “italic” style and “small-caps” variant options to fonts.
- #6967 Fix applying autotickangles on axes with showdividers as well as cases where tickson is set to “boundaries”, with thanks to @my-tien for the contribution!
- #6970 Fix positioning of multi-line axis titles with standoff, with thanks to @my-tien for the contribution!
- [#6918, #6953] Add
zorder
attribute to various cartesian traces for controlling stacking order of SVG traces drawn into a subplot. This feature was anonymously sponsored: thank you to our sponsor! - #6927 Add “between” option to shape layer for placing them above grid lines and below traces, with thanks to @my-tien for the contribution!
- #6938 Add “raw”
sizemode
to cone trace. - [#6947, #6950] Add
layout.hoversubplots
to enable hover effects across multiple cartesian suplots sharing one axis. - #6905 Add fill gradients for scatter traces, with thanks to @lumip for the contribution!
- #6874 Add
indentation
to legend, with thanks to @my-tien for the contribution! - #6761 Add
layout.barcornerradius
andtrace.marker.cornerradius
properties to support rounding the corners of bar traces, with thanks to Displayr for sponsoring development! - #6790 Add
autotickangles
to cartesian and radial axes, with thanks to @my-tien for the contribution! - #6800 Add
align
option to sankey nodes to control horizontal alignment, with thanks to @adamreeve for the contribution! - #6784 Add the possibility of loading “virtual-webgl” script for WebGL 1 to help display several WebGL contexts on a page.
- #5230 Add options to use base64 encoding (
bdata
) andshape
(for 2 dimensional arrays) to declare various typed arrays, i.e.dtype=(float64|float32|int32|int16|int8|uint32|uint16|uint8)
- [#6776, #6778] Adjust stamen styles to point to
stadiamaps.com
, the users may also need to provide their own API_KEY viaconfig.mapboxAccessToken
Fixed
- #4562 Fixed
orjson
loading issue - #4429 Ensure scatter
mode
is deterministic frompx
. Thanks to @alev000 for the PR. - #4411 Fix issue with creating dendrogram in subplots. Thanks to @Brainor for the PR.
- #2812 Fix issue with px.line not accepting “spline” line shape.
- #4437 Fix KeyError when using column of
pd.Categorical
dtype with unobserved categories. Thanks to @arcanaxion for the PR. - #4442 Fix dataframe interchange in case
column_names
returns an unmaterialized object: generator, iterator, etc. Thanks to @anmyachev for the PR. - #4519 Fix issue with
FutureWarning
being displayed when setting thecolor
argument inplotly.express
Previous Releases
Dash 2.15.0 Released - Slider Tooltip Styling, Setting Allowed Axes Range, More Page Routing Inputs
Dash 2.13.0 Released - Coupling with Plotly.py, Shapes in Legends, and Broader DataFrame Support
Dash 2.11.0 Released - Dash in Jupyter, Locked Flask versions, and dcc.Graph Updates
Dash 2.9.2 Released - Partial Property Updates, Duplicate Outputs, dcc.Geolocation, Scatter Group Attributes
Dash 2.7 Released - Directional Arrows Feature, Map Bounds, and DataTable Filter Text