J'ai commencé à créer une application Tkinter et j'utilisais initialement les Figure
et figure.add_subplot
de matplotlib. Avec cela, tout fonctionne parfaitement. Pour plus de personnalisation, je souhaite maintenant passer à pyplot
et subplot2grid
, mais ce faisant, soudainement, toute ma variable tkinter cesse de fonctionner.
Dans mon MWE, la variable gArrChoice
suit quel bouton radio est sélectionné et doit par défaut être la première option. Sur la base de cette option, le graphique doit tracer une ligne planant autour de 0,1. Si la deuxième option est sélectionnée, le graphique devrait changer pour passer autour de 5. Le graphique se met à jour automatiquement toutes les 2,5 secondes. Si vous commentez les 3 lignes sous «Working» et utilisez les 3 lignes «Not Working» à la place, les paramètres par défaut de la variable cessent de fonctionner et la commutation entre les boutons radio n'a plus d'effet. Déclarer une fonction à l'intérieur de la fonction animate ne change pas le problème.
Comment puis-je utiliser plt avec Tkinter et ne pas détruire mes variables?
MWE:
import tkinter as tk import matplotlib matplotlib.use("TkAgg") #make sure you use the tkinter backend from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.animation as animation import numpy as np gArrChoice = 0 #Working - using Figure and add_subplot from matplotlib.figure import Figure f = Figure() a = f.add_subplot(121) #Not Working - using plt and subplot2grid # from matplotlib import pyplot as plt # f = plt.figure() # a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5) class BatSimGUI(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) container = tk.Frame(self) container.pack(side="top", fill="both", expand=True) self.frames = {} frame = StartPage(container,self) self.frames[StartPage] = frame frame.grid(row=0, column=0, sticky="nsew") frame.tkraise() class StartPage(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) #Set defaults for global variable global gArrChoice gArrChoice = tk.IntVar() gArrChoice.set(1) radioArr1 = tk.Radiobutton(self, variable=gArrChoice, text="Exponential", value=1, command= lambda: print(gArrChoice.get())) radioArr1.grid(row=2, column=0) radioArr2 = tk.Radiobutton(self, variable=gArrChoice, text="Normal", value=2, command= lambda: print(gArrChoice.get())) radioArr2.grid(row=3, column=0) #Add Canvas canvas = FigureCanvasTkAgg(f, self) canvas.draw() canvas.get_tk_widget().grid(row=1, column=1, columnspan=7, rowspan = 10) def animate(i): global gArrChoice if gArrChoice.get() == 1: lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1) else: lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1) a.clear() a.step(list(range(100)), list(lam)) #Actually run the interface app = BatSimGUI() app.geometry("800x600") ani = animation.FuncAnimation(f, animate, interval = 2500) app.mainloop()
3 Réponses :
Il semble y avoir un bogue sur la mise à jour de IntVar ()
lorsque vous utilisez à la place pyplot
. Mais vous pouvez contourner le problème si vous forcez un changement de valeur dans vos boutons radio:
import tkinter as tk import matplotlib matplotlib.use("TkAgg") from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import matplotlib.animation as animation import numpy as np from matplotlib import pyplot as plt class BatSimGUI(tk.Tk): def __init__(self, *args, **kwargs): tk.Tk.__init__(self, *args, **kwargs) container = tk.Frame(self) container.pack(side="top", fill="both", expand=True) self.frames = {} self.start_page = StartPage(container,self) self.frames[StartPage] = self.start_page self.start_page.grid(row=0, column=0, sticky="nsew") self.start_page.tkraise() class StartPage(tk.Frame): def __init__(self, parent, controller): tk.Frame.__init__(self, parent) self.gArrChoice = tk.IntVar() self.gArrChoice.set(1) radioArr1 = tk.Radiobutton(self, variable=self.gArrChoice, text="Exponential", value=1) radioArr1.grid(row=2, column=0) radioArr2 = tk.Radiobutton(self, variable=self.gArrChoice, text="Normal", value=2) radioArr2.grid(row=3, column=0) self.f = plt.figure() self.a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5) canvas = FigureCanvasTkAgg(self.f, self) canvas.draw() canvas.get_tk_widget().grid(row=1, column=1, columnspan=7, rowspan = 10) def animate(self,i): if self.gArrChoice.get() == 1: lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1) else: lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1) self.a.clear() self.a.step(list(range(100)), list(lam)) app = BatSimGUI() app.geometry("800x600") ani = animation.FuncAnimation(app.start_page.f, app.start_page.animate, interval=1000) app.mainloop()
Ou vous pouvez créer votre IntVar
comme attribut de StartPage à la place qui semble fonctionner très bien.
radioArr1 = tk.Radiobutton(self, variable=gArrChoice, text="Exponential", value=1, command= lambda: gArrChoice.set(1)) radioArr2 = tk.Radiobutton(self, variable=gArrChoice, text="Normal", value=2, command= lambda: gArrChoice.set(2))
Oui, le problème semble provenir de la mise à jour de la variable. Ce problème se produit également avec les autres types de variables tkinter. Pour les boutons radio, vos suggestions fonctionnent, mais avez-vous une idée de comment faire quelque chose de similaire pour les champs d'entrée (en fin de compte, les paramètres saisis dans le champ Entrée doivent être utilisés pour exécuter la fonction d'animation. Étant donné que les champs d'entrée n'ont pas de fonction de commande qui est exécuté à l'entrée je ne vois pas comment prendre soin de cela.
Vous pouvez probablement lier un événement à votre entrée
, mais il semble que si vous mettez votre IntVar comme attribut de votre classe à la place, cela fonctionnerait très bien. J'ai mis à jour ma réponse.
Je pense qu'une approche OO serait meilleure.
Voir ci-dessous, j'ai utilisé le fil et la file d'attente pour gérer l'animation du tracé, vous pouvez même définir l'intervalle de temps et changer à la volée le type de graphique p>
Bon travail quand même, très intéressant
#!/usr/bin/python3 import tkinter as tk from tkinter import ttk from tkinter import messagebox import threading import queue import time from matplotlib.figure import Figure import matplotlib.animation as animation from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg try: from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk as nav_tool except: from matplotlib.backends.backend_tkagg import NavigationToolbar2TkAgg as nav_tool import numpy as np class MyThread(threading.Thread): def __init__(self, queue, which, ops, interval): threading.Thread.__init__(self) self.queue = queue self.check = True self.which = which self.ops = ops self.interval = interval def stop(self): self.check = False def run(self): while self.check: if self.which.get() ==0: lam = np.random.exponential(scale=.1, size = 100).reshape(-1,1) else: lam = np.random.normal(loc=5, scale=1, size = 100).reshape(-1,1) time.sleep(self.interval.get()) args = (lam, self.ops[self.which.get()]) self.queue.put(args) else: args = (None, "I'm stopped") self.queue.put(args) class Main(ttk.Frame): def __init__(self, parent): super().__init__() self.parent = parent self.which = tk.IntVar() self.interval = tk.DoubleVar() self.queue = queue.Queue() self.my_thread = None self.init_ui() def init_ui(self): f = ttk.Frame() #create graph! self.fig = Figure() self.fig.suptitle("Hello Matplotlib", fontsize=16) self.a = self.fig.add_subplot(111) self.canvas = FigureCanvasTkAgg(self.fig, f) toolbar = nav_tool(self.canvas, f) toolbar.update() self.canvas._tkcanvas.pack(fill=tk.BOTH, expand=1) w = ttk.Frame() ttk.Button(w, text="Animate", command=self.launch_thread).pack() ttk.Button(w, text="Stop", command=self.stop_thread).pack() ttk.Button(w, text="Close", command=self.on_close).pack() self.ops = ('Exponential','Normal',) self.get_radio_buttons(w,'Choice', self.ops, self.which,self.on_choice_plot).pack(side=tk.TOP, fill=tk.Y, expand=0) ttk.Label(w, text = "Interval").pack() tk.Spinbox(w, bg='white', from_=1.0, to=5.0,increment=0.5, justify=tk.CENTER, width=8, wrap=False, insertwidth=1, textvariable=self.interval).pack(anchor=tk.CENTER) w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=1) f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) def launch_thread(self): self.on_choice_plot() def stop_thread(self): if self.my_thread is not None: if(threading.active_count()!=0): self.my_thread.stop() def on_choice_plot(self, evt=None): if self.my_thread is not None: if (threading.active_count()!=0): self.my_thread.stop() self.my_thread = MyThread(self.queue,self.which, self.ops, self.interval) self.my_thread.start() self.periodiccall() def periodiccall(self): self.checkqueue() if self.my_thread.is_alive(): self.after(1, self.periodiccall) else: pass def checkqueue(self): while self.queue.qsize(): try: args = self.queue.get() self.a.clear() self.a.grid(True) if args[0] is not None: self.a.step(list(range(100)), list(args[0])) self.a.set_title(args[1], weight='bold',loc='left') else: self.a.set_title(args[1], weight='bold',loc='left') self.canvas.draw() except queue.Empty: pass def get_radio_buttons(self, container, text, ops, v, callback=None): w = ttk.LabelFrame(container, text=text,) for index, text in enumerate(ops): ttk.Radiobutton(w, text=text, variable=v, command=callback, value=index,).pack(anchor=tk.W) return w def on_close(self): if self.my_thread is not None: if(threading.active_count()!=0): self.my_thread.stop() self.parent.on_exit() class App(tk.Tk): """Start here""" def __init__(self): super().__init__() self.protocol("WM_DELETE_WINDOW", self.on_exit) self.set_title() self.set_style() Main(self) def set_style(self): self.style = ttk.Style() #('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative') self.style.theme_use("clam") def set_title(self): s = "{0}".format('Simple App') self.title(s) def on_exit(self): """Close all""" if messagebox.askokcancel("Simple App", "Do you want to quit?", parent=self): self.destroy() if __name__ == '__main__': app = App() app.mainloop()
Je vais certainement réécrire le code à long terme. C'était la première fois que je travaillais sur quelque chose d'un peu plus grand avec Tkinter et je voulais commencer par créer le cadre de base. Mais la POO convenue est la voie à suivre :)
Il semble que le problème soit de remplacer
from matplotlib.figure import Figure f = Figure() gs = f.add_gridspec(10,7) a = f.add_subplot(gs[:, :5])
de manière indépendante de pyplot. Une option est l'utilisation de gridspec:
# Not Working - using plt and subplot2grid from matplotlib import pyplot as plt f = plt.figure() a = plt.subplot2grid((10, 7), (0, 0), rowspan=10, colspan=5)
montrer d'abord un code de travail minimal - afin que nous puissions l'exécuter et voir le problème.
Puisque vous ne devriez pas utiliser pyplot (
plt
) dans les interfaces graphiques personnalisées, je soupçonne que c'est le problème. Je ne comprends pas pourquoi vous souhaitez soudainement utiliserpyplot
. Ne l'utilisez pas et cela devrait continuer à fonctionner.@ImportanceOfBeingErnest. C'est juste une réponse terrible à tous égards. Vous n'expliquez pas ou ne créez pas de lien vers une source qui explique pourquoi pyplot doit être évité dans les interfaces graphiques personnalisées, ce que je n'ai jamais entendu ailleurs. Les avantages de personnalisation de pyplot par rapport à Figure me donnent envie de l'utiliser, mais la question de la motivation à des fins de programmation est secondaire. Si le mieux que vous puissiez faire pour résoudre ce problème est de suggérer de ne pas utiliser le programme que j'ai essayé d'utiliser, alors vous n'avez rien à publier du tout, mais de cette façon, c'est perdant. Si vous avez des commentaires constructifs, je serai ravi de tenter votre chance.
Ce n'est pas une réponse, mais un commentaire. Je suis désolé si vous pensez que ce n'est pas constructif. Les raisons de ne pas utiliser pyplot en conjonction avec une interface graphique personnalisée peuvent être résumées comme suit: si vous avez votre interface graphique, ainsi que pyplot gère une figure, ils peuvent interférer et causer toutes sortes de problèmes, dont certains sont assez difficiles à suivre vers le bas. Peut-être est-ce suffisant pour vous signaler qu'aucun des exemples d'intégration dans les interfaces graphiques de la page matplotlib n'utilise pyplot?