Black Lives Matter. Please consider donating to Black Girls Code today.

Trigger callback on animation frame change

Hello, how are you?

I have the following situation, can someone help me?

In the left plot I have a figure with scatterplot as data and the red bar defined on layout.
Also in this figure I have many frames with different positions for the red bar.
I want to, when the bar position changes, update the heatmap on the right side.

This red bar in the left plot is defined on figure layout so if I move it with my mouse I can trigger a callback using “relayoutData” property but I wanna know if there is a way to trigger a callback when the bar is beeing moved by the plotly animate.
Basically the heatmap data must depend on scatter layout bar position.

Thanks!

1 Like

Hey @vinicvaz welcome to the Plotly forums!
I’m not sure if a callback can be triggered by an animation event but I think you’ll have an easier time using dcc.Interval to update the figure (https://dash.plotly.com/dash-core-components/interval). You can use the n_intervals property to compute the line position, and the heatmap you want to show. Say in the following example you’ve made a dcc.Graph(id='graph').

n_steps=1000
@app.callback(Output('graph', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_figure(n_intervals):
    fig=plotly.subplots.make_subplots(1,2)
    fig.add_trace(go.Scatter(
       #...the trace representing the sinusoid...
    ),row=1,col=1)
    fig.add_trace(go.Heatmap(
        #...describe the heatmap, probably it has to be computed from n_intervals
    ),row=1,col=2)
    fig.add_shape(dict(type="line",x0=(x_end-x_start)*(n_intervals/n_steps)+x_start,x1=(x_end-x_start)*(n_intervals/n_steps)+x_start,y0=0,y1=1,yref="paper"))
    return fig

If you want to update the figure, I think you can do

```python
@app.callback(Output('graph', 'figure'),
              [Input('interval-component', 'n_intervals')],
              [State("graph","figure")])
def update_figure(n_intervals,prev_fig):
    fig=go.Figure(**prev_fig)
    # .... update the figure using fig.update_layout and the like ...
    return fig

You’ll just need to initialize the figure keyword when you create dcc.Graph in the initial layout.

@nickest , correct me if I’m wrong, but using dcc.Interval will cause the data to be continuously sent from backend to frontend, which will hurt performance, right?

Animate solves this by having the data on frontend already, stored on frames.

Besides, how could you replicate the Play/Pause functionality with the dcc.Interval component?

You are right about sending from backend to frontend. Will it hurt performance? That depends on how much data you want to send, maybe if the heatmap you want to send has many points this will cause some lag. To implement Play/Pause you probably need a couple a dcc.Store components: one to hold the play/pause state, and the other to keep track of the frame you were on. In this case, you wouldn’t use the n_interval value but rather just increment the value in a store (by passing it in as a State variable and also having it as one of the outputs). The play/pause state store could be updated with a button or something and the frame number store only update if its contents are non-zero.
I agree if animations could trigger relayout callbacks, that would be the easiest solution!
Alternatively, if you just want to write Javascript, this could be done using window.setInterval. Here’s some code that just updates a line, which could easily be extended to update a heatmap trace:

<!DOCTYPE html>
<html>
<head>
<meta http-equiv='Content-Type' content='text/html:charset=utf-8' />
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id="tester"></div>
<script>
var TESTER = document.getElementById('tester');
var datas = [
    {
        type:"scatter",
        x:[1,2,3],
        y:[5,2,3]
    },
];
Plotly.newPlot(TESTER, datas);
var step=0;
var n_steps=1000;
var line_x_start=1;
var line_x_end=3;
window.setInterval(function () {
    Plotly.relayout(TESTER, {
        shapes:[{
            type:"line",
            x0: (line_x_end - line_x_start)*step/n_steps + line_x_start,
            x1: (line_x_end - line_x_start)*step/n_steps + line_x_start,
            y0: 0,
            y1: 1,
            yref: 'paper'
        }]
    });
    step = (step + 1) % n_steps;
},10);

</script>
</body>
</html>

@nickest this approach looks good.
A doubt about it, its possible to update a dcc.Graph object using your code without using Plotly.newPlot() ?

var TESTER = document.getElementById('my-graph');
Plotly.relayout(TESTER, {
        shapes:[{
            type:"line",
            x0: (line_x_end - line_x_start)*step/n_steps + line_x_start,
            x1: (line_x_end - line_x_start)*step/n_steps + line_x_start,
            y0: 0,
            y1: 1,
            yref: 'paper'
        }]
 });

Yes it is :slight_smile: The relayout and react functions just change what changed and don’t redraw the whole plot. You can learn more here: https://plotly.com/javascript/plotlyjs-function-reference/#plotlyrelayout

Thanks @nickest
I tried to used that code but I’m getting this error:

TypeError: Cannot read property '_guiEditing' of undefined

The element with id=‘tester’ is a dcc.Graph created in python code.
I’m using dcc.Interval() to call the javascript code to have control of when to leave the loop.
I can make it works creating a new plot for each iteration, but if I just try to get the graph element by the id and relayout it I get the above error.

Ah yes! I forgot to say that when you use dash, you just return a new plot and dash takes care of finding the difference between the new plot and the one already on the page, so you don’t need to use relayout, etc. It’s only if you’re writing pure Javascript that you would use relayout, etc.