How to use Plotly Python for complex 3D animations

dfexample

I have a pandas dataframe in the above format from which I’m trying to create a complex animation from offline in a VScode Jupyter notebook. I’ve been trying for a couple days now to animate it but can’t quite figure out the exact configuration needed for it to work, and now think I need to raise my hand and ask my help.

Part of my struggle is that it is producing a graphic (see below) but no error message that would hint at where to investigate next. I had this working in a much simpler animation but it has failed to scale appropriately. When I deconstruct the output of the animateDataSource it outputs a list of frames as expected. Any suggestions on how to improve the code would be appreciated. (I am around to answer follow up questions if that’s helpful.)

#imports
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.offline import init_notebook_mode
init_notebook_mode(connected=False)

source = df
def frameMaker(i):
    """
    returns x,y,z dict of currently indexed frame by vector component key
    """
    scale = 2
    current_dict = dict({
        "x": [[source["X"][i], scale * source["X"][i+1]], [source["Y"][i], source["Y"][i]], [source["Z"][i], source["Z"][i]]],
        "y": [[source["X"][i], source["X"][i]], [source["Y"][i], scale * source["Y"][i+1]], [source["Z"][i], source["Z"][i]]],
        "z": [[source["X"][i], source["X"][i]], [source["Y"][i], source["Y"][i]], [source["Z"][i], scale * source["Z"][i+1]]]
    })
    return current_dict

def animateDataSource(time_series_source):
    """
    Takes lists of x,y,z data and returns a list of plotly frames through the frameMaker function. 
    """
    list_of_frames = []
    for k in range(time_series_source.shape[0]-1):
        current_vector_data = frameMaker(k)
        list_of_frames.append(
            go.Frame(
                data=[go.Scatter3d(
                    x = [time_series_source["X"][k]],
                    y = [time_series_source["Y"][k]],
                    z = [time_series_source["Z"][k]],
                    name = "target",
                    mode = "markers",
                    marker=dict(color="red",size=10,opacity=0.5)),
                go.Scatter3d(
                    x=time_series_source["X"],
                    y=time_series_source["Y"],
                    z=time_series_source["Z"],
                    name="path",
                    mode="lines",
                    line=dict(color="black",  width=1)),    
                go.Scatter3d(
                    x=current_vector_data["x"][0],
                    y=current_vector_data["x"][1],
                    z=current_vector_data["x"][2],
                    name = "x",
                    line=dict(color='blue',width=3)),
                go.Scatter3d(
                    x=current_vector_data["y"][0],
                    y=current_vector_data["y"][1],
                    z=current_vector_data["y"][2],
                    name = "y",
                    line=dict(color='green',width=3)),
                go.Scatter3d(
                    x=current_vector_data["z"][0],
                    y=current_vector_data["z"][1],
                    z=current_vector_data["z"][2],
                    name = "z",
                    line=dict(color='red',width=3))
                ]
            )
        )
    return list_of_frames

vect = frameMaker(0)
plt = go.Figure(
    data=[go.Scatter3d(
            x=[df["X"].iloc[0]],
            y=[df["Y"].iloc[0]],
            z=[df["Z"].iloc[0]],
            name="target",
            mode="markers",
            marker=dict(
                color="red",
                size=10,
                opacity=0.5)),
        go.Scatter3d(
            x=df["X"].values,
            y=df["Y"].values,
            z=df["Z"].values,
            name="path",
            mode="lines",
            line=dict(
                color="black",
                width=1)),
        go.Scatter3d(
            x = vect["x"][0],
            y = vect["x"][1],
            z = vect["x"][2],
            name = "x",
            mode = "lines",
            line = dict(color='blue', width=3)),
        go.Scatter3d(
            x = vect["y"][0],
            y = vect["y"][1],
            z = vect["y"][2],
            name = "y",
            mode = "lines",
            line = dict(color='green', width=3)),
        go.Scatter3d(
            x=vect["z"][0],
            y=vect["z"][1],
            z=vect["z"][2],
            name = "z",
            mode = "lines",
            line = dict(color='red', width=3))],
    layout = 
        go.Layout(
            title = go.layout.Title(text="Movement"),
            scene_aspectmode="cube",
            scene = dict(
                xaxis = dict(range=[-5,5], nticks=10, autorange=False),
                yaxis = dict(range=[-5,5], nticks=10, autorange=False),
                zaxis = dict(range=[-5,5], nticks=10, autorange=False)),
            updatemenus=[dict(type="buttons",
                          buttons=[dict(label="Play",
                                        method="animate",
                                        args=[None])])]),
    frames = animateDataSource(df)
)
plt.show()

Updated 2019-12-26:
Needs to be refactored a bit but I’ve updated the above with the framemaker def.

Hi @wower, thanks for sharing your code – however could you please make it completely standalone, for example by using dummy data to create the object frameMaker ? At the moment I cannot execute your code and hence not reproduce the problem.

@Hi @wower

As we discussed related to a previous question on animation, in your frame definition is missing the parameter traces, namely traces = [0,1,2,3,4], to inform plotly.js that the trace k, in in the frame data list, updates the trace k in your list, plt.data, k =0,1,2,3,4.

Hence you should insert traces =[0,1,2,3,4] in the definition of each frame, i.e. in the function
animateDataSource() define:

list_of_frames.append(
            go.Frame(
                data=[go.Scatter3d(
                    x = [time_series_source["X"][k]],
                    y = [time_series_source["Y"][k]],
                    z = [time_series_source["Z"][k]],
                    name = "target",
                    mode = "markers",
                    marker=dict(color="red",size=10,opacity=0.5)),
                go.Scatter3d(
                    x=time_series_source["X"],
                    y=time_series_source["Y"],
                    z=time_series_source["Z"],
                    name="path",
                    mode="lines",
                    line=dict(color="black",  width=1)),    
                go.Scatter3d(
                    x=current_vector_data["x"][0],
                    y=current_vector_data["x"][1],
                    z=current_vector_data["x"][2],
                    name = "x",
                    line=dict(color='blue',width=3)),
                go.Scatter3d(
                    x=current_vector_data["y"][0],
                    y=current_vector_data["y"][1],
                    z=current_vector_data["y"][2],
                    name = "y",
                    line=dict(color='green',width=3)),
                go.Scatter3d(
                    x=current_vector_data["z"][0],
                    y=current_vector_data["z"][1],
                    z=current_vector_data["z"][2],
                    name = "z",
                    line=dict(color='red',width=3))
                ],
                traces =[0,1,2,3,4] ####THIS IS THE LINE THAT MUST BE INSERTED
            )
        )

With this simple code update, and synthetic data I succeeded to make your animation work.

Hello,

First thank you for the discussion.
I am a fresh user of Plotly, and i’m trying to make this code working but I guess i didn’t get what df is in this case.

basically, I’m trying since two days to make an animation of an orthogonal base which evolves in the time (by Euler rotation). I succeed to make a 3d plot of the orthogonal base (with Numpy array, not with panda library), but the animation still doesn’t work.
I guess your solution might help me but I’m not able to make it work…

If you can help me on this, it would be great,

Thanks a lot,

Alex

Hi @AlexSouch,

Could you, please give more details? Initially you give an othonormal basis, but you did not explain how it transforms from a frame to frame.

Hi @empet,

I working on an ellipsoĂŻde. I image this ellipsoĂŻde with a microscope, over the time, and then, I fit his shape at each time. The ellipsoĂŻde moves, so the axis of his own base moves also at each time. My goal now is to plot the base of the ellipsoid, and make an animation to show his movement

I have been able to plot the base at each time. But know I’m trying to make the animation. Here is the code for one base :

import plotly.graph_objs as go
from plotly.offline import plot
import plotly.express as px
import numpy as np
import xlrd 

def trace_base (p):
    #prepare plotting base
    vector = go.Scatter3d( x = [-p[0,0],p[0,0]],
                           y = [-p[1,0],p[1,0]],
                           z = [-p[2,0],p[2,0]],
                           marker = dict( size = 1,
                                          color = "rgb(84,48,5)"),
                           line = dict( color = "rgb(84,48,5)",
                                        width = 6)
                         )
                           
    vector2 = go.Scatter3d( x = [-p[0,1],p[0,1]],
                           y = [-p[1,1],p[1,1]],
                           z = [-p[2,1],p[2,1]],
                           marker = dict( size = 1,
                                          color = "rgb(84,48,5)"),
                           line = dict( color = "rgb(84,48,5)",
                                        width = 6)
                         )
                           
    vector3 = go.Scatter3d( x = [-p[0,2],p[0,2]],
                           y = [-p[1,2],p[1,2]],
                           z = [-p[2,2],p[2,2]],
                           marker = dict( size = 1,
                                          color = "rgb(84,48,5)"),
                           line = dict( color = "rgb(84,48,5)",
                                        width = 6)
                         )
    #direction of mouvement                    
    vector4 = go.Scatter3d( x = [0,100],
                           y = [0,0],
                           z = [0,0],
                           marker = dict( size = 20,
                                          color = "rgb(250,0,0)"),
                           line = dict( color = "rgb(250,0,0)",
                                        width = 15)
                         )
                           
    data = [vector,vector2,vector3,vector4]
    layout = go.Layout(margin = dict( l = 0,
                                      r = 0,
                                      b = 0,
                                      t = 0),
                           title=go.layout.Title(text="A Figure Specified By A Graph Object")
                      )
    fig = go.Figure(data=data,layout=layout)
    fig.write_html('3d-base.html', auto_open=True)
    #py.iplot(fig, filename='cone-basic', validate=False)
    return

#Main - extracting datas from  
workbook = xlrd.open_workbook('serie11-2020-07-28.xlsx')
SheetNameList = workbook.sheet_names()
#for i in  range(len (SheetNameList)):
#    print(SheetNameList)
worksheet = workbook.sheet_by_name(SheetNameList[0])



    
value = []
for i in range(0,7):
    p=np.zeros((3,3))

    
    p[0,0] = worksheet.cell_value(17, 1+i*4)*100    
    p[0,1] = worksheet.cell_value(15, 1+i*4)*100   
    p[0,2] = worksheet.cell_value(16, 1+i*4)*100   
    
    p[1,0] = worksheet.cell_value(17, 2+i*4)*100   
    p[1,1] = worksheet.cell_value(15, 2+i*4)*100   
    p[1,2] = worksheet.cell_value(16, 2+i*4)*100   
    
    p[2,0] = worksheet.cell_value(17, 3+i*4)*100   
    p[2,1] = worksheet.cell_value(15, 3+i*4)*100   
    p[2,2] = worksheet.cell_value(16, 3+i*4)*100      
    value.append(p)
    
    
    
trace_base(p)


as I said, the base § is a numpy array. So for exemple the first base is :

p= np.array([[ 85.0940004 , -27.90698053, -44.49956779],
       [ 20.92464951,  95.71634598, -20.01349932],
       [ 48.17852362,   7.7189086 ,  87.2883057 ]])

and the second one is :

p2=np.array([[ 90.10427126, -14.36238945, -40.92605612],
             [ 10.85156116 , 98.82226846, -10.78901653],
             [ 41.99361762 ,  5.28024872 , 90.60162831]])

I tried a lot to make the animation with [Visualization of MRI volume slices](https://plotly.com/python/visualizing-mri-volume-slices/). I have been able to make some stuff, but only with surface.

If you have idea to guide me in this work, i would appreciate a lot!

Thank you,

Alex

Hi @AlexSouch,

With only two bases you provided, I couldn’t define an animation. That’s why I animated the Frenet frame associated to a spiral. Here is the code with a detailed explanation: https://chart-studio.plotly.com/~empet/15765.

Plotly uses, by default, the perspective projection for 3d plots, but you can set the 'orthographic' projection, too.

Because of these projections the three vectors are not displayed as orthogonal vectors, although they are.

I hope you’ll understand now, how a 3d animation is set up.

Hi @empet,

Thanks a lot for your answer! I get it and I made what I wanted to do.
Here is my code for those who want to do the same, or want another exemple :

import plotly.graph_objs as go
import numpy as np
import xlrd

def get_frame_data(s, value):    
    q=value[s]
    return go.Scatter3d(x = [-q[0][0], q[0][0], None, -q[0][1], q[0][1], None, -q[0][2], q[0][2], None],
                        y = [-q[1][0], q[1][0], None, -q[1][1], q[1][1], None, -q[1][2], q[1][2], None],
                        z = [-q[2][0], q[2][0], None, -q[2][1], q[2][1], None, -q[2][2], q[2][2],None],
                        marker = dict( size = 5,color = "rgb(0,255,0)"),
                        line = dict( color = "rgb(255,255,0)", width = 5))
# =============================================================================
# Main - extracting datas from  excel doc
# =============================================================================
#workbook = xlrd.open_workbook('serie11-2020-07-28.xlsx') #open my excel work
#SheetNameList = workbook.sheet_names()
#worksheet = workbook.sheet_by_name(SheetNameList[0])
#value = [] #all my bases coordonates
#for i in range(0,7):
#    p=np.zeros((3,3))
#    p[0,1] = worksheet.cell_value(6, 1+i*4)*100   
#    p[0,2] = worksheet.cell_value(7, 1+i*4)*100   
#    p[0,0] = worksheet.cell_value(8, 1+i*4)*100 
#          
#    p[1,1] = worksheet.cell_value(6, 2+i*4)*100   
#    p[1,2] = worksheet.cell_value(7, 2+i*4)*100   
#    p[1,0] = worksheet.cell_value(8, 2+i*4)*100 
#    
#    p[2,1] = worksheet.cell_value(6, 3+i*4)*100   
#    p[2,2] = worksheet.cell_value(7, 3+i*4)*100  
#    p[2,0] = worksheet.cell_value(8, 3+i*4)*100 
#    value.append(p)
#    
    
value = [np.array([[ 88.60118172, -16.41451377, -43.36351388],
                   [-15.22968245, -98.63756253,   6.21997024],
                   [ 43.79369099,  -1.09315833,  89.89392435]]),
         np.array([[ 90.81901838,  -6.96528468, -41.27215417],
                [  2.25676684,  99.27708117, -11.78847564],
                [-41.79489088,  -9.77476157, -90.31966083]]),
         np.array([[-91.18490585,  23.37877136,  33.74531071],
                [ -9.12940982, -91.68989999,  38.85377866],
                [-40.02457772, -32.34803378, -85.74169283]]),
         np.array([[ 91.45549733, -16.18384006, -37.0671732 ],
                [  5.07290762,  95.51181034, -29.18492238],
                [ 40.12676932,  24.81083246,  88.17179241]]),
         np.array([[ 91.72517715,  -9.91460243, -38.57709858],
                [  2.62569402,  98.1469013 , -18.98134602],
                [ 39.74415186,  16.39775669,  90.28574621]]),
         np.array([[ 89.65439257, -20.78696585, -39.11511145],
                [  8.72123699,  94.86013945, -30.42193236],
                [-43.42844596, -23.8632771 , -86.85916237]]),
         np.array([[ 86.08110262, -23.28852651, -45.25139009],
                [-14.49839349, -96.45302789,  22.05923834],
                [ 48.78360748,  12.42811099,  86.40429213]])]    
    
time_frame = np.array(range(len(value)))    
fig = go.Figure(go.Scatter3d( x = [0,100], y = [0,0], z = [0,0], marker = dict( size = 5,color = "rgb(0,255,0)"),line = dict( color = "rgb(0,255,0)", width = 5)))
fig.add_trace(get_frame_data(0, value)) #add the orthonormal basis at the spiral point corresponding to s=0
fig.data[1].update(mode='lines', line_color='red', line_width=7) # update the basis trace by setting vector color, etc
fig.update_layout(width=800, height=600);

frames = []
for  t in time_frame:
    frames.append(go.Frame(data=[get_frame_data(t, value)], traces=[1])) #traces=[1] tells Plotly.js that each frame updates fig.data[1], i.e. the vectors    
  
fig.update(frames=frames);  
fig.update_layout(
        title='Slices in volumetric data',
         width=800,
         height=600,
         scene=dict(
                    xaxis=dict(range=[-100, 100], autorange=False),
                    yaxis=dict(range=[-100, 100], autorange=False),
                    zaxis=dict(range=[-100, 100], autorange=False),
                    aspectratio=dict(x=1, y=1, z=1),
                    ),    
        updatemenus=[dict(type='buttons', 
                                y=0.2,
                                x=1.05,
                                active=0,
                                buttons=[dict(label='Play',
                                              method='animate',
                                              args=[None, 
                                                    dict(frame=dict(duration=500, #HERE you can change the frame rate
                                                                    redraw=True),
                                                         transition=dict(duration=10),
                                                         fromcurrent=True,
                                                         mode='immediate')])])]);
    
fig.write_html('3d-base_mvt.html', auto_open=True) 

Now, I’m trying to add a slider on it. Again, I suppose that I’m missing something. I see that it “almost” works, but I can’t be able to only have the steps in my sliders. When the fig appears, I get all my traces in one image. I worked on it all the week-end to present you this work, but I didn’t succeed… I guess I miss something into the add_traces parameters…

import plotly.graph_objs as go
import numpy as np

def get_frame_data(s, value):    
    q=value[s]
    base = go.Scatter3d(x = [-q[0][0], q[0][0], None, -q[0][1], q[0][1], None, -q[0][2], q[0][2], None],
                        y = [-q[1][0], q[1][0], None, -q[1][1], q[1][1], None, -q[1][2], q[1][2], None],
                        z = [-q[2][0], q[2][0], None, -q[2][1], q[2][1], None, -q[2][2], q[2][2],None],
                        line = dict( color = "rgb(255,0,0)", width = 5))
    vector = go.Scatter3d( x = [0,100], y = [0,0], z = [0,0], marker = dict( size = 5,color = "rgb(0,255,0)"),line = dict( color = "rgb(0,255,0)", width = 5))  
    data = [base, vector]
    return  data
                           
=====================================================================
# Main 
# ====================================================================
value = [np.array([[ 88.60118172, -16.41451377, -43.36351388],
                   [-15.22968245, -98.63756253,   6.21997024],
                   [ 43.79369099,  -1.09315833,  89.89392435]]),
         np.array([[ 90.81901838,  -6.96528468, -41.27215417],
                [  2.25676684,  99.27708117, -11.78847564],
                [-41.79489088,  -9.77476157, -90.31966083]]),
         np.array([[-91.18490585,  23.37877136,  33.74531071],
                [ -9.12940982, -91.68989999,  38.85377866],
                [-40.02457772, -32.34803378, -85.74169283]]),
         np.array([[ 91.45549733, -16.18384006, -37.0671732 ],
                [  5.07290762,  95.51181034, -29.18492238],
                [ 40.12676932,  24.81083246,  88.17179241]]),
         np.array([[ 91.72517715,  -9.91460243, -38.57709858],
                [  2.62569402,  98.1469013 , -18.98134602],
                [ 39.74415186,  16.39775669,  90.28574621]]),
         np.array([[ 89.65439257, -20.78696585, -39.11511145],
                [  8.72123699,  94.86013945, -30.42193236],
                [-43.42844596, -23.8632771 , -86.85916237]]),
         np.array([[ 86.08110262, -23.28852651, -45.25139009],
                [-14.49839349, -96.45302789,  22.05923834],
                [ 48.78360748,  12.42811099,  86.40429213]])]   
    
time_frame = np.array(range(len(value)))  

q=value[0]
base = go.Scatter3d(x = [-q[0][0], q[0][0], None, -q[0][1], q[0][1], None, -q[0][2], q[0][2], None],
                    y = [-q[1][0], q[1][0], None, -q[1][1], q[1][1], None, -q[1][2], q[1][2], None],
                    z = [-q[2][0], q[2][0], None, -q[2][1], q[2][1], None, -q[2][2], q[2][2],None],
                    line = dict( color = "rgb(255,0,0)", width = 5))
vector = go.Scatter3d( x = [0,100], y = [0,0], z = [0,0], marker = dict( size = 5,color = "rgb(0,255,0)"),line = dict( color = "rgb(0,255,0)", width = 5))    
data = [base, vector]  
fig = go.Figure(data)
#fig.add_traces(go.Scatter3d( x = [0,100], y = [0,0], z = [0,0], marker = dict( size = 5,color = "rgb(0,255,0)"),
#                           line = dict( color = "rgb(0,255,0)", width = 5))) #add the orthonormal basis at the spiral point corresponding to s=0
#fig.data[1].update(mode='lines', line_color='red', line_width7) # update the basis trace by setting vector color, etc
fig.update_layout(width=600, height=600);
frames = []
for  t in time_frame:
    frames.append(go.Frame(data=get_frame_data(t,value), traces=[1])) #traces=[1] tells Plotly.js that each frame updates fig.data[1], i.e. the vectors    
    fig.add_traces(get_frame_data(t, value)[0]) 

# =============================================================================
#     Slider button
# =============================================================================   
# Create and add slider
steps = []
for i in range(len(fig.data)):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(fig.data)},
              {"title": "Ellipsoide au temps : " + str(i)}],  # layout attribute
    )
    step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
    steps.append(step)

sliders = [dict(
    active=0,
    currentvalue={"prefix": "time : "},
    pad={"t": 15},
    steps=steps
)]

fig.update_layout(sliders=sliders)
    
fig.write_html('3d-base mvt.html', auto_open=True) 

Again, thank you for your help, and have a nice day,

Alex

@AlexSouch,

To add slider to animation you have to add a name to each frame, because the slider steps are connected to frames via these names.

For my example the new frame definition is this one:

frames = []

for  k, s in enumerate(t):
    frames.append(go.Frame(data=[get_frame_data(s, f=vec_fac)],
                           name = f'fr{k}',
                           traces=[1])) 

and

 sliders =  [dict(steps= [dict(method= 'animate',
                                               args= [ [ f'fr{k}'], #this steps refers to the frame of name f'fr{k}
                                               dict(mode= 'immediate',
                                                    frame= dict( duration=100, redraw= True ),
                                                    fromcurrent=True,
                                                    transition=dict( duration= 0))],
                                                label=f"fr{k}") for k, s  in enumerate(t)], #this is the step label on the slider 
                                  minorticklen=0,
                                  x=0,
                                   len=1)]
fig.update_layout(sliders=sliders)

Thank you so much, I get it.
I’m really sorry to bother you, but every time I understand, I try something new thinking that I have understand and… no…
Indeed, i made it work in my case, and that’s a huge progress. However, I tried to change the color for each axis. So I changed my function "get_frame"data so his output would be a list of scatter3d

def get_frame_data(s, value):    
    q=value[s]
    base_x = go.Scatter3d(x = [-q[0][0], q[0][0]],
                        y = [-q[1][0], q[1][0]],
                        z = [-q[2][0], q[2][0]], 
                        line = dict( color = "rgb(255,0,0)", width = 5))
    base_y = go.Scatter3d(x = [ -q[0][1], q[0][1]],
                        y = [-q[1][1], q[1][1]],
                        z = [-q[2][1], q[2][1]], 
                        line = dict( color = "rgb(255,255,0)", width = 5))
    base_z = go.Scatter3d(x = [-q[0][2], q[0][2]],
                        y = [-q[1][2], q[1][2]],
                        z = [-q[2][2], q[2][2]], 
                        line = dict( color = "rgb(0,0,255)", width = 5))
  
    return  [base_x,base_y,base_z]    

each line should have his own color, but then, only one line is traced on my graph at each time.
I also tried to make three different function to ensure that the type of each axis is a “scatter3d”, but it doesn’t change anything : only one axis appears. I suppose that go.Frame doesn’t read as go.Figure, but I didn’t find anything on it?

here is my entire code :

import plotly.graph_objs as go
import numpy as np

def get_frame_data(s, value):    
    q=value[s]
    base_x = go.Scatter3d(x = [-q[0][0], q[0][0]],
                        y = [-q[1][0], q[1][0]],
                        z = [-q[2][0], q[2][0]], 
                        line = dict( color = "rgb(255,0,0)", width = 5))
    base_y = go.Scatter3d(x = [ -q[0][1], q[0][1]],
                        y = [-q[1][1], q[1][1]],
                        z = [-q[2][1], q[2][1]], 
                        line = dict( color = "rgb(255,255,0)", width = 5))
    base_z = go.Scatter3d(x = [-q[0][2], q[0][2]],
                        y = [-q[1][2], q[1][2]],
                        z = [-q[2][2], q[2][2]], 
                        line = dict( color = "rgb(0,0,255)", width = 5))
  
    return  [base_x,base_y,base_z]       

# =============================================================================
# Main - extracting datas from  
# =============================================================================
value = [np.array([[ 88.60118172, -16.41451377, -43.36351388],
                   [-15.22968245, -98.63756253,   6.21997024],
                   [ 43.79369099,  -1.09315833,  89.89392435]]),
         np.array([[ 90.81901838,  -6.96528468, -41.27215417],
                [  2.25676684,  99.27708117, -11.78847564],
                [-41.79489088,  -9.77476157, -90.31966083]]),
         np.array([[-91.18490585,  23.37877136,  33.74531071],
                [ -9.12940982, -91.68989999,  38.85377866],
                [-40.02457772, -32.34803378, -85.74169283]]),
         np.array([[ 91.45549733, -16.18384006, -37.0671732 ],
                [  5.07290762,  95.51181034, -29.18492238],
                [ 40.12676932,  24.81083246,  88.17179241]]),
         np.array([[ 91.72517715,  -9.91460243, -38.57709858],
                [  2.62569402,  98.1469013 , -18.98134602],
                [ 39.74415186,  16.39775669,  90.28574621]]),
         np.array([[ 89.65439257, -20.78696585, -39.11511145],
                [  8.72123699,  94.86013945, -30.42193236],
                [-43.42844596, -23.8632771 , -86.85916237]]),
         np.array([[ 86.08110262, -23.28852651, -45.25139009],
                [-14.49839349, -96.45302789,  22.05923834],
                [ 48.78360748,  12.42811099,  86.40429213]])]   
    
time_frame = np.array(range(len(value)))  
vector = go.Scatter3d( x = [0,100], y = [0,0], z = [0,0], marker = dict( size = 5,color = "rgb(0,255,0)"),line = dict( color = "rgb(0,255,0)", width = 5))    
#layout = go.Layout(margin = dict( l = 0,
#                                  r = 0,
#                                  b = 0,
#                                  t = 0),
#                       title=go.layout.Title(text="3d base")
#                      )
fig = go.Figure(data = get_frame_data(0,value))#, layout = layout)
fig.add_traces(vector) #direction of cells

fig.update_scenes( 
                  xaxis_autorange=False,
                  yaxis_autorange=False,
                  zaxis_autorange=False,
                  xaxis_range=[-100, 100],
                  yaxis_range=[-100, 100],
                  zaxis_range=[-100, 100], 
                  camera_eye=dict(x=1.2, y=1.2, z=0.8), # camera position
#                  camera_projection_type='orthographic'    #thedefault projection is perspective
                  )

fig.update_layout(width=800, height=800);

frames = []

for  k, t in enumerate(time_frame):
    print(k,t)
    frames.append(go.Frame(data = get_frame_data(t,value),
                           name = f'fr{k}',
                           traces=[1])) 
    
fig.update(frames=frames);  
# =============================================================================
#     Slider button
# =============================================================================   
# Create and add slider
sliders =  [dict(steps= [dict(method= 'animate',
                                               args= [ [ f'fr{k}'], #this steps refers to the frame of name f'fr{k}
                                               dict(mode= 'immediate',
                                                    frame= dict( duration=100, redraw= True ),
                                                    fromcurrent=True,
                                                    transition=dict( duration= 0))],
                                                label=f"fr{k}") for k, time_frame  in enumerate(range(len(value)))], #this is the step label on the slider 
                                  minorticklen=0,
                                  x=0,
                                  len=1)]
fig.update_layout(sliders=sliders)   

    
fig.write_html('3d-base mvt.html', auto_open=True) 

@AlexSouch

From your code I cannot see how fig.data is defined. It should consist in three traces, representing the three orthonormal vectors at the starting time.
If fig.data is already defined like that, then in the frame definition set traces = [0, 1, 2], meaning that the j^th trace, j=0,1,2, of a frame data, updates the j^th trace in fig.data.

Thank you very much! It works and I understood how does it work! Thank’s a lot for your help!