4
\$\begingroup\$

I'd like to plot an animation of Lissajous curves using Python and matplotlib's animate library. I really do not have a lot of experience with Python, so rather than performance increasements, I'm looking for best practices to improve (and/or shorten) my code.

The following code produces a .gif file when evaluated as a jupyter-lab cell:

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter

x_data = []
y_data = []
max_range = 1.2

f1 = 3        # sets the frequency for the horizontal motion
f2 = 5        # sets the frequency for the vertical motion
d1 = 0.0      # sets the phase shift for the horizontal motion
d2 = 0.5      # sets the phase shift for the vertical motion
delta1 = d1 * np.pi # I define the phase shift like this in order to use 
delta2 = d2 * np.pi # ...d1 and d2 in the export file name

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(4,4), gridspec_kw={'width_ratios': [6, 1], 'height_ratios': [1, 6]})

for i in [ax1, ax2, ax3, ax4]: 
    i.set_yticklabels([])         # in order to remove the ticks and tick labels
    i.set_xticklabels([])
    i.set_xticks([])
    i.set_yticks([])
    i.set_xlim(-max_range, max_range)
    i.set_ylim(-max_range, max_range)
ax2.set_visible(False)

line, = ax3.plot(0, 0)        # line plot in the lower left
line2 = ax3.scatter(0, 0)     # moving dot in the lower left
linex = ax1.scatter(0, 0)     # moving dot on top
liney = ax4.scatter(0, 0)     # moving dot on the right

def animation_frame(i):
    ax1.clear()              # I tried to put this in a loop like this: 
    ax1.set_yticklabels([])  # for i in [ax1,ax3,ax4]: 
    ax1.set_xticklabels([])  #     i.clear() 
    ax1.set_xticks([])       #     (... etc.)
    ax1.set_yticks([])       # but this didn't work
    ax3.clear()
    ax3.set_yticklabels([])
    ax3.set_xticklabels([])
    ax3.set_xticks([])
    ax3.set_yticks([])
    ax4.clear()
    ax4.set_yticklabels([])
    ax4.set_xticklabels([])
    ax4.set_xticks([])
    ax4.set_yticks([])
    ax1.set_xlim(-max_range, max_range)     # after ax.clear() I apparently have to re-set these
    ax3.set_xlim(-max_range, max_range)
    ax3.set_ylim(-max_range, max_range)
    ax4.set_ylim(-max_range, max_range)
    x_data.append(np.sin(i * f1 + delta1)) # for the line plot
    y_data.append(np.sin(i * f2 + delta2))
    x_inst = np.sin(i * f1 + delta1)       # for the scatter plot
    y_inst = np.sin(i * f2 + delta2)
    line, = ax3.plot(x_data, y_data)
    line2 = ax3.scatter(x_inst, y_inst)
    linex = ax1.scatter(x_inst, 0)
    liney = ax4.scatter(0, y_inst)
    fig.canvas.draw()
    transFigure = fig.transFigure.inverted()       # in order to draw over 2 subplots
    coord1 = transFigure.transform(ax1.transData.transform([x_inst, 0]))
    coord2 = transFigure.transform(ax3.transData.transform([x_inst, y_inst]))
    my_line1 = matplotlib.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]), transform=fig.transFigure, linewidth=1, c='gray', alpha=0.5)
    coord1 = transFigure.transform(ax3.transData.transform([x_inst, y_inst]))
    coord2 = transFigure.transform(ax4.transData.transform([0, y_inst]))
    my_line2 = matplotlib.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]), transform=fig.transFigure, linewidth=1, c='gray', alpha=0.5)
    fig.lines = my_line1, my_line2,         # moving vertical and horizontal lines
    return line, line2, linex, liney

animation = FuncAnimation(fig, func=animation_frame, frames=np.linspace(0, 4*np.pi, num=800, endpoint=True), interval=1000)
animation.save('lissajous_{0}_{1}_{2:.2g}_{3:.2g}.gif'.format(f1,f2,d1,d2), writer='pillow', fps=50, dpi=200)
# This takes quite long, but since I'd like to have a smooth, slow animation, 
# ...I'm willing to accept a longer execution time. 

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.