3D Scatter Animation

Hello, i am new to programming and plotly.

Basically i am trying to create 3D scatter plot animation. I’ve succeed to animate markers but how can i see previous frames as animation goes on.

I would like to create something like this (withouth surface and contour parts)
example (starts at 0:18)

Here is my code;

import numpy as np
import plotly.graph_objects as go

x,y,z = np.genfromtxt(r'dat.txt', unpack=True)


# Create figure
fig = go.Figure(
    data=[go.Scatter3d(x=[], y=[], z=[],
                     mode="markers",marker=dict(color="red", size=10))])
    
fig.update_layout(
        
         scene = dict(
        
        xaxis=dict(range=[min(x), max(x)], autorange=False),
        yaxis=dict(range=[min(y), max(y)], autorange=False),
        zaxis=dict(range=[min(z), max(z)], autorange=False),
        )),


frames = [go.Frame(data= [go.Scatter3d(
                                       x=x[[k]], 
                                       y=y[[k]],
                                       z=z[[k]])],
                   
                   traces= [0],
                   name=f'frame{k}'      
                  )for k  in  range(len(x))]
fig.update(frames=frames),




fig.update_layout(updatemenus=[dict(type="buttons",
                          buttons=[dict(label="Play",
                                        method="animate",
                                        args=[None, dict(frame=dict(redraw=True,fromcurrent=True, mode='immediate'))      ])])])


fig.show()

1 Like

@akroma

To keep the previous plotted points, define the frames as follows:

frames = [go.Frame(data= [go.Scatter3d(
                                       x=x[:k+1], 
                                       y=y[:k+1],
                                       z=z[:k+1])],
                   
                   traces= [0],
                   name=f'frame{k}'      
                  )for k  in  range(len(x)-1)]

Remove the comma after fig.update_layout(....) and fig.update(frames=frames).

1 Like

It worked. Thank you so much @empet

Hey, I have recently come across your question and was wondering if perhaps you know how to add a slider to your animation.

Hey @Eithankam, i did not try to add a slider to my animation . But if i succeed i will let you know.

I would appreciate that.

@Eithankam ,
I added the slider :partying_face:

import numpy as np
import plotly.graph_objects as go

x,y,z = np.genfromtxt(r'dat.txt', unpack=True)


# Create figure
fig = go.Figure(go.Scatter3d(x=[], y=[], z=[],
                             mode="markers",
                             marker=dict(color="red", size=10)
                             )
                )

    
# Frames
frames = [go.Frame(data= [go.Scatter3d(x=x[:k+1],
                                       y=y[:k+1],
                                       z=z[:k+1]
                                       )
                          ],
                   traces= [0],
                   name=f'frame{k}'      
                  )for k  in  range(len(x)-1)
          ]

fig.update(frames=frames)




def frame_args(duration):
    return {
            "frame": {"duration": duration},
            "mode": "immediate",
            "fromcurrent": True,
            "transition": {"duration": duration, "easing": "linear"},
            }


sliders = [
    {"pad": {"b": 10, "t": 60},
     "len": 0.9,
     "x": 0.1,
     "y": 0,
     
     "steps": [
                 {"args": [[f.name], frame_args(0)],
                  "label": str(k),
                  "method": "animate",
                  } for k, f in enumerate(fig.frames)
              ]
     }
        ]

fig.update_layout(

    updatemenus = [{"buttons":[
                    {
                        "args": [None, frame_args(50)],
                        "label": "Play", 
                        "method": "animate",
                    },
                    {
                        "args": [[None], frame_args(0)],
                        "label": "Pause", 
                        "method": "animate",
                  }],
                    
                "direction": "left",
                "pad": {"r": 10, "t": 70},
                "type": "buttons",
                "x": 0.1,
                "y": 0,
            }
         ],
         sliders=sliders
    )

fig.update_layout(scene = dict(xaxis=dict(range=[min(x), max(x)], autorange=False),
                               yaxis=dict(range=[min(y), max(y)], autorange=False),
                               zaxis=dict(range=[min(z), max(z)], autorange=False)
                               )
                  )

fig.update_layout(sliders=sliders)
fig.show()

2 Likes

@akroma works like a charm!
Thank you so much!

Hi @akroma ,

I see you have background in petroleum and geoscience. Have you tried to plot well path?
I’ve x,y,z coordinates and scatter plot doesnt seem to work. My markers aren’t showing up. Moreover, as animation goes the canvas seems to be stretching up. Although, I have set:

xaxis=dict(range=[min(x), max(x)], autorange=False,zeroline=False)

image
image

Also @empet seems pretty knowledgeable about animation stuff in plotly. I hope she reads this :smiley:

Hey @timche ,

Welcome to the community.

I’m geoscience student and working in geothermal industry. I did plot 3d wells and lithology sometime in the past. Currently I am on field but I’ll be back home and help you in few days.

Have a nice day.

1 Like

Hi @timche and welcome to the plotly community.

I’m curios have you seen Scatter3D plots: 3d scatter plots in Python (plotly.com)

Also Check Plotly Animation: Intro to animations in Python

Here is a sample that may helps you:

import plotly.graph_objects as go
import numpy as np
from plotly.offline import iplot
import copy


def rndata():
    return np.random.randint(1,10,10)
    
theta=[i for i in range(100)]


fig = go.Figure(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes',marker=dict(color='yellow'),mode='markers'))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes1',marker=dict(color='blue'),mode='markers'))
fig.add_traces(go.Scatter3d(x=rndata(),y=rndata(),z=rndata(),name='Nodes2',marker=dict(color='red'),mode='markers'))

frames = []
frames.append({'data':copy.deepcopy(fig['data']),'name':f'frame{0}'})

for k in range(len(theta)):
    fig.update_traces(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),selector = ({'name':'Nodes'}))
    fig.update_traces(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),selector = ({'name':'Nodes1'}))
    fig.update_traces(x=tuple(rndata()),y=tuple(rndata()),z=tuple(rndata()),selector = ({'name':'Nodes2'}))
    frames.append({'data':copy.deepcopy(fig['data']),'name':f'frame{k+1}'})
    

fig.update(frames=frames)


iplot(fig)

@empet is person that I got many helps about animations (Check His posts on this community). Also @akroma helped me so much and also check his posts too.

1 Like

Hey @timche ,

Did your markers show up? If you can share some example data I can work on it.

Thank you for response! I’d like to keep previous points during animation just like @akroma

My code is identical to the one posted above

# select folder
root_folder = r'C:\Users\cepch\data'
well_names = os.listdir(root_folder)
df_drill_all = []


# Creating dict of wells where each key contains certain well and values represent X,Y,Z #coordinates
for well in well_names:
    ds_data_path = os.path.join(os.path.join(root_folder, well), 'Deviation_Survey')
    excel_file = os.listdir(ds_data_path)
    if len(excel_file)!=0:
        df = pd.read_excel(os.path.join(ds_data_path,excel_file[0]))
        df_drill_all.append(df)
        # df = pd.concat(df_drill_all).reset_index(drop=True)
well_dict = dict(zip(well_names,df_drill_all))     

# Choosing just one well for the sake of time
cardiff = well_dict['Cardiff-1']
#getting coordinates from dataframe
x = cardiff.X.values
y = cardiff.Y.values
z = cardiff.Z.values

# copied from forum
fig = go.Figure(go.Scatter3d(x=[], y=[], z=[],
                             mode="markers",
                             marker=dict(color="red", size=20))
                             )
                
frames = [go.Frame(data= [go.Scatter3d(x=[x[:k+1]],
                                       y=[y[:k+1]],
                                       z=[z[:k+1]],
                                       mode="markers",
                                       marker=dict(color="red", size=20)
                                       )
                          ],
                   traces= [0],
                   name=f'frame{k}'      
                  )for k  in  range(0,len(x)-1,100)
          ]
fig.update_layout(scene = dict(xaxis=dict(range=[min(x), max(x)], autorange=False,zeroline=False),
                               yaxis=dict(range=[min(y), max(y)], autorange=False,zeroline=False),
                               zaxis=dict(range=[min(z), max(z)], autorange=False,zeroline=False)
                               )
                  )

fig.update(frames=frames)

def frame_args(duration):
    return {
            "frame": {"duration": duration},
            "mode": "immediate",
            "fromcurrent": True,
            "transition": {"duration": duration, "easing": "linear"},
            }


sliders = [
    {"pad": {"b": 10, "t": 60},
     "len": 0.9,
     "x": 0.1,
     "y": 0,
     
     "steps": [
                 {"args": [[f.name], frame_args(0)],
                  "label": str(k),
                  "method": "animate",
                  } for k, f in enumerate(fig.frames)
              ]
     }
        ]

fig.update_layout(

    updatemenus = [{"buttons":[
                    {
                        "args": [None, frame_args(10)],
                        "label": "Play", 
                        "method": "animate",
                    },
                    {
                        "args": [[None], frame_args(0)],
                        "label": "Pause", 
                        "method": "animate",
                  }],
                    
                "direction": "left",
                "pad": {"r": 10, "t": 70},
                "type": "buttons",
                "x": 0.1,
                "y": 0,
            }
         ],
         sliders=sliders
    )



fig.update_layout(sliders=sliders)
fig.show()

Example of data

X Y Z
421293.4156	386343.3083	646.3804
421293.4156	386343.3083	646.5328
421293.4156	386343.3083	646.6852
421293.4156	386343.3083	646.8376
421293.4156	386343.3083	646.99
421293.4156	386343.3083	647.1424
421293.4156	386343.3083	647.2948
421293.4156	386343.3083	647.4472
421293.4156	386343.3083	647.5996
421293.4156	386343.3083	647.752
421293.4156	386343.3083	647.9044
421293.4156	386343.3083	648.0568
421293.4156	386343.3083	648.2092
421293.4156	386343.3083	648.3616
421293.4156	386343.3083	648.514
421293.4156	386343.3083	648.6664
421293.4156	386343.3083	648.8188
421293.4156	386343.3083	648.9712
421293.4156	386343.3083	649.1236
421293.4156	386343.3083	649.276
421293.4156	386343.3083	649.4284
421293.4156	386343.3083	649.5808
421293.4156	386343.3083	649.7332
421293.4156	386343.3083	649.8856
421293.4156	386343.3083	650.038
421293.4156	386343.3083	650.1904
421293.4156	386343.3083	650.3428
421293.4156	386343.3083	650.4952
421293.4156	386343.3083	650.6476
421293.4156	386343.3083	650.8
421293.4156	386343.3083	650.9524
421293.4156	386343.3083	651.1048
421293.4156	386343.3083	651.2572
421293.4156	386343.3083	651.4096
421293.4156	386343.3083	651.562

Hey @timche

import numpy as np
import plotly.graph_objects as go

x,y,z = np.genfromtxt(r'your_data.txt', unpack=True)


# Create figure
fig = go.Figure(go.Scatter3d(x=[], y=[], z=[],
                             mode="markers",
                             marker=dict(color="red", size=10)
                             )
                )

fig.update_layout(scene = dict(xaxis=dict(range=[min(x)-10, max(x)+10]),
                               yaxis=dict(range=[min(y)-10, max(y)+10]),
                               zaxis=dict(range=[min(z)-10, max(z)+10]),
                               aspectmode="cube"
                               ),
                  )
  
# Frames
frames = [go.Frame(data= [go.Scatter3d(x=x[:k+1],
                                       y=y[:k+1],
                                       z=z[:k+1]
                                       )
                          ],
                   traces= [0],
                   name=f'frame{k}'      
                  )for k  in  range(len(x)-1)
          ]

fig.update(frames=frames)




def frame_args(duration):
    return {
            "frame": {"duration": duration},
            "mode": "immediate",
            "fromcurrent": True,
            "transition": {"duration": duration, "easing": "linear"},
            }




fig.update_layout(

    updatemenus = [{"buttons":[{
                        "args": [None, frame_args(50)],
                        "label": "Play", 
                        "method": "animate",
                    },
                    {
                        "args": [[None], frame_args(0)],
                        "label": "Pause", 
                        "method": "animate",
                  }],
                    
                "direction": "left",
                "pad": {"r": 10, "t": 70},
                "type": "buttons",
                "x": 0.1,
                "y": 0,
            }
         ],
    )


fig.show()

Have a nice day.

1 Like

Hi Everyone,

Please shine some light on the txt file. What kind of data we should have in it and what kind of format we should have?
I can’t test the code without the file. Thanks.

Regards
CL

Hey @pocupine welcome to the forums.

Just copy the the data into a text file and name it your_data.txt

X Y Z
421293.4156	386343.3083	646.3804
421293.4156	386343.3083	646.5328
421293.4156	386343.3083	646.6852
421293.4156	386343.3083	646.8376
421293.4156	386343.3083	646.99
421293.4156	386343.3083	647.1424
421293.4156	386343.3083	647.2948
421293.4156	386343.3083	647.4472
421293.4156	386343.3083	647.5996
421293.4156	386343.3083	647.752
421293.4156	386343.3083	647.9044
421293.4156	386343.3083	648.0568
421293.4156	386343.3083	648.2092
421293.4156	386343.3083	648.3616
421293.4156	386343.3083	648.514
421293.4156	386343.3083	648.6664
421293.4156	386343.3083	648.8188
421293.4156	386343.3083	648.9712
421293.4156	386343.3083	649.1236
421293.4156	386343.3083	649.276
421293.4156	386343.3083	649.4284
421293.4156	386343.3083	649.5808
421293.4156	386343.3083	649.7332
421293.4156	386343.3083	649.8856
421293.4156	386343.3083	650.038
421293.4156	386343.3083	650.1904
421293.4156	386343.3083	650.3428
421293.4156	386343.3083	650.4952
421293.4156	386343.3083	650.6476
421293.4156	386343.3083	650.8
421293.4156	386343.3083	650.9524
421293.4156	386343.3083	651.1048
421293.4156	386343.3083	651.2572
421293.4156	386343.3083	651.4096
421293.4156	386343.3083	651.562

Hi Aimped,

Thanks so much for the help.

Regards
CL

Hi,
Sorry, I encountered some errors using these numbers.
1: Line #55 (got 2 columns instead of 3)
Line #56 (got 1 columns instead of 3)
Line #57 (got 2 columns instead of 3)
Line #58 (got 1 columns instead of 3)
Line #59 (got 2 columns instead of 3)
Line #60 (got 1 columns instead of 3)
Line #61 (got 2 columns instead of 3)
Line #62 (got 1 columns instead of 3)
Line #63 (got 2 columns instead of 3)
Line #64 (got 1 columns instead of 3)
Line #65 (got 2 columns instead of 3)
Line #66 (got 1 columns instead of 3)
Line #67 (got 2 columns instead of 3)
Line #68 (got 1 columns instead of 3)
Line #69 (got 2 columns instead of 3)
Line #70 (got 1 columns instead of 3)
Line #71 (got 2 columns instead of 3)
I used invalid_raise = False to fix this problem.
x,y,z = np.genfromtxt(r’dat.txt’, unpack=True,delimiter=’ ', invalid_raise = False)

2:TypeError: ‘numpy.float64’ object is not iterable

Anyone can help? Thanks.

Regards
CL

Not sure what exactly you did, but this file in combination withe the above code does not throw any error:

1 Like

Hi,

Thanks. All good. IF anyone here encounter the same problem, Please do not copy and paste the data into a TXT file directly. AIMPED’s file is good to use. ONE post on Stackoverflow mentioned that it might be because of encoding problem.

Regards
CL