在Dash中创建用于悬停跟踪的客户端回调

我已经创建了一个Dash应用程序来跟踪和存储图像的悬停数据(参见下面的代码)。我想把悬停跟踪部分变成客户端回调,让APP更具伸缩性,提高悬停跟踪的性能。下面是我使用普通回调的代码。如何将此函数转换为客户端回调?

import dash
import dash_core_components as dcc
import dash_html_components as html
import numpy as np
import plotly.express as px
import requests
from dash.dependencies import ClientsideFunction, Input, Output, State
from dash.exceptions import PreventUpdate
from PIL import Image


app = dash.Dash(__name__)
server =  app.server

# In reality, there are 50 screenshot images with non-sequential indexes
urls = ["https://pbs.twimg.com/media/EW8GhG_XkAEOyAh.jpg",
        "https://pbs.twimg.com/media/CqzwpPnWEAAiGjW.jpg"]

def url_to_fig(url):
    rgb_arr = np.array(Image.open(requests.get(url, stream=True).raw))
    fig = px.imshow(rgb_arr)
    fig.update_xaxes(visible=False)
    fig.update_yaxes(visible=False)
    fig.update_layout(
        dragmode=False, width=800, height=800)
    fig.update_traces(hoverinfo='none', hovertemplate=None)
    return fig

app.layout = html.Div([
    dcc.Store(id='ss-idx', data=0),
    dcc.Graph(id='ss-img', figure=url_to_fig(urls[0]), config = {"displayModeBar": False}),
    html.Button("Next", id='next-button', n_clicks=0),
    dcc.Store(id='hoverdata', data=[]), # Place to append new hoverdata
])


# Change to client side callback (in JavaScript)
@app.callback(
    [Output('ss-idx', 'data'),
     Output('ss-img', 'figure'),
     Output('hoverdata', 'data')],
    [Input('ss-img', 'hoverData'),
     Input('next-button', 'n_clicks')],
    [State('hoverdata', 'data'),
     State('ss-idx', 'data')]
)
def add_to_hoverdata(hover_point, next_clicks, hoverdata, ss_idx):
    ctx = dash.callback_context
    if not ctx.triggered:
        raise PreventUpdate

    button_id = ctx.triggered[0]['prop_id'].split('.')[0]
    if 'ss-img' in button_id:
        if hover_point is not None:
            x = hover_point["points"][0]["x"]
            y = hover_point["points"][0]["y"]
            hoverdata.append((x, y))
            return dash.no_update, dash.no_update, hoverdata

    elif 'next-button' in button_id:
        # Add hoverdata and screenshot index to mysql database (code not shown)
        print(next_clicks)
        if next_clicks < len(urls):
            new_idx = ss_idx + 1
            return new_idx, url_to_fig(urls[new_idx]), [] # Reset hoverdata
        else:
            raise PreventUpdate


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


解决方案

尝试将其转换为客户端回调...但首先要注意到的两点是:

  1. 因为url是在服务器端定义的数组,所以必须通过dcc.Store组件添加到客户端。

  2. 您将无法在客户端回调中向数据库发送数据。这是在最下面of the Dash docs的第三点中提到的。如果您能够连接到数据库并以JS格式发送数据,我想您可以这样做,但这可能是一个非常糟糕的主意...

回调:

app.clientside_callback(
    # from dash.dependencies import ClientsideFunction
    ClientsideFunction(
        namespace="clientside",
        function="addToHoverData"
    ),
    [Output('ss-idx', 'data'),
     Output('ss-img', 'figure'),
     Output('hoverdata', 'data')],
    [Input('ss-img', 'hoverData'),
     Input('next-button', 'n_clicks')],
    [State('hoverdata', 'data'),
     State('ss-idx', 'data'),
     # you must create a Store with the url list
     State('url-list', 'data') 
    ]
)

assets/clientside.js

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        add_to_hoverdata: function(hoverData, nClicks, storeHoverData, storeIdx, storeURLs) {
            // storeURLs is an array that holds the image URLs
            const ctx = dash_clientside.callback_context;

            if (!("triggered" in ctx)){
                return dash_clientside.no_update
            }

            const triggered_id = ctx.triggered[0].prop_id.split(".")[0];

            if (triggered_id === "ss-img"){
                if (hoverData !== undefined){
                    // assumes storeHoverData is an array
                    storeHoverData.push(hoverData.points[0]);
                    return [
                        dash_clientside.no_update,
                        dash_clientside.no_update,
                        storeHoverData
                    ]
                }
            } else if (triggered_id === "next-button"){
                console.log(nClicks);

                if (nClicks < storeURLs.length){
                    return [
                        storeIdx +1,
                        storeURLs[storeIdx +1],
                        []
                    ]
                }
                else {
                    return dash_clientside.no_update
                }
            }
        }
    }
});

我尽量使js代码靠近您的python回调。

相关文章