Tracking the current frame in an animated plotly figure (example code included!)

This was previously asked by someone else here: How to Get Current Frame in an Animated Plot, but wasn’t answered, and I wanted to make another attempt for this year.

Basically, I have a plotly animation which uses a slider and pause/play buttons to go through a dataset. I want to extract the number of the current frame (i.e., the current index in the ‘steps’/‘frames’ lists which the slider is on) in a Dash callback, so that I can update a table based on the main graph.

For example, in this situation:

I would like to be able to get ‘6’, the current step number, from the figure.

Here is some example code with a toy dataset, but the same basic UI and structure (from above):

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)] 

layout = go.Layout(width=650,
                height=650,
                showlegend=False,
                hovermode='closest',
                updatemenus=[dict(type='buttons', showactive=False,
                                y=-.1,
                                x=0,
                                xanchor='left',
                                yanchor='top',
                                pad=dict(t=0, r=10),
                                buttons=[dict(label='Play',
                                            method='animate',
                                            args=[None, 
                                                    dict(frame=dict(duration=200, redraw=False), 
                                                        transition=dict(duration=0),
                                                        fromcurrent=True,
                                                        mode='immediate'
                                                                )
                                            ]),
                                        dict(label='Pause', # https://github.com/plotly/plotly.js/issues/1221 / https://plotly.com/python/animations/#adding-control-buttons-to-animations
                                            method='animate',
                                            args=[[None],
                                                    dict(frame=dict(duration=0, redraw=False), 
                                                        transition=dict(duration=0),
                                                        fromcurrent=True,
                                                        mode='immediate' )
                                            ])
                                        ])
                            ])

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

# 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)

Basically, in that callback, I just want to get the current index of the slider, i.e. the current frame that the animation is on in order to index into the dataframe and display that info in a table. I just want to be able to access the ‘Steps’ that is displaying above the slider, the current value. It clearly exists somewhere, but I can’t find it for the life of me (tried going through the Github source code, but couldn’t locate it).

I would really appreciate any help with this! My dataset is fairly large (20 mb) and doesn’t fit into browser memory, so I haven’t had much luck with a Dash solution using dcc.Slider and dcc.Graph that is still performant.

Cross-posting an answer I got on Stackoverflow: Is there a way to extract the current frame from a plotly figure? - Stack Overflow

(Big thank you for the answer there!)

Basically, it looks like you can’t trigger a callback on an animation frame change, nor can you extract the current frame (as far as I can tell!). It seems like the only way to do this is with Dash and a slider, so I guess I’ll switch to trying to find the most efficient way to do that.

Hopefully this will be helpful if other people find it as well!