×

注意!页面内容来自https://dash.plotly.com/clientside-callbacks,本站不储存任何内容,为了更好的阅读体验进行在线解析,若有广告出现,请及时反馈。若您觉得侵犯了您的利益,请通知我们进行删除,然后访问 原网页

Clientside Callbacks

To get the most out of this pagemake sure you’ve read about Basic Callbacks in the Dash Fundamentals.

Sometimes callbacks can incur a significant overheadespecially when they:
- receive and/or return very large quantities of data (transfer time)
- are called very often (network latencyqueuinghandshake)
- are part of a callback chain that requires multiple roundtrips between the browser and Dash

When the overhead cost of a callback becomes too great and no other optimization is possiblethe callback can be modified to be run
directly in the browser instead of a making a request to Dash.

The syntax for the callback is almost exactly the same; you use
Input and Output as you normally would when declaring a callback,
but you also define a JavaScript function as the first argument to the
@callback decorator.

For examplethe following callback:


@callback(
    Output('out-component''value'),
    Input('in-component1''value'),
    Input('in-component2''value')
)
def large_params_function(largeValue1largeValue2):
    largeValueOutput = someTransform(largeValue1largeValue2)

    return largeValueOutput

Can be rewritten to use JavaScript like so:

from dash import clientside_callbackInputOutput

clientside_callback(
    """
    function(largeValue1largeValue2) {
        return someTransform(largeValue1largeValue2);
    }
    """,
    Output('out-component''value'),
    Input('in-component1''value'),
    Input('in-component2''value')
)


You also have the option of defining the function in a . file in
your assets/ folder. To achieve the same result as the code above,
the contents of the . file would look like this:

window.dash_clientside = Object.assign({}window.dash_clientside{
    clientside: {
        large_params_function: function(largeValue1largeValue2) {
            return someTransform(largeValue1largeValue2);
        }
    }
});

In Dashthe callback would now be written as:

from dash import clientside_callbackClientsideFunctionInputOutput

clientside_callback(
    ClientsideFunction(
        namespace='clientside',
        function_name='large_params_function'
    ),
    Output('out-component''value'),
    Input('in-component1''value'),
    Input('in-component2''value')
)

A Simple Example

Below are two examples of using clientside callbacks to update a
graph in conjunction with a dcc.Store component. In these examples,
we update a dcc.Store
component on the backend; to create and display the graphwe have a clientside callback in the
frontend that adds some extra information about the layout that we
specify using the radio buttons under “Graph scale”.

from dash import DashdcchtmlInputOutputcallbackclientside_callback
import pandas as pd

import on

external_sheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__external_sheets=external_sheets)

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')

available_countries = df['country'].unique()

app.layout = html.Div([
    dcc.Graph(
        id='clientside-graph'
    ),
    dcc.Store(
        id='clientside-figure-store',
        data=[{
            'x': df[df['country'] == 'Canada']['year'],
            'y': df[df['country'] == 'Canada']['pop']
        }]
    ),
    'Indicator',
    dcc.Dropdown(
        {'pop' : 'Population''lifeExp': 'Life Expectancy''gdpPercap': 'GDP per Capita'},
        'pop',
        id='clientside-graph-indicator'
    ),
    'Country',
    dcc.Dropdown(available_countries'Canada'id='clientside-graph-country'),
    'Graph scale',
    dcc.RadioItems(
        ['linear''log'],
        'linear',
        id='clientside-graph-scale'
    ),
    html.Hr(),
    html.Details([
        html.Summary('Contents of figure storage'),
        dcc.Markdown(
            id='clientside-figure-on'
        )
    ])
])


@callback(
    Output('clientside-figure-store''data'),
    Input('clientside-graph-indicator''value'),
    Input('clientside-graph-country''value')
)
def update_store_data(indicatorcountry):
    dff = df[df['country'] == country]
    return [{
        'x': dff['year'],
        'y': dff[indicator],
        'mode': 'markers'
    }]


clientside_callback(
    """
    function(datascale) {
        return {
            'data': data,
            'layout': {
                 'yaxis': {'type': scale}
             }
        }
    }
    """,
    Output('clientside-graph''figure'),
    Input('clientside-figure-store''data'),
    Input('clientside-graph-scale''value')
)


@callback(
    Output('clientside-figure-on''children'),
    Input('clientside-figure-store''data')
)
def generated_figure_on(data):
    return '```\n'+on.dumps(dataindent=2)+'\n```'


if __name__ == '__main__':
    app.run(debug=True)
Indicator
Country
Graph scale

Contents of figure storage

None

Note thatin this examplewe are manually creating the figure
dictionary by extracting the relevant data from the
dataframe. This is what gets stored in our
dcc.Store component;
expand the “Contents of figure storage” above to see exactly what
is used to construct the graph.

Using Plotly Express to Generate a Figure

Plotly Express enables you to create one-line declarations of
figures. When you create a graph withfor example,
plotly_express.Scatteryou get a dictionary as a return
value. This dictionary is in the same shape as the figure
argument to a dcc.Graph component. (See
here for
more information about the shape of figures.)

We can rework the example above to use Plotly Express.

from dash import DashdcchtmlInputOutputcallbackclientside_callback
import pandas as pd
import on

import plotly.express as px

external_sheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = Dash(__name__external_sheets=external_sheets)

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')

available_countries = df['country'].unique()

app.layout = html.Div([
    dcc.Graph(
        id='clientside-graph-px'
    ),
    dcc.Store(
        id='clientside-figure-store-px'
    ),
    'Indicator',
    dcc.Dropdown(
        {'pop' : 'Population''lifeExp': 'Life Expectancy''gdpPercap': 'GDP per Capita'},
        'pop',
        id='clientside-graph-indicator-px'
    ),
    'Country',
    dcc.Dropdown(available_countries'Canada'id='clientside-graph-country-px'),
    'Graph scale',
    dcc.RadioItems(
        ['linear''log'],
        'linear',
        id='clientside-graph-scale-px'
    ),
    html.Hr(),
    html.Details([
        html.Summary('Contents of figure storage'),
        dcc.Markdown(
            id='clientside-figure-on-px'
        )
    ])
])


@callback(
    Output('clientside-figure-store-px''data'),
    Input('clientside-graph-indicator-px''value'),
    Input('clientside-graph-country-px''value')
)
def update_store_data(indicatorcountry):
    dff = df[df['country'] == country]
    return px.scatter(dffx='year'y=str(indicator))


clientside_callback(
    """
    function(figurescale) {
        if(figure === undefined) {
            return {'data': []'layout': {}};
        }
        const fig = Object.assign({}figure{
            'layout': {
                ...figure.layout,
                'yaxis': {
                    ...figure.layout.yaxistype: scale
                }
             }
        });
        return fig;
    }
    """,
    Output('clientside-graph-px''figure'),
    Input('clientside-figure-store-px''data'),
    Input('clientside-graph-scale-px''value')
)


@callback(
    Output('clientside-figure-on-px''children'),
    Input('clientside-figure-store-px''data')
)
def generated_px_figure_on(data):
    return '```\n'+on.dumps(dataindent=2)+'\n```'


if __name__ == '__main__':
    app.run(debug=True)
Indicator
Country
Graph scale

Contents of figure storage

None

Againyou can expand the “Contents of figure storage” section
above to see what gets generated. You may notice that this is
quite a bit more extensive than the previous example; in
particulara layout is already defined. Soinstead of creating
a layout as we did previouslywe have to mutate the existing
layout in our JavaScript code.

Clientside Callbacks with Promises

Dash 2.4 and later supports clientside callbacks that return promises.

Fetching Data Example

In this examplewe fetch data (based on the value of the dropdown) using an async clientside callback function that outputs it to a dash_table.DataTable component.

from dash import DashdcchtmlInputOutputdash_tableclientside_callback

app = Dash()

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

clientside_callback(
    """
    async function(value) {
    const response = await fetch(value);
    const data = await response.on();
    return data;
    }
    """,
    Output("my-table-promises""data"),
    Input("data-select""value"),
)

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

Notifications Example

This example uses promises and sends desktop notifications to the user once they grant permission and select the Notify button:

from dash import DashdcchtmlInputOutputclientside_callback

app = Dash()

app.layout = html.Div(
    [
        dcc.Store(id="notification-permission"),
        html.Button("Notify"id="notify-btn"),
        html.Div(id="notification-output"),
    ]
)


clientside_callback(
    """
    function() {
        return navigator.permissions.query({name:'notifications'})
    }
    """,
    Output("notification-permission""data"),
    Input("notify-btn""n_clicks"),
    prevent_initial_call=True,
)

clientside_callback(
    """
    function(result) {
        if (result.state == 'granted') {
            new Notification("Dash notification"{ body: "Notification already granted!"});
            return null;
        } else if (result.state == 'prompt') {
            return new Promise((resolvereject) => {
                Notification.requestPermission().then(res => {
                    if (res == 'granted') {
                        new Notification("Dash notification"{ body: "Notification granted!"});
                        resolve();
                    } else {
                        reject(`Permission not granted: ${res}`)
                    }
                })
            });
        } else {
            return result.state;
        }
    }
    """,
    Output("notification-output""children"),
    Input("notification-permission""data"),
    prevent_initial_call=True,
)

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

Notification with promises


Callback Context

You can use dash_clientside.callback_context.triggered_id within a clientside callback to access the ID of the component that triggered the callback.

In this examplewe display the triggered_id in the app when a button is clicked.

from dash import DashhtmlInputOutput

app = Dash(prevent_initial_callbacks=True)

app.layout = html.Div(
    [
        html.Button("Button 1"id="btn1"),
        html.Button("Button 2"id="btn2"),
        html.Button("Button 3"id="btn3"),
        html.Div(id="log"),
    ]
)

app.clientside_callback(
    """
    function(){
        console.log(dash_clientside.callback_context);
        const triggered_id = dash_clientside.callback_context.triggered_id;
        return "triggered id: " + triggered_id
    }
    """,
    Output("log""children"),
    Input("btn1""n_clicks"),
    Input("btn2""n_clicks"),
    Input("btn3""n_clicks"),
)

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

Partial Property Updates

New in Dash 3.3

Use dash_clientside.Patch on clientside callbacks to make partial updates to a property on a component. In the following exampleclicking the button toggles the legend visibility using a partial update to the callback’s output (in this casea figure on the dcc.Graph component).

from dash import DashhtmldccInputOutputclientside_callback

app = Dash()

app.layout = html.Div([
    html.Button("Toggle Legend"id="legend-toggle"n_clicks=0),
    dcc.Graph(
        id="graph-clientside-example",
        figure={
            "data": [
                {"x": [123]"y": [243]"type": "bar""name": "Series 1"},
                {"x": [123]"y": [314]"type": "bar""name": "Series 2"}
            ],
            "layout": {"showlegend": True}
        }
    )
])

clientside_callback(
    """
    function(n_clicks) {
        var patch = new dash_clientside.Patch;
        // Toggle between true and false: even clicks show legendodd clicks hide it
        var showLegend = (n_clicks % 2 === 0);
        return patch.assign(['layout''showlegend']showLegend).build();
    }
    """,
    Output("graph-clientside-example""figure"),
    Input("legend-toggle""n_clicks")
)

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

Notes on this example

In this example:

  • new dash_clientside.Patch creates a new patch object.
  • .assign(['layout''showlegend']showLegend) sets figure.layout.showlegend to the new value. Here the first argument to assign['layout''showlegend']is the path to updatewhile the second valueshowLegendis the value to update it to: true for even clicksfalsefor odd clicks.
  • Calls .build() to finalize the patch object to be returned.

Clientside Patch supports the following operations:

  • .assign(pathvalue) - Assign a new value to a property
  • .append(pathvalue) - Append a value to an array
  • .prepend(pathvalue) - Prepend a value to an array
  • .extend(pathvalues) - Extend an array with multiple values
  • .insert(pathindexvalue) - Insert a value at a specific index in an array
  • .merge(pathobject) - Merge an object with existing data
  • .delete(path) - Delete a property or array item
  • .clear(path) - Clear an array
  • .reverse(path) - Reverse an array
  • .remove(pathvalue) - Remove a specific value from an array
  • .add(pathvalue) - Add to a numeric value
  • .sub(pathvalue) - Subtract from a numeric value
  • .mul(pathvalue) - Multiply a numeric value
  • .div(pathvalue) - Divide a numeric value

Set Props

New in 2.16

dash_clientside.set_props allows you to update a Dash component property directly instead of updating it by having it as an output of a clientside callback. This can be useful if you have a non-Dash component (for examplea custom JavaScript component) that you want to update a Dash component property fromor if you want to implement custom functionality that is not available directly within Dash but that interacts with Dash.

For an example of using set_props with a custom JavaScript componentgo to the community-driven Dash Example Index.

The following example adds an event listener to the page. This event listener responds to the user pressing <kbd>Ctrl<kbd>+<kbd>R<kbd> by updating a dcc.Store component’s data. Another callback has the dcc.Store component’s data property as an input so runs each time it changesoutputting the updated data to an html.Div component.

from dash import DashhtmldccInputOutput

app = Dash()

app.layout = html.Div(
    [
        html.Span(
            [
                "Press ",
                html.Kbd("Ctrl"),
                " + ",
                html.Kbd("R"),
                " to refresh the app's data",
            ]
        ),
        dcc.Store(id="store-events"data={}),
        html.Div(id="container-events"),
    ],
    id="document",
)


app.clientside_callback(
    """
    function () {
        document.addEventListener('keydown'function(e) {

            if (e.ctrlKey && e.keyCode == 82) {
                // Simulate getting new data
                newData = JSON.stringify(new Date())

                // Update dcc.Store with ID store-events
                dash_clientside.set_props("store-events"{data: newData})

                event.preventDefault()
                event.stopPropagation()
                return dash_clientside.no_update;
            }
        });
        return dash_clientside.no_update;
    }
    """,
    Output('document''id'),
    Input('document''id'),
)


@app.callback(
    Output('container-events''children'),
    Input('store-events''data'),
    prevent_initial_call=True
)
def handle_key_press(data):
    return f"Current data value: {data}"


if __name__ == '__main__':
    app.run(debug=True)
Press Ctrl + R to refresh the app's data

Notes about this example

  • dash_clientside.set_props takes two arguments. The first is the ID of the Dash component to update. The second is an object with the name of the property to update as a keyand the value as the new value to update that property to. In this example dash_clientside.set_props("store-events"{data: newData}) updates the data property of the Dash component with ID store-eventswith a new value of newDatawhich here is a variable that contains a string representation of the current date and time.
  • The clientside callback returns dash_clientside.no_updatemeaning it doesn’t update any Dash component specified as an Output. The only update that happens to the page from the clientside callback is via dash_clientside.set_props.
  • The Input for the callback that adds the event listener is the ID of the app’s main container html.Div. We use the id property as this won’t change after our app loadsmeaning this clientside callback only runs when the app loads.
  • In this examplethe Output is the same as the Inputbut it could be anything because we don’t update the Output.

Limitations

There are a few limitations to keep in mind:

  1. Clientside callbacks execute on the browser’s main thread and will block
    rendering and events processing while being executed.
  2. Clientside callbacks are not possible if you need to refer to global
    variables on the server or a DB call is required.
  3. Dash versions prior to 2.4.0 do not support asynchronous clientside callbacks and will
    fail if a Promise is returned.