Search code examples
pythonmatplotlibanimated-gifastronomy

How do I create a MatPlotLib or similar graph that varies with time?


What I am trying to do is to plot the data from an orbital simulation using Matplotlib; an example of what I am trying to achieve is attached below. Orbital simulation

I have the data of planets as well as the spacecraft in X,Y,Z, time coordinates(or if necessary I can rerun to get it in other coordinate systems) with respect to the Sun.

How can I make a GIF like this?

I tried just using pyplot of x,y, but instead of the moving GIF I only get a flat image.(As expected, because PyPlot can only make static graphs). Any help/pointers to where I can look is greatly appreciated.


Solution

  • One can use matplotlib FuncAnimation facility to create animations based on data in matplotlib. The idea is to view the animation as a collection of frames and define the graph/contents of each frame based on the frame number (assuming that the frames are placed in ascending order). More details on matplotlib animations can be found on this page on the official site: Matplotlib Animations. Rest of the answer provides some guidance specifically for the question.


    End Result

    Here is a dummy animation that I created using matplotlib FuncAnimation. It is based around the idea of the question (though with dummy data).

    Sample GIF animation created for some data using matplotlib FuncAnimation

    Code

    Here is the code to generate the above animation -

    from matplotlib.animation import FuncAnimation
    import matplotlib.pyplot as plt
    import math
    
    # dummy parameters, replace them with your own
    sun_x, sun_y = 0, 0
    voyager1_theta, voyager1_speed, voyager1_radius = 0, 0.003*math.pi, 40
    planet1_theta, planet1_speed, planet1_radius = 0, 0.002*math.pi, 40
    planet2_theta, planet2_speed, planet2_radius = 0, 0.001*math.pi, 90
    fps = 10   # frames per second in animation
    total_time = 5  # in seconds, total time of animation
    time_ratio = 100 # in seconds, how many seconds in reality for every second on animation
    
    # extra calculations
    interval = 1/fps # in seconds
    total_frames = total_time * fps
    
    voyager_x_data = []
    voyager_y_data = []
    
    
    fig, ax = plt.subplots()
    
    def update(frame):
        # this function accepts the frame-number as frame and creates the figure for it
        global ax
    
        # dummy calculations, replace them with your own calculations
        time = frame*interval*time_ratio  # in seconds
        voyager_x = (voyager1_radius+time/20) * math.cos(voyager1_speed*time + voyager1_theta)
        voyager_y = (voyager1_radius+time/20) * math.sin(voyager1_speed*time + voyager1_theta)
        voyager_x_data.append(voyager_x)
        voyager_y_data.append(voyager_y)
    
        planet1_x = planet1_radius * math.cos(planet1_speed*time + planet1_theta)
        planet1_y = planet1_radius * math.sin(planet1_speed*time + planet1_theta)
        planet2_x = planet2_radius * math.cos(planet2_speed*time + planet2_theta)
        planet2_y = planet2_radius * math.sin(planet2_speed*time + planet2_theta)
    
        # plotting
        ax.clear()  # clear the figure (to remove the contents of last frame)
        ax.set_ylim(-100,100)
        ax.set_xlim(-100,100)
        ax.set_aspect('equal')
        ax.set_title('Time = {:.3f} sec'.format(time))
        ax.scatter(sun_x, sun_y, color = 'yellow', marker = 'o') #, label='Sun')
        ax.scatter(planet1_x, planet1_y, color = 'blue', marker = 'o') #, label = 'Planet1')
        ax.scatter(planet2_x, planet2_y, color = 'green', marker = 'o') #, label = 'Planet2')
        ax.plot(voyager_x_data, voyager_y_data, color = 'red')
        ax.scatter(voyager_x, voyager_y, color = 'red', label = 'Voyager')
        ax.legend()
    
    anim = FuncAnimation(fig, update, frames = range(total_frames), interval = interval*1000)  # multiplying by 1000 to convert sec into milli-sec
    anim.save('result.gif', fps = fps)  # replace the name of file with your own path and name
    #plt.show()  # uncomment this line and comment out the second-last line to see in realtime instead of saving
    

    Explanation

    The comments inside the code contain all the assistive information required while reading and working with the code. This section contains overall explanation.

    1. Create the data. You can compute or load data from any database. In the code above, we are computing the data. Some data is also computed in the update function while creating the frames. Also, there are some extra convenience parameters in the code like fps, total_time, time_ratio.
    2. Initialize the matplotlib figure and axis (fig, ax = plt.subplots()) and then define the update function which takes frame number as input and creates the figure for that frame number.
    3. Create the animation using FuncAnimation. In the code we did this with FuncAnimation(fig, update, frames = range(total_frames), interval = interval*1000). We specify the frames to match the time we want and interval to match the FPS we want in the animation.
    4. Finally, view/save the animation. To view, call plt.show(). To save, call anim.save() in which, specify the path to the output file along with the filename and the fps to match the FPS we want.

    Note

    1. The extension in the filename specified to anim.save() is used to decide the export format. Additional formats are supported with the help of ffmpeg. To get support for additional formats, install ffmpeg and specify writer='ffmpeg' in anim.save().
    2. While viewing the animation in realtime i.e. using plt.show(), set frames=None in FuncAnimation() since we don't want to specify how long the animation runs. We want it to run as long as we are viewing it.