📣 Dash 1.19.0 - Circular Callbacks, `drag_value` in `dcc.Slider`, Close Button in Dev Tools, dcc.Graph Bug Fixes

We’re pleased to announce that Dash 1.19.0 is out :tada:

pip install dash==1.19.0

Dash 1.19.0 is backwards compatible feature release that:

  1. Adds support for circular callbacks that within the same callback. Thanks to @chadaeschliman for the contribution! :tada:
  2. Adds a drag_value property to dcc.Slider to fire callbacks from dragging and releasing.
  3. Adds a much needed close button to close the Dash Dev Tools. Many thanks to @AnnMarieW for the contribution :tada:
  4. Upgrades plotly.js, the graphing library behind dcc.Graph from 1.58.2 to 1.58.4.
  5. Dedents error messages more carefully.
  6. Various other bug fixes.

:arrow_right: Official Changelog

Circular Callbacks

This section is adapted from the Dash circular callback examples in the documentation.

These circular updates work within the same callback. Circular callback chains that involve multiple callbacks are not supported. Circular callbacks can be used to keep multiple inputs synchronized with each other.

Common examples include:

Synchronizing a Slider with an Input

import dash
from dash.dependencies import Input, Output
import dash_core_components as dcc
import dash_html_components as html

external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(
    [
        dcc.Slider(
            id="slider", min=0, max=20, 
            marks={i: str(i) for i in range(21)}, 
            value=3
        ),
        dcc.Input(
            id="input", type="number", min=0, max=20, value=3
        ),
    ]
)
@app.callback(
    Output("input", "value"),
    Output("slider", "value"),
    Input("input", "value"),
    Input("slider", "value"),
)
def callback(input_value, slider_value):
    ctx = dash.callback_context
    trigger_id = ctx.triggered[0]["prop_id"].split(".")[0]
    value = input_value if trigger_id == "input" else slider_value
    return value, value
app.run_server(debug=True)

Synchronizing Two Inputs with Different Units (Unit Conversion)

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    html.Div('Convert Temperature'),
    'Celsius',
    dcc.Input(
        id="celsius",
        value=0.0,
        type="number"
    ),
    ' = Fahrenheit',
    dcc.Input(
        id="fahrenheit",
        value=32.0,
        type="number",
    ),
])

@app.callback(
    Output("celsius", "value"),
    Output("fahrenheit", "value"),
    Input("celsius", "value"),
    Input("fahrenheit", "value"),
)
def sync_input(celsius, fahrenheit):
    ctx = dash.callback_context
    input_id = ctx.triggered[0]["prop_id"].split(".")[0]
    if input_id == "celsius":
        fahrenheit= None if celsius is None else (float(celsius) * 9/5) + 32
    else:
        celsius = None if fahrenheit is None else (float(fahrenheit) - 32) * 5/9
    return celsius, fahrenheit

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

Synchronizing Two Checklists Together

import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
options = [
    {"label": "New York City", "value": "NYC"},
    {"label": "Montréal", "value": "MTL"},
    {"label": "San Francisco", "value": "SF"},
]
all_cities = [option["value"] for option in options]
app.layout = html.Div(
    [
        dcc.Checklist(
            id="all-checklist",
            options=[{"label": "All", "value": "All"}],
            value=[],
            labelStyle={"display": "inline-block"},
        ),
        dcc.Checklist(
            id="city-checklist",
            options=options,
            value=[],
            labelStyle={"display": "inline-block"},
        ),
    ]
)
@app.callback(
    Output("city-checklist", "value"),
    Output("all-checklist", "value"),
    Input("city-checklist", "value"),
    Input("all-checklist", "value"),
)
def sync_checklists(cities_selected, all_selected):
    ctx = dash.callback_context
    input_id = ctx.triggered[0]["prop_id"].split(".")[0]
    if input_id == "city-checklist":
        all_selected = ["All"] if set(cities_selected) == set(all_cities) else []
    else:
        cities_selected = all_cities if all_selected else []
    return cities_selected, all_selected
if __name__ == "__main__":
    app.run_server(debug=True)

Previous Releases

13 Likes

Man all these updates are significant enhancements from the pre v1.0 days! Now time to go back to all my old apps and re-write my convoluted hacks and/or add the functionality I wish I could’ve at the time.

3 Likes

I love the new circular callbacks feature! :partying_face:

6 Likes

Congratulations for all this inprovements :tada: :tada: :tada:

3 Likes