import matplotlib
from matplotlib.animation import FuncAnimation as FA
from matplotlib.patches import Polygon as mpP
from matplotlib.patches import Rectangle as mpR
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
#Colors :
c = ['#575757FF',
'#EEEEEEFF',
'#482677FF',
'#55C667FF',
'#238A8DFF']
#Clear axes.
def clearAx(ax):
"""
Clear axes.
Parameters:
ax - Current ax.
Returns:
ax - Updated ax.
"""
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["bottom"].set_visible(False)
ax.spines["left"].set_visible(False)
ax.set_xticks([])
ax.set_yticks([])
return(ax)
def getLamina(i) :
"""
Get center of mass.
Parameters:
i - θ, angle from r = 0.
Returns:
p - List of ordered pairs defining perimeter of lamina.
"""
#Initialize base.
p = [(1, 0)]
#Create a list of angles between 0 and i.
a0 = np.linspace(0, np.pi*i/180, 200)
#Partial circumference at r = 2.
outer = [(2*np.cos(t), 2*np.sin(t)) for t in a0]
#Partial circumference at r = 1.
inner = list(reversed([(np.cos(t), np.sin(t)) for t in a0]))
p += p + outer + inner
return(p)
def getXY(o, i) :
"""
Calculate center of mass.
Parameters:
option - Fiddler or EC.
i - Growth increment.
Fiddler draws rectangle based towers, with "L".
EC draws a lamina tower with "θ".
Returns:
XY - List of ordered pairs of (X, Y), center of mass, from 0 to i.
"""
XY = []
for j in [k*i/100 for k in range(101)] :
if o == 'Fiddler' :
XY += [((-j**2/2 + j + 3) / (j + 2),
(7*j/4 + 2) / (j + 2))]
else :
if j == 0 :
XY = [(14/9, 0)]
else :
t = np.pi*j/180
XY += [(14*np.sin(t) / (9*t),
14*(1-np.cos(t)) / (9*t))]
return(XY)
def growingTower(fig, o, i) :
"""
Plot the growing tower.
Parameters:
o - option, Fiddler or EC.
i - Growth increment.
Fiddler draws rectangle based towers, with "L".
EC draws a lamina tower with "θ".
Returns:
Plot of tower with variable value and center of mass.
"""
sns.set()
#fig = plt.figure(figsize = (8, 5))
ax = fig.add_subplot(xlim = (-0.5, 3), ylim = (-1/16, 17/8))
fig.subplots_adjust(left = 0, bottom = 0, right = 1, top = 1)
#Remove axes and ticks.
ax = clearAx(ax)
#Fiddler.
if o == 'Fiddler' :
#Fiddler specific.
i = i/500
variable = 'L'
com_format = f"{i:.3f}"
#Tower.
ax.add_patch(mpR(xy = (1, 0),
width = 1,
height = 2,
edgecolor = c[0],
facecolor = c[1]))
#Side block.
ax.add_patch(mpR(xy = (1-i, 1),
width = i,
height = 1,
edgecolor = c[0],
facecolor = c[1]))
#----L----
ax.plot([1-i, 1-i/2-.05], [0.95, 0.95], color = c[2], lw = 1, ls = ':')
text = [(variable, (1-i/2, 0.93), c[2], 0)]
ax.plot([1-i/2+.05, 1], [0.95, 0.95], color = c[2], lw = 1, ls = ':')
#Extra Credit.
else :
i = i/10
variable = 'θ'
com_format = f"{i:.1f}"
#Tower.
ax.add_patch(mpP(xy = getLamina(i),
edgecolor = c[0],
facecolor = c[1]))
#<θ.
ax.plot([0, 1], [0, 0], color = c[2], lw = 1, ls = ':')
text = [(variable, (0.3*np.cos(np.pi*i/360), 0.3*np.sin(np.pi*i/360)), c[2], 0)]
ax.plot([0, np.cos(np.pi*i/180)], [0, np.sin(np.pi*i/180)], color = c[2], lw = 1, ls = ':')
#x = 1.
text += [("x = 1", (0.95, np.sin(np.pi*i/180)), c[4], 90)]
ax.plot([1, 1], [0, 17/8], color = c[4], lw = 2, ls = ':')
#Center of Mass
XY = getXY(o, i)
X, Y = XY[-1]
ax.scatter(X, Y, color = c[3], lw = 10)
#Trace all center of mass.
XY = np.array(XY).T
ax.plot(XY[0], XY[1], color = c[3], lw = 1)
text += [(variable + ' :', (2.5, 1.65), c[2], 0),
(com_format, (2.5, 1.5), c[2], 0),
("Center of Mass :", (2.5, 1), c[3], 0),
(f"({X:.3f}, {Y:.3f})", (2.5, 0.85), c[3], 0)]
for t in text :
plt.annotate(text = t[0],
xy = t[1],
c = t[2],
size = 22,
ha = 'center',
va = 'center',
rotation = t[3])
#Animate
def animate(option, frames, intervals) :
sns.set()
fig = plt.figure(figsize = (8, 5))
def animate(i):
plt.clf()
growingTower(fig, option, i)
#Run animation.
anim = FA(fig, animate,
frames = frames,
interval = 20)
#Save animation.
anim.save('2025.12.12' + option + '.mp4');
animate('Fiddler', 708, 1)
animate('EC', 892, 1)