在 Dash 中为悬停跟踪图像创建客户端回调(修订版)

Creating client-side callbacks for hover tracking images in Dash (revised)

提问人:acciolurker 提问时间:9/3/2021 最后编辑:acciolurker 更新时间:11/6/2021 访问量:770

问:

我的最后一个问题,发布在这里可能太耗时而无法回答(并且没有得到答案),所以这是对这个问题的重大修订。我现在 app.py 的是:

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
    dcc.Store(id='blank-output')
])



app.clientside_callback(
    ClientsideFunction(namespace="clientside", function_name="collect_hoverdata"),
    Output('blank-output', 'data'),
    Input('ss-img', 'hoverData')
)


app.clientside_callback(
    """
    function(nclicks) {
        to_return = img_hover_data.slice();
        img_hover_data = [];
        return to_return;
    }
    """,
    Output("hoverdata", "data"),
    Input('next-button', 'n_clicks')
)



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

        # Add hoverdata and screenshot index to mysql database (code not shown)
    print(len(hoverdata))
    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)



然后在 assets/hovertracker.js 中,我添加了这个函数:

var img_hover_data = [];

window.dash_clientside = Object.assign({}, window.dash_clientside, {
    clientside: {
        collect_hoverdata: function(hover_point) {
            var points_array = hover_point.points;
            var points = alert(points_array[0])
            var x = points.x;
            var y = points.y;
            var time = Date.now();
            img_hover_data.push([x, y, time]);
            return 0;
        }
    }
})

这里的想法是,实际的图形仍然是用plotly express创建的,然后每当它悬停在上面时,它就会在内部积累悬停数据客户端(客户端全局变量),直到按下按钮,此时它将用一个新图形刷新页面(通过正常回调完成),并且值将再次为空列表(客户端回调)。img_hover_datanextimg_hover_data

在执行此操作时,每当图像悬停在上面时,我都会收到错误:

Cannot read properties of undefined (reading 'apply')

我的新问题(希望更简单)是:为什么我会收到这个错误,我该如何解决它?

javascript python plotly-dash 客户端

评论

0赞 Luke Allpress 10/19/2021
我也收到了这个错误,对我来说,解决方案是定义 js 函数,而不仅仅是原始 js 代码(我只是试图记录到控制台)。Obv 这似乎不是你的问题,但我想我会分享。不过,一个想法是:既然您以两种不同的方式运行两个客户端回调,您是否确定哪个引发了错误?

答:

0赞 José Luiz Ferreira 11/6/2021 #1

我刚刚找到了你的第一个问题并试了一下,所以很抱歉给你发了两个答案。

您收到的错误很可能与第二个客户端回调有关。如果您打算在 Dash 回调中操作它,那么在全局范围内定义它有点危险,因此一个快速的解决方法是将其作为组件数据并将其传递给两个客户端回调。这样做不会有任何性能成本,因为在这两种情况下,数据都将在客户端。img_hover_datadcc.Store

关于第一次回调的两点观察:

  1. 达世币会在应用启动时评估回调,因此无论您是阻止回调执行(使用 ),还是必须处理hover_point。防止在客户端进行更新的方法是通过(更多信息在这里)。prevent_initial_update=Trueundefineddash_clientside.no_updates

  2. 在这一部分中:

var points_array = hover_point.points;
var points = alert(points_array[0])
var x = points.x;
var y = points.y;

将引发另一个异常,因为 is undefined ( 返回 ),因此您想将其更改为 和 相同。pointsalertundefinedvar x = points_array[0].xy