#javascript #python #plotly-dash #client-side
Вопрос:
Я создал приложение Dash, которое отслеживает и хранит данные о наведении изображений (см. Код ниже). Я хотел бы превратить часть отслеживания наведения в обратный вызов на стороне клиента, чтобы сделать приложение более масштабируемым и повысить производительность отслеживания наведения. Ниже приведен мой код, использующий обычный обратный вызов. Как я могу превратить эту функцию в обратный вызов на стороне клиента?
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
компонента. - Вы не сможете отправлять данные в базу данных при обратном вызове на стороне клиента. Это упомянуто в пункте 3 в самом низу документов Dash. Я думаю, вы могли бы это сделать, если бы могли подключиться к БД и отправлять данные в 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.