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.
