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
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])
rotatingTriangle("Fiddler", 0)
rotatingTriangle("Fiddler", 45)
rotatingTriangle('EC', 37.3737)
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
1.06508563705874
#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()
rotatingTriangle('EC', 37.37)