📣 Dash 2.9.2 Released - Partial Property Updates with Patch(), Duplicate Outputs, dcc.Geolocation, Scatter Group Attributes and More

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 :rocket:

Register for the upcoming webinar to learn how to use the open-source feature.

pip install dash==2.9.2

Official Changelog :arrow_forward: Dash v2.9.2

Highlights :arrow_down_small:

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. The update 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)

table-add

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)

extendagGrid

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)

nodes

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)

colors

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)

annotate

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)

pattern

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 :slightly_smiling_face:

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)

allow-duplicate

When setting allow_duplicate=True on a callback output, you’ll need to either set prevent_initial_call=True on the callback, or set app = 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)

Geolocation running example

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 :arrow_forward: 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 :slightly_smiling_face:. 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! :heart_decoration:

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 multiple gunicorn 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 if inline is false in RadioItems & Checklist components. To keep previous behavior, set inline=True.
  • #2400 Added disable_n_clicks=True to the html.Div components in page_container. You can read more about it in the docs. Thank you @AnnMarieW :pray:
  • #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" in dcc.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 call dash._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 in to_html [#4114]
  • Added packaging to install requirements, fixing a No 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 and autoshift 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 and scattergap [#6381], this feature was anonymously sponsored: thank you to our sponsor!
  • Add marker.cornerradius attribute to treemap 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

:mega: Dash 2.7 Released - Directional Arrows Feature, Map Bounds, and DataTable Filter Text
:mega: Dash 2.6 Released - Background Callbacks, Unit Testing, Persistent Selections, Dropdown Features
:mega: Dash 2.5 Released - Easier Multi-Page Apps, Component Properties
:mega: Dash 2.4 Released - Improved Callback Context, Clientside Promises, Typescript Components, Minor Ticks
: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

26 Likes

Wow Duplicate Callback Outputs is potentially a game changer! Will test this out soon. Prior to this, handling everything within a single callback was very clunky.

6 Likes

Thanks
Very nice improvement
Please keep sharing and motivating us

4 Likes

Huge update! Congratulations, IMHO thi is the biggest game changer since introduction of pattern matching callbacks.

Maybe a stupid question regarding initial update. Wouldn’t it be possible to set something like order for initial loading instead of just preventing it?

2 Likes

+1 - Being able to set up an ordering / priority hierarchy would be awesome if possible.

1 Like

Thanks for these features!

1 Like

@martin2097
:wave: welcome to the community.

What do you mean? can you clarify or give an example of what you’d like to do?

1 Like

Duplicate callback output that’s what we want!!

1 Like

So right now, when you use allow_duplicate, you need to set prevent_initial_call = True or prevent_initial_call = "initial_duplicate" (I believe this is because of the possibility of race conditions that would result in the output of one callback being overwritten by another one that was fired at the same time and updating the same output. Instead, if it were somehow possible to store the responses and then apply the one with the highest priority when multiple are targeting the same output, that would be awesome (but admittedly probably wildly difficult to actually do).

1 Like

The duplicate callback output is such a gamechanger!
I had to use the MultiplexerTransform() extension from dash_extensions.enrich for the longest time and it produced so many roadblocks with later versions of Dash.
This is exactly what I needed. Thank you so much for offering it!

2 Likes

can you use patch in plotly-js, or is this already a feature that is just new to Dash? Ummm, asking for a friend.

1 Like

Hi Adam,
Thanks a lot. It is a huge improvement! Expecting a video tutorial on your youtube channel soon:).

For plotly.js, you have direct access to the data, so, yes, you can patch directly. And then pass the new elements to the plotly functions.

The benefit of the patch for Dash is the decrease of network traffic. :slight_smile:

1 Like

Hey @allsyntax
Patch() is a dash feature, so you can use it in dcc.graph to modify a plotly.js figure, but you can’t use it directly in plotly.js.

That said, there are lots of ways to incrementally modify a plotly.js figure.

If you’re in javascript, you can use Plotly.restyle and Plotly.relayout or you can directly modify the figure object and use Plotly.react.

if you’re in jupyter you can use the FigureWidget, that allows you to modify the figure object in Python and it’ll send only the necessary edits to the browser.

1 Like

Great updates and can’t wait to use.

I ran into an issue with a clientside callback that works in Dash 2.9.0 but not 2.9.2. I’m getting an error that says cannot read undefined properties ‘apply’. I can create a post on this, but not sure if there were breaking changes in 2.9.2.

Hello @aschutte,

Yes, there is a patch already in place for this, it should be included in the next release. :slight_smile:

1 Like

Thanks for the update! Not sure if we should reopen a bug report, but despite this Fix side effect on updating possible array children triggering callbacks that were not updated. by T4rk1n · Pull Request #2429 · plotly/dash · GitHub , the bug still exists in 2.9.2. I have tested multiple versions and the latest version to work was 2.7.0. Is this a known issue?

@jokin That fix will be released in 2.9.3

2 Likes

n callbacks → 1 output is so major, thanks

2 Likes

Thanks for welcome Adam!

What I meant is that now when I have 2 callbacks targeting same output i.e. one of them handling data and one of them handling style I have to prevent initial call at one of them because there is no way to set order in which they fire on initial load. My idea was that instead prevent initial call one would set initial order for each callback (i.e. 1 for data 2 for style) so there would be clear order in which the callbacks fire on page load. I hope it is clear what I mean from this description :slight_smile: