Tkinter plt.figure () не печатает, а Figure () делает - PullRequest
2 голосов
/ 05 апреля 2019

Я начал создавать приложение Tkinter и изначально использовал matplotlib Figure и figure.add_subplot. С этим все работает отлично. Для большей настройки я теперь хочу перейти к pyplot и subplot2grid, но при этом внезапно все мои переменные tkinter перестают работать.

В моем MWE переменная gArrChoice отслеживает, какая радио-кнопка выбрана и должна по умолчанию использовать первую опцию. Основываясь на этой опции, график должен построить линию, колеблющуюся около 0,1. Если выбран второй вариант, график должен измениться на 5. График автоматически обновляется каждые 2,5 секунды. Если вы закомментируете 3 строки ниже «Работает» и вместо этого используете 3 строки «Не работает», настройки переменной по умолчанию перестают работать, и переключение между переключателями больше не действует. Объявление внутри функции анимации не меняет проблему.

Как я могу использовать plt с Tkinter и не уничтожать мои переменные?

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 ]

1 голос
/ 06 апреля 2019

Я думаю, что подход ОО это будет лучше.

См. Ниже, я использую поток и очередь для управления анимацией графика, вы даже можете установить временной интервал и изменить тип графика на лету

В любом случае, хорошая работа, очень интересно

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

1 голос
/ 06 апреля 2019

Кажется, есть ошибка при обновлении 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()
0 голосов
/ 07 апреля 2019

Кажется, проблема в том, чтобы заменить

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

независимым способом. Одним из вариантов является использование gridspec:

from matplotlib.figure import Figure
f = Figure()
gs = f.add_gridspec(10,7)
a = f.add_subplot(gs[:, :5])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...