使用 Python 和 Tkinter 在傅里叶 Series/Transform 中错位圆
Missalgined circles in Fourier Series/Transform using Python and Tkinter
我制作了一个 Fourier Series/Transform Tkinter 应用程序,到目前为止,一切都按我想要的方式运行,只是我遇到了圆圈未对齐的问题。
这是一张解释我的问题的图片(事后添加了绿色和粉红色以更好地解释问题):
我已将问题缩小到线条的开头,因为它们似乎在正确的位置结束,圆圈也在正确的位置。
正确位置和线条开始位置之间的距离似乎在拉长,但实际上与圆圈旋转的速度成正比,因为圆圈旋转的幅度越大,因此越快。
代码如下:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
我知道这不是实现我想要实现的目标的最佳方法,因为使用 类 会好得多。我打算这样做,以防万一没人能找到解决方案,并希望重写时,这个问题不会持续存在。
您面临的主要问题是您从计算中收到浮点数,但您只能对像素使用整数。下面我将向您展示您失败的地方以及解决问题的最快方法。
首先你的目标是连接线,你在这里计算点数:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
当您将以下代码添加到此函数中时,您会发现它在那里失败了。
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
因为 x
和 y
应该始终与最后一点(上一行的末尾)匹配,即 endCoord[i-1][0]
和 endCoord[i-1][1]
.
为了解决您的问题,我只是跳过了 后续行 起点的匹配,并使用以下替代函数获取了前一行的坐标:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
其他建议是:
- 不要使用通配符导入
- 只导入您在代码中真正使用的内容
random
您的示例中没有使用
- 在
global
命名空间中使用 global 是没有用的
- 创建函数以避免重复代码
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
- 使用
.after(ms,func,*args)
而不是中断 while
循环和阻塞调用 time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
- 阅读PEP 8 -- Style Guide for Python
- 使用直观的变量名让您的代码在未来更易于他人和您自己阅读
list_of_radii = [200] #instead of rList
- 如前所述,像素将用整数表示,而不是浮点数
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
- 如果您想显示更多循环,对每个动画使用 类 和 canvas 会很方便
正如@Thingamabobs 所说,未对齐的主要原因是像素坐标使用整数值。我对您的项目感到兴奋,并决定使用 matplotlib 制作一个示例,这样我就不必使用坐标的整数值。该示例适用于任何函数,我使用正弦波、方波和锯齿波函数实现了示例。
我也尝试遵循一些命名、类型注释等方面的良好做法,希望对您有所帮助
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()
我制作了一个 Fourier Series/Transform Tkinter 应用程序,到目前为止,一切都按我想要的方式运行,只是我遇到了圆圈未对齐的问题。 这是一张解释我的问题的图片(事后添加了绿色和粉红色以更好地解释问题):
我已将问题缩小到线条的开头,因为它们似乎在正确的位置结束,圆圈也在正确的位置。 正确位置和线条开始位置之间的距离似乎在拉长,但实际上与圆圈旋转的速度成正比,因为圆圈旋转的幅度越大,因此越快。
代码如下:
from tkinter import *
import time
import math
import random
root = Tk()
myCanvas = Canvas(root, width=1300, height=750)
myCanvas.pack()
myCanvas.configure(bg="#0A2239")
global x,y, lines, xList, yList
NumOfCircles = 4
rList = [200]
n=3
for i in range(0, NumOfCircles):
rList.append(rList[0]/n)
n=n+2
print(rList)
num = 250/sum(rList)
for i in range(0, NumOfCircles):
rList[i] = rList[i]*num
x=0
y=0
lines = []
circles = []
centerXList = [300]
for i in range(0,NumOfCircles):
centerXList.append(0)
centerYList = [300]
for i in range(0,NumOfCircles):
centerYList.append(0)
xList = [0]*NumOfCircles
yList = [0]*NumOfCircles
waveLines = []
wavePoints = []
con=0
endCoord = []
for i in range(0, NumOfCircles):
endCoord.append([0,0])
lastX = 0
lastY = 0
count = 0
randlist = []
n=1
for i in range(0, NumOfCircles):
randlist.append(200/n)
n=n+2
def createCircle(x, y, r, canvasName):
x0 = x - r
y0 = y - r
x1 = x + r
y1 = y + r
return canvasName.create_oval(x0, y0, x1, y1, width=r/50, outline="#094F9A")
def updateCircle(i):
newX = endCoord[i-1][0]
newY = endCoord[i-1][1]
centerXList[i] = newX
centerYList[i] = newY
x0 = newX - rList[i]
y0 = newY - rList[i]
x1 = newX + rList[i]
y1 = newY + rList[i]
myCanvas.coords(circles[i], x0, y0, x1, y1)
def circleWithLine(i):
global line, lines
circle = createCircle(centerXList[i], centerYList[i], rList[i], myCanvas)
circles.append(circle)
line = myCanvas.create_line(centerXList[i], centerYList[i], centerXList[i], centerYList[i], width=2, fill="#1581B7")
lines.append(line)
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
def lineBetweenTwoPoints(x, y, x2, y2):
line = myCanvas.create_line(x, y, x2, y2, fill="white")
return line
def lineForWave(y1, y2, y3, y4, con):
l = myCanvas.create_line(700+con, y1, 702+con, y2, 704+con, y3, 706+con, y4, smooth=1, fill="white")
waveLines.append(l)
for i in range(0,NumOfCircles):
circleWithLine(i)
myCanvas.create_line(700, 20, 700, 620, fill="black", width = 3)
myCanvas.create_line(700, 300, 1250, 300, fill="red")
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 0.5)
myCanvas.create_line(300, 0, 300, 600, fill="red", width = 0.5)
while True:
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
time.sleep(0.01)
root.mainloop()
我知道这不是实现我想要实现的目标的最佳方法,因为使用 类 会好得多。我打算这样做,以防万一没人能找到解决方案,并希望重写时,这个问题不会持续存在。
您面临的主要问题是您从计算中收到浮点数,但您只能对像素使用整数。下面我将向您展示您失败的地方以及解决问题的最快方法。
首先你的目标是连接线,你在这里计算点数:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
myCanvas.coords(lines[i], x, y, endCoord[i][0], endCoord[i][1])
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
当您将以下代码添加到此函数中时,您会发现它在那里失败了。
if i != 0:
print(i,x,y)
print(i,endCoord[i-1][0], endCoord[i-1][1])
因为 x
和 y
应该始终与最后一点(上一行的末尾)匹配,即 endCoord[i-1][0]
和 endCoord[i-1][1]
.
为了解决您的问题,我只是跳过了 后续行 起点的匹配,并使用以下替代函数获取了前一行的坐标:
def update(i, x, y):
endCoord[i][0] = x+(rList[i]*math.cos(xList[i]))
endCoord[i][1] = y+(rList[i]*math.sin(yList[i]))
if i == 0:
points = x, y, endCoord[i][0], endCoord[i][1]
else:
points = endCoord[i-1][0], endCoord[i-1][1], endCoord[i][0], endCoord[i][1]
myCanvas.coords(lines[i], *points)
xList[i] += (math.pi/randlist[i])
yList[i] += (math.pi/randlist[i])
其他建议是:
- 不要使用通配符导入
- 只导入您在代码中真正使用的内容
random
您的示例中没有使用 - 在
global
命名空间中使用 global 是没有用的 - 创建函数以避免重复代码
def listinpt_times_circles(inpt):
return [inpt]*CIRCLES
x_list = listinpt_times_circles(0)
y_list = listinpt_times_circles(0)
center_x_list = listinpt_times_circles(0)
center_x_list.insert(0,300)
center_y_list = listinpt_times_circles(0)
center_y_list.insert(0,300)
- 使用
.after(ms,func,*args)
而不是中断while
循环和阻塞调用time.sleep
def animate():
global count,con,lastX,lastY
for i in range(0, len(lines)):
update(i, centerXList[i], centerYList[i])
for i in range(1, len(lines)):
updateCircle(i)
if count >= 8:
lineBetweenTwoPoints(lastX, lastY, endCoord[i][0], endCoord[i][1])
if count % 6 == 0 and con<550:
lineForWave(wavePoints[-7],wavePoints[-5],wavePoints[-3],wavePoints[-1], con)
con += 6
wavePoints.append(endCoord[i][1])
myCanvas.update_idletasks()
lastX = endCoord[i][0]
lastY = endCoord[i][1]
if count != 108:
count += 1
else:
count = 8
root.after(10,animate)
animate()
root.mainloop()
- 阅读PEP 8 -- Style Guide for Python
- 使用直观的变量名让您的代码在未来更易于他人和您自己阅读
list_of_radii = [200] #instead of rList
- 如前所述,像素将用整数表示,而不是浮点数
myCanvas.create_line(0, 300, 600, 300, fill="red", width = 1) #0.5 has no effect compare 0.1 to 1
- 如果您想显示更多循环,对每个动画使用 类 和 canvas 会很方便
正如@Thingamabobs 所说,未对齐的主要原因是像素坐标使用整数值。我对您的项目感到兴奋,并决定使用 matplotlib 制作一个示例,这样我就不必使用坐标的整数值。该示例适用于任何函数,我使用正弦波、方波和锯齿波函数实现了示例。
我也尝试遵循一些命名、类型注释等方面的良好做法,希望对您有所帮助
from numbers import Complex
from typing import Callable, Iterable, List
import matplotlib.pyplot as plt
import numpy as np
def fourier_series_coeff_numpy(f: Callable, T: float, N: int) -> List[Complex]:
"""Get the coefficients of the Fourier series of a function.
Args:
f (Callable): function to get the Fourier series coefficients of.
T (float): period of the function.
N (int): number of coefficients to get.
Returns:
List[Complex]: list of coefficients of the Fourier series.
"""
f_sample = 2 * N
t, dt = np.linspace(0, T, f_sample + 2, endpoint=False, retstep=True)
y = np.fft.fft(f(t)) / t.size
return y
def evaluate_fourier_series(coeffs: List[Complex], ang: float, period: float) -> List[Complex]:
"""Evaluate a Fourier series at a given angle.
Args:
coeffs (List[Complex]): list of coefficients of the Fourier series.
ang (float): angle to evaluate the Fourier series at.
period (float): period of the Fourier series.
Returns:
List[Complex]: list of complex numbers representing the Fourier series.
"""
N = np.fft.fftfreq(len(coeffs), d=1/len(coeffs))
N = filter(lambda x: x >= 0, N)
y = 0
radius = []
for n, c in zip(N, coeffs):
r = 2 * c * np.exp(1j * n * ang / period)
y += r
radius.append(r)
return radius
def square_function_factory(period: float):
"""Builds a square function with given period.
Args:
period (float): period of the square function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 if x % period < period / 2 else -1.0 for x in t]
elif isinstance(t, float):
return 1.0 if t % period < period / 2 else -1.0
return f
def saw_tooth_function_factory(period: float):
"""Builds a saw-tooth function with given period.
Args:
period (float): period of the saw-tooth function.
"""
def f(t):
if isinstance(t, Iterable):
return [1.0 - 2 * (x % period / period) for x in t]
elif isinstance(t, float):
return 1.0 - 2 * (t % period / period)
return f
def main():
PERIOD = 1
GRAPH_RANGE = 3.0
N_COEFFS = 30
f = square_function_factory(PERIOD)
# f = lambda t: np.sin(2 * np.pi * t / PERIOD)
# f = saw_tooth_function_factory(PERIOD)
coeffs = fourier_series_coeff_numpy(f, 1, N_COEFFS)
radius = evaluate_fourier_series(coeffs, 0, 1)
fig, axs = plt.subplots(nrows=1, ncols=2, sharey=True, figsize=(10, 5))
ang_cum = []
amp_cum = []
for ang in np.linspace(0, 2*np.pi * PERIOD * 3, 200):
radius = evaluate_fourier_series(coeffs, ang, 1)
x = np.cumsum([x.imag for x in radius])
y = np.cumsum([x.real for x in radius])
x = np.insert(x, 0, 0)
y = np.insert(y, 0, 0)
axs[0].plot(x, y)
axs[0].set_ylim(-GRAPH_RANGE, GRAPH_RANGE)
axs[0].set_xlim(-GRAPH_RANGE, GRAPH_RANGE)
ang_cum.append(ang)
amp_cum.append(y[-1])
axs[1].plot(ang_cum, amp_cum)
axs[0].axhline(y=y[-1],
xmin=x[-1] / (2 * GRAPH_RANGE) + 0.5,
xmax=1.2,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
min_x, max_x = axs[1].get_xlim()
line_end_x = (ang - min_x) / (max_x - min_x)
axs[1].axhline(y=y[-1],
xmin=-0.2,
xmax=line_end_x,
c="black",
linewidth=1,
zorder=0,
clip_on=False)
plt.pause(0.01)
axs[0].clear()
axs[1].clear()
if __name__ == '__main__':
main()