pythonpython-3.xpandasplotly

Plotly Express with Animation Frame: Having Issues with Legend and Displaying Points


I am trying to make a movie showing the evolution of an estimator. The idea is to draw the history of the estimator after each update. I want to do this as a movie as some updates come in out of sequence and this has been the easiest way to display this.

The animation_frame option for scatter or line in ploty.express has worked well but when I try and color points, or use anything that makes a legend, I am running into issues.

  1. The legend only includes items from the first frame. This also leads to any color not on the first frame not drawing.
  2. If I make a "zeroeth" frame with all the data to get the legend correct a color is only updated correctly sometimes. For example when a frame updates a blue point all blue points are correct but red points are not updated, even if they should change.

Here is a code snippet showing the first problem. It is a simple scatter of some y=x points. Each frame has one new point compared to the last and points above (3, 3) are red. Doing this in what I think is the simplest way leads to red points not being displayed in the legend or on the graph.

import numpy as np
import pandas as pd
import plotly.express as px

# Make a y=x line that grows from origin (0, 0), to (9, 9)
x = [val for stop in range(10) for val in np.arange(0, stop, 1)]
frame_id = [stop for stop in range(10) for _ in np.arange(0, stop, 1)]
y = x
color = ['blue' if val < 3 else 'red' for val in x]


simple_df = pd.DataFrame().assign(
    x=x,
    y=y,
    color=color,
    frame_id=frame_id,
).sort_values(by='frame_id')

px.scatter(
    simple_df, x='x', y='y', color='color', animation_frame='frame_id'
).update_layout(
    title='Animation of y=x line growing from (0, 0) to (9, 9)',
).update_yaxes(range=[-1, 10]).update_xaxes(range=[-1, 10]).show()

I have sort of fixed this by adding a starting frame with all points displayed. I have made this less ugly in real code by setting the size of the points to zero, but am displaying them here for clarity. The issue now is only colors that are added on a given frame are handled correctly. So on frames 1-3 blue points only are updated. This leads to the red points hanging around until the first frame where a red point is added. This leads to further issues when going forwards and backwards using the slider but I think this movie rolling forward displays the core of the issue.

zero_frame = pd.concat([simple_df.assign(frame_id=0), simple_df])
px.scatter(
    zero_frame, x='x', y='y', color='color', animation_frame='frame_id'
).update_layout(
    title='Animation of y=x line growing from (0, 0) to (9, 9)',
).update_yaxes(range=[-1, 10]).update_xaxes(range=[-1, 10]).show()

Does anyone have any fixes for this? To re-iterate, in the toy example I would like a line of blue dots to grow from (1, 1) and have dots after (3, 3) be red, with one dot added per frame. I think if this worked I could get it to behave on real data.


Solution

  • I would do it like below with adding a fake red dot outside the y range:

    import numpy as np
    import pandas as pd
    import plotly.express as px
    
    # define your frames 
    frame = np.arange(10)
    #define x values for each frame 
    x = [np.arange(f+1) for f in frame] 
    # add the y values 
    y =  x 
    # define the colors 
    color = [ ['blue' if val <3 else 'red' for val in xx] for xx in x] 
    #put that in a dataframe 
    df = pd.DataFrame().assign(frame = frame,x=x,y=y,color=color) 
    #explode the lists of list 
    df = df.explode(['x','y','color']) 
    # add a fake point on frame 0 with red color and y value below the y range min 
    df.loc[-1] = [0,3,-2,'red']  
    
    # plot it 
    px.scatter(df,x='x',y='y',color='color',animation_frame='frame').update_yaxes(range=[-1,10]).update_xaxes(range=[-1,10])
    

    you will just see a somehow weird behavior for the first red point, sliding from the bottom but not sure if/how it can be done in a better way.

    Another approach could be to define it with correct x and y but with a size at 0. Then the glitch would be that the dot will "grow" in size on the correct spot.