有没有办法从绘图图中提取当前帧?

Is there a way to extract the current frame from a plotly figure?

基本上,我有一个情节动画,它使用滑块和 pause/play 按钮来浏览数据集。我想在 Dash 回调中提取当前帧的编号(即滑块所在的“步骤”/“帧”列表中的当前索引),以便我可以更新 table 基于主图。

例如,在这种情况下:

Dash app with slider

我希望能够从图中得到‘6’,即当前步数。

这是一些带有玩具数据集的示例代码,但基本 UI 和结构相同(从上面看,减去按钮以减少代码块的长度):

import pandas as pd
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objects as go


external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# Dataset
x = [10, 1, 3, 4, 5, 6, 7, 8, 9, 10]
y = [10, 1, 3, 4, 5, 6, 7, 8, 9, 10]
df = pd.DataFrame(list(zip(x, y)), columns = ['x', 'y'])

# Adding a trace
trace = go.Scatter(x=df.x[0:2], y=df.y[0:2],
                            name='Location',
                            mode='markers',
                            marker=dict(color="white", 
                                        size=10,
                                        line=dict(
                                        color='DarkSlateGrey',
                                        width=2)
                                       )
                            )

# Adding frames
frames = [dict(name=k,data= [dict(type='scatter',
                           x=df.x[k:k + 1],
                           y=df.y[k:k + 1],
                            ),
                        ],
               traces = [0], 
              ) for k  in  range(len(df) - 1)] 

fig = go.Figure(data=[trace], frames=frames)

# Adding a slider
sliders = [{
        'yanchor': 'top',
        'xanchor': 'left', 
        'active': 1,
        'currentvalue': {'font': {'size': 16}, 'prefix': 'Steps: ', 'visible': True, 'xanchor': 'right'},
        'transition': {'duration': 200, 'easing': 'linear'},
        'pad': {'b': 10, 't': 50}, 
        'len': 0.9, 'x': 0.15, 'y': 0, 
        'steps': [{'args': [[k], {'frame': {'duration': 200, 'easing': 'linear', 'redraw': False},
                                    'transition': {'duration': 0, 'easing': 'linear'}}], 
                    'label': k, 'method': 'animate'} for k in range(len(df) - 1)       
                ]}]

fig['layout'].update(sliders=sliders)

app.layout = html.Div(children=[
                    html.Div([
                        dcc.Graph(
                            id= 'my-graph',
                            figure=fig
                        ),
                        html.Br(),
                        html.Div(id='my-output'),
                    ])
            ])

@app.callback(
    Output(component_id='my-output', component_property='children'),
    Input(component_id='my-graph', component_property='figure')
)

# How to get the current frame index here?
def update_output_div(figure):
    return 'Output: {}'.format(figure['layout']['sliders'][0])

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

基本上,在那个回调中,我只想获取滑块的当前索引,即动画所在的当前帧。它由滑块上方的“步骤”标签显示,因此它显然存在于某处,但我终生找不到它(尝试查看 Github 源代码,但找不到它) .

如果能提供帮助,我将不胜感激!我的数据集相当大(20 mb)并且不适合浏览器内存,所以我对使用 dcc.Slider 和 dcc.Graph 的 Dash 解决方案运气不太好,它仍然是高性能的。

import plotly.graph_objects as go
import numpy as np
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

# construct a figure with frames
frames=[go.Frame(name=n, data=go.Scatter(y=np.random.uniform(1, 5, 50)))
        for n in range(8)]
fig = go.Figure(data=frames[0].data, frames=frames)
# fig = fig.update_layout(
#     updatemenus=[{"buttons": [{"args": [None, {"frame": {"duration": 500, "redraw": True}}],
#                                "label": "▶",
#                                "method": "animate",},],
#                   "type": "buttons",}],
#     sliders=[{"steps": [{"args": [[f.name],{"frame": {"duration": 0, "redraw": True}, "mode": "immediate",},],
#                          "label": f.name, "method": "animate",}
#                         for f in frames],
#              }],)


# Build App
app = JupyterDash(__name__)
app.layout = html.Div(
    [dcc.Graph(id="graph", figure=fig), 
     html.Button("Play", id="dashPlay", n_clicks=0),
     dcc.Slider(id="dashSlider", min=0, max=len(frames)-1, value=0, marks={i:{"label":str(i)} for i in range(len(frames))}),
     dcc.Interval(id="animateInterval", interval=400, n_intervals=0, disabled=True),
     html.Div(id="whichframe", children=[]),
    ],
)

# core update of figure on change of dash slider    
@app.callback(
    Output("whichframe", "children"),
    Output("graph", "figure"),
    Input("dashSlider", "value"),
)
def setFrame(frame):
    if frame:
        tfig = go.Figure(fig.frames[frame].data, frames=fig.frames, layout=fig.layout)
        try:
            tfig.layout['sliders'][0]['active'] = frame
        except IndexError:
            pass
        return frame, tfig
    else:
        return 0, fig

# start / stop Interval to move through frames
@app.callback(
    Output("animateInterval","disabled"),
    Input("dashPlay", "n_clicks"),
    State("animateInterval","disabled"),
)
def play(n_clicks, disabled):
    return not disabled
    
@app.callback(
    Output("dashSlider", "value"),
    Input("animateInterval", "n_intervals"),
    State("dashSlider", "value")
)
def doAnimate(i, frame):
    if frame < (len(frames)-1): 
        frame += 1
    else:
        frame = 0
    return frame

# Run app and display result inline in the notebook
app.run_server(mode="inline")