Setup¶

In [1]:
import math
from math import pi as pi
from math import cos as cos
from math import sin as sin
from math import tan as tan
from math import sqrt as sR
import matplotlib
from matplotlib.animation import FuncAnimation as FA
from matplotlib import colormaps as cm
import matplotlib.colors as mCm
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

Functions¶

In [2]:
def getViridis(n):
    """
    Get a viridis color sequence.

    Parameters:
    n - Number of colors.
    
    Returns:
    List of n hex codes as strings.
    """
    
    return([mCm.to_hex(cm['viridis_r'].resampled(n).colors[v]) for v in range(n)])

colors = getViridis(7)


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 rotatingTriangle(fig, option, t) :
    
    """
    Draw a triangle

    Parameters:
    t - θ angle rotated clockwise
    
    Returns:
    Plot.
    """    

    if option != "EC" :
        figx = 7.5
        xlim = 0.9
        
    else :
        figx = 10
        xlim = 1.5
    
    #fig = plt.figure(figsize = (figx, 7.5))
    ax = fig.add_subplot(xlim = (-0.9, xlim),
                         ylim = (-0.9, 0.9))
    fig.subplots_adjust(left = 0, bottom = 0, right = 1, top = 1)
    
    #Remove axes and ticks.
    ax = clearAx(ax)

    #Circumradius of unit equilateral triangle.
    h = 1/sR(3)

    if option != "EC" :
        Xs = []
        Ys = []
    
        for a in [0+t, 120+t, 240+t] :
            Xs.append(h*sin(pi*a/180))
            Ys.append(h*cos(pi*a/180))
    
        xy = list(zip(Xs, Ys))
        #Triangle.
        ax.add_patch(mpP(xy,
                         ec = colors[2],
                         fc = 'None',
                         lw = 3))
    
        #Square.
        xmin = min(Xs)
        xmax = max(Xs)
        xr = xmax-xmin
    
        ymin = min(Ys)
        ymax = max(Ys)
        yr = ymax-ymin
    
        s = max(xr, yr) + .01
        xy = (xmax-s, ymax-s)
    
        ax.add_patch(mpR(xy,
                         s,
                         s,
                         ec = colors[6],
                         fc = 'None',
                         lw = 3))

        if t == 45 :

            text = [("1/√2", (-0.3, -0.55), colors[6]),
                    ("h-1/√2", (0.18, -0.55), colors[6]),
                    ("h", (0.35, -0.1), colors[6])]
                
            for tt in text :
                plt.annotate(text = tt[0],
                             xy = tt[1],
                             c = tt[2],
                             size = 20)
       
        fig.savefig("2026.09.25Fiddler" + str(t) + ".png", bbox_inches = 'tight') 

    
    #Animation!
    else :
        
        ax.scatter(0, 0,
                   c = 'k',
                   lw = 10,
                   zorder = 4)
        
        #Initial Triangle.
        Xs = []
        Ys = []
    
        for a in [45, 165, 285] :
            Xs.append(h*sin(pi*a/180))
            Ys.append(h*cos(pi*a/180))

        ax.add_patch(mpP(list(zip(Xs, Ys)),
                         ec = colors[1],
                         fc = 'None',
                         lw = 3))
        
        ax.scatter([Xs[0], Xs[2]],
                   [Ys[0], Ys[2]],
                   c = colors[1],
                   lw = 10)
        
        #Rotated Triangle.
        Xts = []
        Yts = []
    
        for a in [45+t, 165+t, 285+t] :
            Xts.append(h*sin(pi*a/180))
            Yts.append(h*cos(pi*a/180))
    
        ax.add_patch(mpP(list(zip(Xts, Yts)),
                         ec = colors[3],
                         fc = 'None',
                         lw = 3))
        
        ax.scatter(Xts[0:2],
                   Yts[0:2],
                   c = colors[3],
                   lw = 10)
        
        #Square.
        t0 = tan(pi*(45-t/2)/180)
        tr = tan(pi*(90-t/2)/180)
        tl = tan(pi*(180-t/2)/180)

        #Bottom left vertex of square.
        leftx = (Yts[1] - tl*Xts[1]) / (t0 - tl)
        lefty = t0 * leftx
        ax.scatter(leftx,
                   lefty,
                   c = colors[6],
                   lw = 10)        
        
        #Top right vertex of frame.
        rightx = (Yts[0] - tr*Xts[0]) / (t0 - tr)
        righty = t0 * rightx
        ax.scatter(rightx,
                   righty,
                   c = colors[6],
                   lw = 10)

        
        #The diagonal of the frame / √2 yields the side.
        s = sR((rightx-leftx)**2 + (righty-lefty)**2) / sR(2)
        #print(s)
        
        ax.add_patch(mpR((leftx, lefty),
                         s,
                         s,
                         angle = -t/2,
                         rotation_point = (leftx, lefty),
                         ec = colors[6],
                         fc = 'None',
                         lw = 3))
        

        if t == 37.3737 :
            
            #Original triangle angle.
            ax.plot([0, Xs[0]],
                    [0, Ys[0]],
                    c = colors[1],
                    lw = 3,
                    ls = ":")
            
            #Diagonal of frame.
            ax.plot([leftx, rightx],
                    [lefty, righty],
                    c = colors[6],
                    lw = 3,
                    ls = ":")
            
            #Rotated triangle angle.
            ax.plot([0, Xts[0]],
                    [0, Yts[0]],
                    c = colors[3],
                    lw = 3,
                    ls = ":")
            
            #Horizontal
            ax.plot([0, 1],
                    [0, 0],
                    c = "k",
                    lw = 3,
                    ls = ":")
            
            text = [("O", (0, 0.03), "k", "center", 30),
                
                    ("A", (Xs[0]+.03, Ys[0]), colors[1], "left", 30),
                    ("B", (Xs[1]+.03, Ys[1]), colors[1], "left", 30),
                    ("C", (Xs[2]-.03, Ys[2]), colors[1], "right", 30),
                    
                    ("A'", (Xts[0]+.03, Yts[0]), colors[3], "left", 30),
                    ("B'", (Xts[1]-.03, Yts[1]), colors[3], "right", 30),
                    ("C'", (Xts[2]+.03, Yts[2]), colors[3], "left", 30),
                   
                    ("D", (1, 0), "k", "left", 30),
                    
                    ("E", (rightx+.03, righty), colors[6], "left", 30),
                    ("F", (righty+.03, leftx), colors[6], "left", 30),
                    ("G", (leftx-.03, lefty), colors[6], "right", 30),
                    ("H", (-righty-.03, -leftx), colors[6], "right", 30)]
            
            for tt in text :
                plt.annotate(text = tt[0],
                             xy = tt[1],
                             c = tt[2],
                             ha = tt[3],
                             size = tt[4])
                
                fig.savefig("2026.09.25EC.png", bbox_inches = 'tight') 
        
        #Animation!
        else :
            
            text = [("Rotation :", (1.5, 0.4), colors[3], "right", 50),
                    ('{0:.2f}'.format(t), (1.5, 0.2), colors[3], "right", 50),
                    ("Side :", (1.5, -0.3), colors[6], "right", 50),
                    ('{0:.4f}'.format(s), (1.5, -0.5), colors[6], "right", 50)]
                
            for tt in text :
                plt.annotate(text = tt[0],
                             xy = tt[1],
                             c = tt[2],
                             ha = tt[3],
                             size = tt[4])
In [3]:
rotatingTriangle("Fiddler", 0)
In [4]:
rotatingTriangle("Fiddler", 45)
In [5]:
rotatingTriangle('EC', 37.3737)
In [6]:
h = 1/sR(3)

S = []

for t in range(12000) :
    t = t/100

    Xts = []
    Yts = []
    
    for a in [45+t, 165+t, 285+t] :
        Xts.append(h*sin(pi*a/180))
        Yts.append(h*cos(pi*a/180))

    #Square.
    t0 = tan(pi*(45-t/2)/180)
    tr = tan(pi*(90-t/2)/180)
    tl = tan(pi*(180-t/2)/180)

    rightx = (Yts[0] - tr*Xts[0]) / (t0 - tr)
    righty = t0 * rightx

    leftx = (Yts[1] - tl*Xts[1]) / (t0 - tl)
    lefty = t0 * leftx

    s = sR((rightx-leftx)**2 + (righty-lefty)**2) / sR(2)

    S.append(s)

sum(S)/12000
Out[6]:
1.06508563705874
In [3]:
#Animate. 
def rotateFootball() :
    
    fig = plt.figure(figsize = (10, 7.5))
    fig.set_tight_layout(True)
    
    def animate(i):
        
        plt.clf()-++++++
        rotatingTriangle(fig, 'EC', i/25)
                       
    #Run animation.
    anim = FA(fig, animate,
              frames = 3001,
              interval = 5)   

    #Save animation.
    anim.save('2026.09.25EC.mp4'); 
    
rotateFootball()
In [12]:
rotatingTriangle('EC', 37.37)

Rohan Lewis¶

2026.09.29¶