Setup¶

In [1]:
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

Constants & Functions¶

In [2]:
#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)

Fiddler¶

In [3]:
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])
In [4]:
#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'); 
In [5]:
animate('Fiddler', 708, 1)
In [6]:
animate('EC', 892, 1)
Rohan Lewis¶

2025.03.17¶