Tkinter plt.figure()不会绘制,但Figure()会绘制

时间:2019-04-05 20:20:56

标签: python python-3.x matplotlib canvas tkinter

我开始构建Tkinter应用程序,最初使用的是matplotlib的Figurefigure.add_subplot。这样,一切都可以完美运行。为了进行更多自定义,我现在想移至pyplotsubplot2grid,但是这样做时,突然所有的tkinter变量都停止工作。

在我的MWE中,变量gArrChoice跟踪选择了哪个单选按钮,并且应默认为第一个选项。基于此选项,图形应绘制一条徘徊在0.1左右的线。如果选择了第二个选项,则图形应更改为悬停在5左右。图形会每2.5秒自动更新一次。如果注释掉“工作”下面的3行并改用3个“不工作”行,则变量的默认设置停止工作,单选按钮之间的切换不再起作用。在动画函数内部声明不会改变问题。

如何在Tkinter中使用plt而不破坏变量?

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 个答案:

答案 0 :(得分:1)

当您改用IntVar()时,似乎在更新pyplot时存在错误。但是,如果您在单选按钮中强制更改值,则可以解决该问题:

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))

或者您也可以将IntVar设置为StartPage的属性,似乎效果很好。

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()

答案 1 :(得分:1)

我认为面向对象的方法会更好。

见下文,我使用线程和队列来管理剧情动画,您甚至可以设置时间间隔并动态更改图形类型

反正很好,很有趣

#!/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()

enter image description here

答案 2 :(得分:0)

问题似乎在于替换

# 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)

以不依赖pyplot的方式。一种选择是使用gridspec:

from matplotlib.figure import Figure
f = Figure()
gs = f.add_gridspec(10,7)
a = f.add_subplot(gs[:, :5])