有没有一种方法可以从Plotly图中提取当前帧?

3
基本上,我有一个使用滑块和暂停/播放按钮浏览数据集的 Plotly 动画。我想在 Dash 回调中提取当前帧的编号(即滑块所处的“步骤”/“帧”列表中的当前索引),以便我可以根据主图更新表格。
例如,在这种情况下: 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解决方案方面并没有取得太大的成功。

我不确定我是否理解你的问题,但我认为帧数是在步骤中显示的变量'k'。 - r-beginners
你好!你是对的,但我想做的是从字典中获取它的当前值,但我还没有弄清楚。基本上,我想找出当它通过循环迭代时索引 k 在哪里。也许我会再试一次去寻找它,但似乎在设置后它可能是无法访问的。 - David Ryan
1个回答

3
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")

谢谢,非常有帮助的伪代码和非常清晰的解释。没有 Plotly 回调的缺点真是遗憾!另外,您有没有任何策略可以通过回调使动画更加流畅,即平稳地从一个帧过渡到下一个,就像使用 Plotly 一样?我尝试制作了一个基本版本来测试它,并注意到动画要颠簸得多。 - David Ryan
另外,我没有足够的声望来给你点赞,但是我非常感谢你的帮助!另外,这种设置方式比我之前见过的要好得多;使用框架的方式非常合理。 - David Ryan
这不是伪代码,而是实际可运行的代码 :-)。我实际上不喜欢这一行代码 tfig = go.Figure(fig.frames[frame].data, frames=fig.frames, layout=fig.layout)。创建一个新的图表(figure)是很耗费资源的。我找不到使用update_traces()或者update_layout()的方法。 - Rob Raymond
你可以接受这个答案。我回答过一些关于Plotly的问题,我认为Plotly的文档非常好。然而,代码示例对于Python、JavaScript和R来说是语言无关的。因此,它们并没有充分利用Python的语言特性。 - Rob Raymond
忘记了如何接受答案,哎呀!谢谢你,这真的是一个很好的例子。我会记住创建图形是昂贵的,并尝试使用update_traces/layout来找出一个版本。希望无论如何它仍然足够高效。Plotly的文档确实非常扎实,但我也发现像你刚分享的这个社区示例对我最有帮助! - David Ryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接