đź“Ł Dash 2.17.0 Released - Callback updates with set_props, No Output Callbacks, Layout as List, dcc.Loading, Trace Zorder

:rocket: 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 :arrow_forward: Dash 2.17.0

Official Changelog :arrow_forward: Plotly 5.22.0

Highlights :arrow_down_small:

:point_right: 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.

:writing_hand: 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)

:writing_hand: 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)

:stop_sign: 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 a dcc.Loading component

  • set_props doesn’t validate the id or property names provided, so no error will be displayed if they contain typos. This can make apps that use set_props harder to debug.

  • Using set_props with chained callbacks may lead to unexpected results.

  • it will not appear in the callback graph

:writing_hand: 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)

callback running

For additional examples using the running argument, see the Advanced Callbacks chapter.

:writing_hand: 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')
]

:writing_hand: 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 or opacity. 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)

custom spinner

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 :arrow_forward: Plotly v5.22.0

:arrow_right: dcc.Graph

:point_right: 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()

:point_right: 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()

:point_right: 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()

:point_right: 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()

:point_right: 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. :pray: 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. :pray: 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: :pray: Thank you @AnnMarieW for the PR.
    • delay_show and delay_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 children
    • target_components specifies components/props triggering the loading spinner
    • custom_spinner enables using a custom component for loading messages instead of built-in spinners
    • display 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.
  • #2758
    • Exposing set_props to dash_clientside.set_props to allow for JS code to interact directly with the dash eco-system. :pray: Thank you @jinnyzor for the PR.
  • #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
  • :pray: Thank you @graingert-coef for the PRs.

Fixed

  • #2362 Global namespace not polluted any more when loading clientside callbacks. :pray: 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. :pray: Thank you @caplinje-NOAA for PR.
  • #2816 Fix dcc.Dropdown value not updated when option is removed. Fixes #2733. :pray: Thank you @TillerBurr for the PR.
  • #2823 Fix None in “wait” methods causing incorrectly passing tests. Fixes #2818. :pray: Thank you @TillerBurr for the PR.
  • #2783 Remove dynamic loading.
  • #2756 Prevent false dangerous link warning. Fixes #2743. :pray: 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. :pray: 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”, :pray: with thanks to @my-tien for the contribution!
  • #6970 Fix positioning of multi-line axis titles with standoff, :pray: 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, :pray: 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, :pray: with thanks to @lumip for the contribution!
  • #6874 Add indentation to legend, :pray: with thanks to @my-tien for the contribution!
  • #6761 Add layout.barcornerradius and trace.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, :pray: with thanks to @my-tien for the contribution!
  • #6800 Add align option to sankey nodes to control horizontal alignment, :pray: 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) and shape (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 via config.mapboxAccessToken

Fixed

  • #4562 Fixed orjson loading issue
  • #4429 Ensure scatter mode is deterministic from px. :pray: Thanks to @alev000 for the PR.
  • #4411 Fix issue with creating dendrogram in subplots. :pray: 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. :pray: Thanks to @arcanaxion for the PR.
  • #4442 Fix dataframe interchange in case column_names returns an unmaterialized object: generator, iterator, etc. :pray: Thanks to @anmyachev for the PR.
  • #4519 Fix issue with FutureWarning being displayed when setting the color argument in plotly.express

Previous Releases

:mega: Dash 2.15.0 Released - Slider Tooltip Styling, Setting Allowed Axes Range, More Page Routing Inputs
:mega: Dash 2.13.0 Released - Coupling with Plotly.py, Shapes in Legends, and Broader DataFrame Support
:mega: Dash 2.11.0 Released - Dash in Jupyter, Locked Flask versions, and dcc.Graph Updates
:mega: Dash 2.9.2 Released - Partial Property Updates, Duplicate Outputs, dcc.Geolocation, Scatter Group Attributes
:mega: Dash 2.7 Released - Directional Arrows Feature, Map Bounds, and DataTable Filter Text

17 Likes

Just AWESOME! Dash nowadays feels so mature but you still find some ways for even easier and more flexible app creation. Huge congrats and thank you to everybody contributing!

4 Likes

That’s really cool. The set_Props is amazing and also to customize the ordering for traces!
Finally I can get rid of some work aroudns :smiley:

2 Likes

Awesome changes, dash has huge potential to become industry standard. There is only one thing lacking - simple internationalization like i18n

1 Like

i trying to set multiple setprops, but only one works, is there any way to set multiple in one callback?

Hi @Reveur
This is a bug that was fixed recently and will be in the next release. PR#2856

1 Like

Thank you for sharing about the bug fix, @AnnMarieW

@Reveur
In the meantime, if you are updating the same components, try to combine the set_props into one. For example, looking at this code:

app.layout = [
    html.Div("initial", id="my-div"),
]
@callback(....)
def some_funct(_):
    set_props("my-div", {"children": "changed"})
    set_props("my-div", {"style": {"background": "red"}})

Try to convert the last two lines to one:

    set_props("my-div", {"children": "changed", "style": {"background": "red"}})
1 Like

Hey @adamschroeder this is awesome, I just have a question about implementing the running parameter for setting properties of a component while a callback is running. It works like a charm for normal ids, but it raises an error when trying to use it for wildcard outputs. For example

running=[
    (
        Output(
           {"type": "test-type", "index":MATCH},
           "disabled",
           ),
           True,
           False,
    )
]

would raise an error Cannot read properties of undefined (reading “concat”). Am i doing something wrong or is this not availible? My component is a dmc.Button. Thanks in advance!

Hi @dashamateur,

Try upgrading to 2.17.1 :slight_smile:

1 Like

Ah, @AnnMarieW , is running working with Match? I was always under the impression that it did not. :sweat_smile:

Hi @simon-u This should work now in 2.17.1

1 Like

Works great, thanks for the reply! :slight_smile: