Как встроить трехмерный анимированный сюжет с помощью tkinter?
Я новичок в использовании tkinter и хотел создать небольшой пользовательский интерфейс для практики. Я выбрал построение системы Лоренца, позволяя пользователю настраивать входные параметры.
Мне удалось подключить диаграмму и поля ввода, и я могу показать статический график. Следующим моим шагом было попробовать показать анимированный сюжет. Однако здесь я столкнулся с проблемами. Кажется, мне не удается отобразить анимацию на моей встроенной фигуре. В приведенном ниже коде вы можете видеть, что я изменил
plot_animated()
функция для создания новой фигуры и осей, и она отлично работает, но когда я меняю их, чтобы использовать
self.figure
а также
self.ax
на сюжете ничего не видно.
Ниже представлена моя реализация пользовательского интерфейса:
from tkinter import *
from matplotlib import animation
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.pyplot as plt
from lorenz_model import run_lorenz
class LorenzUI:
input_width = 10
def __init__(self):
self.root = Tk()
self.root.title("Lorenz Attractor")
self.root.geometry("1290x960")
self.chart_fr = Frame(self.root)
self.figure = plt.Figure()
self.figure.tight_layout()
self.ax = p3.Axes3D(self.figure)
self.canvas = FigureCanvasTkAgg(self.figure, master=self.chart_fr)
self.canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew")
self.input_fr = Frame(self.root, relief=RAISED, bd=2)
self.init_x_lbl = Label(self.input_fr, text="Initial X")
self.init_x_input = Entry(self.input_fr, width=self.input_width)
self.init_y_lbl = Label(self.input_fr, text="Initial Y")
self.init_y_input = Entry(self.input_fr, width=self.input_width)
self.init_z_lbl = Label(self.input_fr, text="Initial Z")
self.init_z_input = Entry(self.input_fr, width=self.input_width)
self.rho_lbl = Label(self.input_fr, text="Rho")
self.rho_input = Entry(self.input_fr, width=self.input_width)
self.sigma_lbl = Label(self.input_fr, text="Sigma")
self.sigma_input = Entry(self.input_fr, width=self.input_width)
self.beta_lbl = Label(self.input_fr, text="Beta")
self.beta_input = Entry(self.input_fr, width=self.input_width)
self.time_lbl = Label(self.input_fr, text="Sim Time")
self.time_input = Entry(self.input_fr, width=self.input_width)
self.is_animated = IntVar(value=1)
self.animate_chk = Checkbutton(self.input_fr, text="Animate?", variable=self.is_animated)
self.run_btn = Button(self.input_fr, text="Run", command=self.generate_model)
self.stop_btn = Button(self.input_fr, text="Stop")
self.reset_btn = Button(self.input_fr, text="Reset Defaults", command=self.set_defaults)
self.root.rowconfigure(0, minsize=50, weight=1)
self.root.columnconfigure(0, minsize=800, weight=1)
self.init_x_lbl.grid(row=0, column=0, padx=5, pady=5)
self.init_x_input.grid(row=0, column=1, padx=5, pady=5)
self.init_y_lbl.grid(row=1, column=0, padx=5, pady=5)
self.init_y_input.grid(row=1, column=1, padx=5, pady=5)
self.init_z_lbl.grid(row=2, column=0, padx=5, pady=5)
self.init_z_input.grid(row=2, column=1, padx=5, pady=5)
self.rho_lbl.grid(row=4, column=0, padx=5, pady=5)
self.rho_input.grid(row=4, column=1, padx=5, pady=5)
self.sigma_lbl.grid(row=5, column=0, padx=5, pady=5)
self.sigma_input.grid(row=5, column=1, padx=5, pady=5)
self.beta_lbl.grid(row=6, column=0, padx=5, pady=5)
self.beta_input.grid(row=6, column=1, padx=5, pady=5)
self.time_lbl.grid(row=7, column=0, padx=5, pady=5)
self.time_input.grid(row=7, column=1, padx=5, pady=5)
self.animate_chk.grid(row=8, column=0, padx=5, pady=5)
self.run_btn.grid(row=9, column=0, padx=5, pady=5)
self.stop_btn.grid(row=9, column=1, padx=5, pady=5)
self.reset_btn.grid(row=10, column=0, padx=5, pady=5)
self.chart_fr.grid(row=0, column=0, sticky="nse")
self.input_fr.grid(row=0, column=1, sticky="nsew")
self.set_defaults()
@staticmethod
def __set_default(field, value):
field.delete(0, END)
field.insert(0, value)
def set_defaults(self):
self.__set_default(self.init_x_input, 1.0)
self.__set_default(self.init_y_input, 1.0)
self.__set_default(self.init_z_input, 1.0)
self.__set_default(self.rho_input, 28.0)
self.__set_default(self.sigma_input, 10.0)
self.__set_default(self.beta_input, 2.666667)
self.__set_default(self.time_input, 40.0)
self.animate_chk.select()
def generate_model(self):
x = float(self.init_x_input.get())
y = float(self.init_y_input.get())
z = float(self.init_z_input.get())
rho = float(self.rho_input.get())
sigma = float(self.sigma_input.get())
beta = float(self.beta_input.get())
t = float(self.time_input.get())
dt = 0.01 # TODO Needed as Input?
self.model = run_lorenz([x, y, z], t, dt, rho, sigma, beta)
if self.is_animated.get():
self.__plot_animated()
else:
self.__plot_static()
def __plot_animated(self):
figure = plt.figure()
ax = p3.Axes3D(figure)
ax.clear()
ax.set_xlim3d([min(self.model[0]), max(self.model[0])])
ax.set_ylim3d([min(self.model[1]), max(self.model[1])])
ax.set_zlim3d([min(self.model[2]), max(self.model[2])])
n_frames = len(self.model[0])
line, = ax.plot(self.model[0, 0:1], self.model[1, 0:1], self.model[2, 0:1])
self.ani = animation.FuncAnimation(figure, self.__animate, n_frames, fargs=(self.model, line),
interval=1, blit=False)
plt.show()
def __animate(self, num, data, line):
line.set_data(data[:2, :num])
line.set_3d_properties(data[2, :num])
def __plot_static(self):
self.ax.clear()
self.ax.plot(self.model[0, :], self.model[1, :], self.model[2, :])
self.canvas.draw()
def run(self):
self.root.mainloop()
У меня есть отдельный файл для расчета модели, который состоит из следующих двух функций:
import numpy as np
from scipy.integrate import odeint
def f(state, t, rho, sigma, beta):
x, y, z = state # Unpack the state vector
return sigma * (y - x), x * (rho - z) - y, x * y - beta * z # Derivatives
def run_lorenz(initial_state, t, dt, rho, sigma, beta):
times = np.arange(0.0, t, dt)
result = odeint(f, initial_state, times, args=(rho, sigma, beta))
return np.array(np.array(result).T)
и, наконец, один главный файл, в котором запускается приложение.
from lorenz_ui import LorenzUI
if __name__ == '__main__':
app = LorenzUI()
app.run()
Так что у меня действительно есть 2 вопроса.
- Как сделать так, чтобы анимация отображалась во встроенном рисунке, а не создавала новую?
- Если вы запустите это, вы увидите, что график отображается только в правом верхнем углу доступного пространства, как заставить его расти и заполнять все доступное пространство?
Заранее благодарю за любую помощь.