đź“Ł Dash 2.4 Released - Improved Callback Context, Clientside Callback Promises, Typescript Components, Minor Ticks, and More!

Update : version 2.5.0 has been released since this was posted.

Good day Community Members,

:rocket: We’re excited to announce that Dash 2.4.0 & 2.4.1 have been released. 2.4.0 contains the big feature items and 2.4.1 contains a quick bugfix. These are a backwards compatible releases and we recommend going straight to 2.4.1.

pip install dash==2.4.1

Official Changelog :arrow_forward: Dash v2.4.0

Highlights :arrow_down_small:

Improved Callback Context

:writing_hand: Improved callback_context (ctx): Thank you @AnnMarieW for designing and developing this new feature.

This new feature simplifies the syntax for determining which Input triggered a callback by replacing the infamous ctx.triggered[0]["prop_id"].split(".")[0] with a new ctx.triggered_id:

Before:

from dash import callback_context 

@app.callback(
    Output('my-div', 'children'),
    Input('button-1', 'n_clicks'),
    Input('button-2', 'n_clicks'),
)
def update(b1, b2):
    triggered_id = callback_context.triggered[0]['prop_id']
    if triggered_id == 'button-1.n_clicks':
         return 'Button 1 was clicked'
    if triggered_id == 'button-2.n_clicks':
         return 'Button 2 was clicked'

After:

from dash import ctx

@app.callback(
    Output('my-div', 'children'),
    Input('button-1', 'n_clicks'),
    Input('button-2', 'n_clicks'),
)
def update(b1, b2):
    if ctx.triggered_id == 'button-1':
         return 'Button 1 was clicked'
    if ctx.triggered_id == 'button-2':
         return 'Button 2 was clicked'

In total, dash.ctx has three additional properties to make it easier to work with (see the official docs):

  • ctx.triggered_id: The id of the component that triggered the callback. For example: 'my-button'
  • ctx.triggered_prop_ids: A dictionary of the component ids and props that triggered the callback. Useful when multiple inputs can trigger the callback at the same time, or multiple properties of the same component can trigger the callback.
  • ctx.args_grouping: A dictionary of the inputs used with flexible callback signatures. The keys are the variable names and the values are dictionaries.

ctx.triggered_id example - determining which input changed

The code below will allow us to determine which button triggered the callback. Notice that we import ctx at the very top. See more about this example in the docs.

from dash import Dash, Input, Output, html, dcc, ctx
import plotly.express as px
import plotly.graph_objects as go

#Initialise the App
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# App Layout
app.layout = html.Div([
  html.Button('Draw Graph', id='draw'),
  html.Button('Reset Graph', id='reset'),
  dcc.Graph(id='graph')
])


# Configure callbacks
@app.callback(
  Output('graph', 'figure'),
  Input(component_id='reset', component_property='n_clicks'),
  Input(component_id='draw', component_property='n_clicks'),
  prevent_initial_call=True
)
def update_graph(b1, b2):
  triggered_id = ctx.triggered_id
  print(triggered_id)

  if triggered_id == 'reset':
      return go.Figure()

  elif triggered_id == 'draw':
      df = px.data.iris()
      return px.scatter(df, x=df.columns[0], y=df.columns[1])


# Run the App
if __name__ == '__main__':
    app.run_server()

triggered_id

ctx.triggered_prop_ids example - determining which property changed

Use ctx.triggered_prop_ids when multiple properties of the same component (ID) can trigger the callback. For example, let’s build a scatter graph and display on the page data generated from the selectedData and the clickData properties of the graph.

from dash import Dash, Input, Output, html, dcc, ctx
import plotly.express as px
import plotly.graph_objects as go

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

df = px.data.iris()
fig = px.scatter(df, x=df.columns[0], y=df.columns[1])

app.layout = html.Div([
    dcc.Markdown(id='content'),
    dcc.Graph(id='graph', figure=fig)
])


@app.callback(
    Output('content', 'children'),
    Input(component_id='graph', component_property='selectedData'),
    Input(component_id='graph', component_property='clickData'),
    prevent_initial_call=True
)
def update_graph(selected, clicked):
    triggered_prop_id = ctx.triggered_prop_ids
    print(triggered_prop_id)

    if 'graph.selectedData' in triggered_prop_id:
        print(selected)
        return 'The x range of the seclected data starts from {}'.format(selected['range']['x'][0])

    elif 'graph.clickData' in triggered_prop_id:
        print(clicked)
        return 'The Sepal width of the clicked data is {}'.format(clicked['points'][0]['y'])


if __name__ == '__main__':
    app.run_server()

more-ctx-gif

ctx.args_grouping example - flexible callback signatures

Use ctx.args_grouping when working with flexible callback signatures. The args_grouping is a dictionary, where the keys are the variable names and the values are dictionaries containing:

  • "id": the component ID. If it’s a pattern matching ID, it will be a dict.
  • "id_str": for pattern matching IDs, it’s the stringified dict ID with no white spaces.
  • "property": the component property used in the callback.
  • "value": the value of the component property at the time the callback was fired.
  • "triggered": a boolean indicating whether this input triggered the callback.

In the code example below, you will notice that the callback decorator has 4 inputs, but the callback function signature has only one argument.

from dash import Dash, dcc, html, Output, Input, ctx

app = Dash(__name__)
app.layout = html.Div([
    dcc.Dropdown(options=['one','two','three'], value='one', id='dropdown-1'),
    dcc.Dropdown(options=['Apple','Banana','Citrus'], value='Apple', id='dropdown-2'),
    html.Button('Submit-1', id='btn-1'),
    html.Button('Submit-2', id='btn-2'),
    html.P(),
    html.Div(id='container')

])

@app.callback(
    Output("container", "children"),
    inputs={
        "all_inputs": {
            "btn1": Input("btn-1", "n_clicks"),
            "btn2": Input("btn-2", "n_clicks"),
            "dropdown1": Input("dropdown-1", "value"),
            "dropdown2": Input("dropdown-2", "value"),
        }
    },
)
def display(all_inputs):
    print(ctx.args_grouping)
    print("-----------------------------------------------------------------")

    c = ctx.args_grouping.all_inputs

    if c.btn1.triggered:
        return f"Button 1 clicked {c.btn1.value} times."
    elif c.btn2.triggered:
        return f"Button 2 clicked {c.btn2.value} times"
    elif c.dropdown1.triggered:
        return "Dropdown 1 was triggered."
    elif c.dropdown2.triggered:
        return [
            "Dropdown 2 was triggered.",
            f' **** Dropdown 2 property is: {c.dropdown2.property}',
            f' **** Dropdown 2 value is: {c.dropdown2.value}.',
            f' **** Dropdown 2 id is: {c.dropdown2.id}.',
        ]

    else:
        "no update"


if __name__ == '__main__':
    app.run_server(debug=True)

args_grouping

ctx.args_grouping with pattern matching callbacks example

Use args_grouping combined with pattern matching to get the index of the button that triggered the callback.

import numpy as np
import plotly.express as px
from dash import Dash, Input, Output, ctx, dcc, html, ALL

N=5

app = Dash(__name__)


def make_figure(n):
    x = np.linspace(0, 8, 81)
    y = x ** int(n)
    return px.scatter(x=x, y=y)


app.layout = html.Div(
    [
        html.Div(
            [html.Button(f"x^{i}", id={"index":i}) for i in range(N)]
        ),
        dcc.Graph(id="graph"),
    ]
)

@app.callback(
    Output("graph", "figure"), dict(btns=Input({"index":ALL}, "n_clicks"))
)
def update_graph(btns):
    # Since we are using {"index":ALL}, then
    # ctx.args_grouping.btns is a list of dicts - one dict for each button.
    for btn in ctx.args_grouping.btns:
        if btn.triggered:
            n = btn.id.index
            print(n)
            return make_figure(n)
    return {}


if __name__ == "__main__":
    app.run_server(debug=True)

pattern-matching


link_target in dcc.Markdown

By default, links in markdown open in the same page. Set dcc.Markdown(link_target='_blank') to open the link in a new browser tab when clicked. For a list of link_target attribute values, see the MDN link target docs.

from dash import Dash, dcc, html
app = Dash(__name__)
         
app.layout = html.Div([
    dcc.Markdown("[DuckDuckGo ](https://duckduckgo.com)", link_target="_blank")
    ])
    
if __name__ == '__main__':
    app.run()

Support for Promises within clientside callbacks

Which means it’s now possible to make web requests in clientside. Thank you, @MrTeale for taking this on in PR #2009 :pray:

from dash import Dash, dcc, html, Input, Output, dash_table

app = Dash(__name__)

app.layout = html.Div([
    dcc.Dropdown(
        options=[
            {
                "label": "Car-sharing data",
                "value": "https://raw.githubusercontent.com/plotly/datasets/master/carshare_data.json",
            },
            {
                "label": "Iris data",
                "value": "https://raw.githubusercontent.com/plotly/datasets/master/iris_data.json",
            },
        ],
        value="https://raw.githubusercontent.com/plotly/datasets/master/iris_data.json",
        id="data-select",
    ),
    html.Br(),
    dash_table.DataTable(id="my-table-promises", page_size=10),
    dcc.Store(id="fetched-data", storage_type="session"),
])

app.clientside_callback(
    """
    async function(value) {
        const response = await fetch(value);
        return await response.text();
    }
    """,
    Output("fetched-data", "data"),
    Input("data-select", "value"),
)


app.clientside_callback(
    """
   function(data) {
        return JSON.parse(data);
    }
    """,
    Output("my-table-promises", "data"),
    Input("fetched-data", "data"),
)

if __name__ == "__main__":
    app.run_server(debug=True)

promise


Added support for creating Dash components with TypeScript

TypeScript is a programming language that is a strict syntactical superset of JavaScript and adds optional static typing to the language. It transpiles to JavaScript.

Dash components can now be generated from React components written in TypeScript. This includes generating the Python API form TypeScript prop types. More on the discussion in PR #1956.

  • See the docs for an example of how TypeScript is used to define props for an Audio component.
  • See this repo to get started with creating Dash components with TypeScript.

Minor Ticks & Gridlines

Cartesian axes now support minor ticks and gridlines:

import plotly.express as px
from dash import Dash, html, dcc

df = px.data.tips()
fig = px.scatter(df, x="total_bill", y="tip", color="sex")
fig.update_xaxes(minor_ticks="outside", minor_showgrid=True)
fig.update_yaxes(minor_ticks="outside", minor_showgrid=True)

app = Dash(__name__)
app.layout = html.Div([
    dcc.Graph(figure=fig)
])

if __name__=='__main__':
    app.run()

This lets you do some interesting things, for example having major ticks/grids on month boundaries and minor ones on week boundaries (the example below also shows off our new griddash parameter for dashed gridlines) and this looks really great with the tick labels set to period mode so that the month labels below are centered on the month they denote:

from dash import Dash, html, dcc
import plotly.express as px
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')
df = df.loc[(df["Date"] >= "2016-07-01") & (df["Date"] <= "2016-12-01")]

fig = px.line(df, x='Date', y='AAPL.High')
fig.update_xaxes(ticks= "outside",  ticklabelmode= "period", 
                 tickcolor= "black",  ticklen=10, 
                 minor=dict(ticklen=4, 
                            dtick=7*24*60*60*1000,  
                            tick0="2016-07-03", 
                            griddash='dot', 
                            gridcolor='white')
                )

app = Dash(__name__)
app.layout = html.Div([dcc.Graph(figure=fig)])

if __name__=='__main__':
    app.run()

This feature requires Plotly 5.8.0

This feature was anonymously sponsored, so thanks to our sponsor :heart:!


app.run

Renamed app.run_server to the simpler app.run. We preserved app.run_server for backwards compatibility.


Notable Bug Fixes

  • #2046 - Fix bug #2045, in which an an import error occurs with Dash v2.4.0 when using pytest but dash[testing] is not installed.

  • #2029 - Restrict the number of props listed explicitly in generated component constructors - default is 250. This prevents exceeding the Python 3.6 limit of 255 arguments. The omitted props are still in the docstring and can still be provided the same as before, they just won’t appear in the signature so autocompletion may be affected.

  • #1968 - Fix bug #1877, in which code that uses merge_duplicate_headers and style_header_conditional to highlight DataTable columns incorrectly highlights header cells.

  • #2015 - Fix bug #1854 in which the combination of row_selectable=“single or multi” and filter_action=“native” causes a JS error.

  • #1976 - Fix bug #1962 in which DatePickerSingle and DatePickerRange are extremely slow when provided a long list of disabled_days.

  • #2035 - Fix bug #2033 in which the in-app error reporting shows HTML code instead of rendering the HTML.

  • #1970 dcc.Dropdown Refactor fixes:

    • Fix bug #1868, in which value does not update when selected option removed from options.
    • Fix bug #1908, in which selected options don’t show when the value contains a comma.

Minor Changes

  • #1839 - The callback decorator returns the original function, not the wrapped function, so that you can still call these functions directly, for example in tests. Note that in this case there will be no callback context so not all callbacks can be tested this way.

  • #2027 - Improve the error message when a user doesn’t wrap children in a list.

  • Plus lots of maintenance updates!


Previous Releases:

:mega: Dash 2.3.0 Release - MathJax and fillpattern option in scatter trace
:mega: Dash 2.2.0 Release - Adds ticklabelstep to axes, and added dash.get_asset_url
:mega: Dash 2.1.0 Release - Autogenerated IDs and reärranged keyword arguments in Dash components
:mega: Dash 2.0 Prerelease Candidate Available!
:mega: Dash v1.21.0 Release - Plotly.js 2.0, Icicle Charts, Bar Chart Patterns, Clipboard Component, Bug Fixes

18 Likes