简体   繁体   中英

Tkinter/Matplotlib animation assistance

I am working on a program where I need two different graphs to be animated. I am having trouble puzzling out how to do so with the structure I am using. I will paste my code below so that you can try it. I have stripped it down as much as I can while still retaining core functionality so hopefully it isn't too hard to understand. In it's current state the animation lines are not doing anything so please let me know where I have gone wrong.

from Tkinter import *               #Used for GUI elements
import time                         #Used for timing elements
import matplotlib                   #Used for graphing
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import numpy as np                  #Used for arrays to find min/max of float array
import random                       #Only used for fake data input


class tkgui:
    def __init__(self, parent):
        #--------------The following are variables that need to be accessed by other functions----------------------
        #Raw input values
        self.x = 209500
        self.y = 0
        self.timeElapsed = 0

        #State values
        self.curFrame = 1

        #List Values
        self.timeList = np.array([])
        self.yList = np.array([])
        self.xList = np.array([])

        self.lastX = 0
        #----------------------------------------------------------------------------------------------------------

        #Make Tkinter fullscreen
        w, h = 320,240 #int(str(root.winfo_screenwidth())), int(str(root.winfo_screenheight())) #320, 240 is the RPiTFT

        #The base layer of the GUI
        topLevelContainer = Frame(parent)
        topLevelContainer.pack()

        #The two 'screens' to switch between. They contain everything on the GUI
        self.buttonsFrame = Frame(topLevelContainer)
        self.graphFrame = Frame(topLevelContainer)

        #Stack the frames so that they are switchable
        for frame in self.buttonsFrame, self.graphFrame:
            frame.grid(row=0, column=0, sticky='news', padx=5, pady=(10, 10))

        buttonsFrameButtons = Frame(self.buttonsFrame)
        buttonsFrameButtons.pack(side=LEFT, padx=(0, 50))

        #X button
        self.xButton = Button(buttonsFrameButtons, command=self.xButtonClick)
        self.xButton.configure(text="X", background="#C8C8C8", width=6, padx=35, pady=35)
        self.xButton.pack(side=TOP, pady=10)

        #Y button
        self.yButton = Button(buttonsFrameButtons, command=self.yButtonClick)
        self.yButton.configure(text="Y", background="#C8C8C8", width=6, padx=35, pady=35)
        self.yButton.pack(side=TOP, pady=10)

        #Bar graph
        buttonsFrameBar = Frame(self.buttonsFrame)
        buttonsFrameBar.pack(side=LEFT)

        self.fBar = Figure(figsize=(2, 4), dpi=50)
        aBar = self.fBar.add_subplot(111)
        self.xBar = aBar.bar([0, 1], [0, 0], width=1)

        lAxes = self.fBar.gca()
        lAxes.axes.get_xaxis().set_ticklabels([])

        aBar.set_ylim([-30000, 30000])
        self.fBar.tight_layout()

        self.buttonsFrame.tkraise()         

        #Setup the matplotlib graph
        self.fGraph = Figure(figsize=(5, 3), dpi=50)
        #Create the Y axis
        aGraph = self.fGraph.add_subplot(111)
        aGraph.set_xlabel("Time (s)")
        aGraph.set_ylabel("Y")
        self.yLine, = aGraph.plot([],[], "r-")

        #Create the X axis
        a2Graph = aGraph.twinx()
        self.xLine, = a2Graph.plot([], [])
        a2Graph.set_ylabel("X")

        #Final matplotlib/Tkinter setup
        self.canvasGraph = FigureCanvasTkAgg(self.fGraph, master=self.graphFrame)
        self.canvasGraph.show()
        self.canvasGraph.get_tk_widget().pack(side=LEFT, fill=BOTH, expand=1)

        self.canvasBar = FigureCanvasTkAgg(self.fBar, master=buttonsFrameBar)
        self.canvasBar.show()
        self.canvasBar.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=1)

        #Resize the plot to fit all of the labels in
        self.fGraph.subplots_adjust(bottom=0.13, left=0.15, right=0.87)       

    def refreshGraph(self, frameno):     #Redraw the graph with the updated arrays and resize it accordingly
        #Update data
        self.yLine.set_data(self.timeList, self.yList)
        self.xLine.set_data(self.timeList, self.xList)

        #Update y axis
        ax = self.canvasGraph.figure.axes[0]
        ax.set_xlim(self.timeList.min(), self.timeList.max())
        ax.set_ylim(self.yList.min(), self.yList.max())

        #Update x axis
        ax2 = self.canvasGraph.figure.axes[1]
        ax2.set_xlim(self.timeList.min(), self.timeList.max())
        ax2.set_ylim(self.xList.min(), self.xList.max())

        #Redraw
        self.canvasGraph.draw()


    def refreshBar(self, frameno):
        curX = self.x
        dif = curX - self.lastX
        i = [dif]
        for rect, h in zip(self.xBar, i):
            rect.set_height(h)
            if dif > 0:
                rect.set_color('b')
            else:
                rect.set_color('r')

        self.canvasBar.draw()
        self.lastX=curX

    def switchFrame(self):      #Switch the current screen. Either x/y buttons or graph
        if self.curFrame:
            self.graphFrame.tkraise()
            self.curFrame = 0
        else:
            self.buttonsFrame.tkraise()
            self.curFrame = 1

    def xButtonClick(self):
        self.switchFrame()

    def yButtonClick(self):
        self.close()

    def close(e):               #Exit the program
        sys.exit()

#Initialisation of global variables
lastTime = 0        #Used for the 'last time' iterated
yState = 0       

def updateNumbers():        #Used to generate fake input variables. Will be replaced by ADC values
    global lastTime
    global yState

    curTime = time.time()                                           #Update the time each time the function is called
    if curTime - lastTime > 0.5:                                    #Only update numbers every 0.5 seconds
        gui.x = random.randrange(200000, 230000)                   #Generates x
        if yState:
            gui.y = gui.y - 20                                #Decrease y
            if gui.y < 1:
                yState = 0                                       #Until it gets to a minimum of 0
        else:
            gui.y = gui.y + 20                                #Increase y
            if gui.y > 1300:
                yState = 1                                       #Until it reaches a maximum of 1300
        gui.yList = np.append(gui.yList, gui.y)            #Add the new y values to the array
        gui.xList = np.append(gui.xList, gui.x/10000.0)          #Add the new x values to the array
        lastTime = time.time()                                      #Record the last time iterated for timing purposes
        gui.timeElapsed += 0.5                                      
        gui.timeList = np.append(gui.timeList, gui.timeElapsed)     #Add the latest time to the array
##        gui.refreshGraph()                                          #Call the function that will redraw the graph with the new figures

if __name__ == "__main__":
    root = Tk()         #Root Tkinter setup
    gui = tkgui(root)   #Setup the gui class

    updateNumbers()
    aniGraph = animation.FuncAnimation(gui.fGraph,gui.refreshGraph,interval=500,frames=100,repeat=True)
    aniBar = animation.FuncAnimation(gui.fBar,gui.refreshBar,interval=500,frames=100,repeat=True)

    while(1):                   #Main loop
        updateNumbers()         #Update fake values

        root.update()           #Update the gui loop

    root.mainloop()             #Tkinter main loop

To be clear, I am only asking how to get the animation working for this code.

Your code works for me - I see all animation - but if you what to run it without While(1): (or more pythonic While True: ) then you can use root.after(milliseconds, function_name) . You can event use it instead of FuncAnimation .

And it lets you control function - start/stop it.

if self.run_bar:
   root.after(100, self.refreshBar)

You use start it (or restart it)

self.run_bar = True
self.refreshBar()

and you can stop it

self.run_bar = False

See all # <-- in code.

from Tkinter import *               #Used for GUI elements
import time                         #Used for timing elements
import matplotlib                   #Used for graphing
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import numpy as np                  #Used for arrays to find min/max of float array
import random                       #Only used for fake data input


class TkGUI: # <-- CamelCase names for classes
             # <-- empty line for readabelity
    def __init__(self, parent):
        #--- The following are variables that need to be accessed by other functions----------------------
        #Raw input values
        self.x = 209500
        self.y = 0
        self.timeElapsed = 0

        #State values
        self.curFrame = 1

        #List Values
        self.timeList = np.array([])
        self.yList = np.array([])
        self.xList = np.array([])

        self.lastX = 0
        #-----------------------------------------------------------

        #Make Tkinter fullscreen
        w, h = 320,240 #int(str(root.winfo_screenwidth())), int(str(root.winfo_screenheight())) #320, 240 is the RPiTFT

        #The base layer of the GUI
        topLevelContainer = Frame(parent)
        topLevelContainer.pack()

        #The two 'screens' to switch between. They contain everything on the GUI
        self.buttonsFrame = Frame(topLevelContainer)
        self.graphFrame = Frame(topLevelContainer)

        #Stack the frames so that they are switchable
        for frame in self.buttonsFrame, self.graphFrame:
            frame.grid(row=0, column=0, sticky='news', padx=5, pady=(10, 10))

        buttonsFrameButtons = Frame(self.buttonsFrame)
        buttonsFrameButtons.pack(side=LEFT, padx=(0, 50))

        #X button
        self.xButton = Button(buttonsFrameButtons, command=self.xButtonClick)
        self.xButton.configure(text="X", background="#C8C8C8", width=6, padx=35, pady=35)
        self.xButton.pack(side=TOP, pady=10)

        #Y button
        self.yButton = Button(buttonsFrameButtons, command=self.yButtonClick)
        self.yButton.configure(text="Y", background="#C8C8C8", width=6, padx=35, pady=35)
        self.yButton.pack(side=TOP, pady=10)

        #Bar graph
        buttonsFrameBar = Frame(self.buttonsFrame)
        buttonsFrameBar.pack(side=LEFT)

        self.fBar = Figure(figsize=(2, 4), dpi=50)
        aBar = self.fBar.add_subplot(111)
        self.xBar = aBar.bar([0, 1], [0, 0], width=1)

        lAxes = self.fBar.gca()
        lAxes.axes.get_xaxis().set_ticklabels([])

        aBar.set_ylim([-30000, 30000])
        self.fBar.tight_layout()

        self.buttonsFrame.tkraise()         

        #Setup the matplotlib graph
        self.fGraph = Figure(figsize=(5, 3), dpi=50)
        #Create the Y axis
        aGraph = self.fGraph.add_subplot(111)
        aGraph.set_xlabel("Time (s)")
        aGraph.set_ylabel("Y")
        self.yLine, = aGraph.plot([],[], "r-")

        #Create the X axis
        a2Graph = aGraph.twinx()
        self.xLine, = a2Graph.plot([], [])
        a2Graph.set_ylabel("X")

        #Final matplotlib/Tkinter setup
        self.canvasGraph = FigureCanvasTkAgg(self.fGraph, master=self.graphFrame)
        self.canvasGraph.show()
        self.canvasGraph.get_tk_widget().pack(side=LEFT, fill=BOTH, expand=1)

        self.canvasBar = FigureCanvasTkAgg(self.fBar, master=buttonsFrameBar)
        self.canvasBar.show()
        self.canvasBar.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=1)

        #Resize the plot to fit all of the labels in
        self.fGraph.subplots_adjust(bottom=0.13, left=0.15, right=0.87)       

    def refreshGraph(self): # <-- without argument
        '''Redraw the graph with the updated arrays and resize it accordingly''' # <-- docstring used by documentation generator and IDE as help 

        #Update data
        self.yLine.set_data(self.timeList, self.yList)
        self.xLine.set_data(self.timeList, self.xList)

        #Update y axis
        ax = self.canvasGraph.figure.axes[0]
        ax.set_xlim(self.timeList.min(), self.timeList.max())
        ax.set_ylim(self.yList.min(), self.yList.max())

        #Update x axis
        ax2 = self.canvasGraph.figure.axes[1]
        ax2.set_xlim(self.timeList.min(), self.timeList.max())
        ax2.set_ylim(self.xList.min(), self.xList.max())

        #Redraw
        self.canvasGraph.draw()

        # run again after 100ms (0.1s)
        root.after(100, self.refreshGraph)  # <-- run again  like in loop

    def refreshBar(self): # <-- without argument
        curX = self.x
        dif = curX - self.lastX
        i = [dif]
        for rect, h in zip(self.xBar, i):
            rect.set_height(h)
            if dif > 0:
                rect.set_color('b')
            else:
                rect.set_color('r')

        self.canvasBar.draw()
        self.lastX=curX
        # run again after 100ms (0.1s)
        root.after(100, self.refreshBar)  # <-- run again like in loop

    def switchFrame(self):      #Switch the current screen. Either x/y buttons or graph
        if self.curFrame:
            self.graphFrame.tkraise()
            self.curFrame = 0
        else:
            self.buttonsFrame.tkraise()
            self.curFrame = 1

    def xButtonClick(self):
        self.switchFrame()

    def yButtonClick(self):
        self.close()

    def close(e):  # Exit the program
        sys.exit()

#Initialisation of global variables
lastTime = 0        #Used for the 'last time' iterated
yState = 0       

def updateNumbers():        #Used to generate fake input variables. Will be replaced by ADC values
    global lastTime
    global yState

    curTime = time.time()                                           #Update the time each time the function is called
    if curTime - lastTime > 0.5:                                    #Only update numbers every 0.5 seconds
        gui.x = random.randrange(200000, 230000)                   #Generates x
        if yState:
            gui.y = gui.y - 20                                #Decrease y
            if gui.y < 1:
                yState = 0                                       #Until it gets to a minimum of 0
        else:
            gui.y = gui.y + 20                                #Increase y
            if gui.y > 1300:
                yState = 1                                       #Until it reaches a maximum of 1300
        gui.yList = np.append(gui.yList, gui.y)            #Add the new y values to the array
        gui.xList = np.append(gui.xList, gui.x/10000.0)          #Add the new x values to the array
        lastTime = time.time()                                      #Record the last time iterated for timing purposes
        gui.timeElapsed += 0.5                                      
        gui.timeList = np.append(gui.timeList, gui.timeElapsed)     #Add the latest time to the array

    # run again after 100ms (0.1s)
    root.after(100, updateNumbers)  # <-- run again like in loop       

if __name__ == "__main__":
    root = Tk()
    gui = TkGUI(root)

    # <-- vvv - without While and without FuncAnimation - vvv

    updateNumbers()     # run first time 

    gui.refreshBar()    # run first time 
    gui.refreshGraph()  # run first time 

    # <-- ^^^ - without While and without FuncAnimation - ^^^

    root.mainloop()     # Tkinter main loop

EDIT: Of course you can keep FuncAnimation without after and use after only in updateNumbers

from Tkinter import *               #Used for GUI elements
import time                         #Used for timing elements
import matplotlib                   #Used for graphing
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import numpy as np                  #Used for arrays to find min/max of float array
import random                       #Only used for fake data input


class TkGUI: # <-- CamelCase names for classes
             # <-- empty line for readabelity
    def __init__(self, parent):
        #--- The following are variables that need to be accessed by other functions----------------------
        #Raw input values
        self.x = 209500
        self.y = 0
        self.timeElapsed = 0

        #State values
        self.curFrame = 1

        #List Values
        self.timeList = np.array([])
        self.yList = np.array([])
        self.xList = np.array([])

        self.lastX = 0
        #-----------------------------------------------------------

        #Make Tkinter fullscreen
        w, h = 320,240 #int(str(root.winfo_screenwidth())), int(str(root.winfo_screenheight())) #320, 240 is the RPiTFT

        #The base layer of the GUI
        topLevelContainer = Frame(parent)
        topLevelContainer.pack()

        #The two 'screens' to switch between. They contain everything on the GUI
        self.buttonsFrame = Frame(topLevelContainer)
        self.graphFrame = Frame(topLevelContainer)

        #Stack the frames so that they are switchable
        for frame in self.buttonsFrame, self.graphFrame:
            frame.grid(row=0, column=0, sticky='news', padx=5, pady=(10, 10))

        buttonsFrameButtons = Frame(self.buttonsFrame)
        buttonsFrameButtons.pack(side=LEFT, padx=(0, 50))

        #X button
        self.xButton = Button(buttonsFrameButtons, command=self.xButtonClick)
        self.xButton.configure(text="X", background="#C8C8C8", width=6, padx=35, pady=35)
        self.xButton.pack(side=TOP, pady=10)

        #Y button
        self.yButton = Button(buttonsFrameButtons, command=self.yButtonClick)
        self.yButton.configure(text="Y", background="#C8C8C8", width=6, padx=35, pady=35)
        self.yButton.pack(side=TOP, pady=10)

        #Bar graph
        buttonsFrameBar = Frame(self.buttonsFrame)
        buttonsFrameBar.pack(side=LEFT)

        self.fBar = Figure(figsize=(2, 4), dpi=50)
        aBar = self.fBar.add_subplot(111)
        self.xBar = aBar.bar([0, 1], [0, 0], width=1)

        lAxes = self.fBar.gca()
        lAxes.axes.get_xaxis().set_ticklabels([])

        aBar.set_ylim([-30000, 30000])
        self.fBar.tight_layout()

        self.buttonsFrame.tkraise()         

        #Setup the matplotlib graph
        self.fGraph = Figure(figsize=(5, 3), dpi=50)
        #Create the Y axis
        aGraph = self.fGraph.add_subplot(111)
        aGraph.set_xlabel("Time (s)")
        aGraph.set_ylabel("Y")
        self.yLine, = aGraph.plot([],[], "r-")

        #Create the X axis
        a2Graph = aGraph.twinx()
        self.xLine, = a2Graph.plot([], [])
        a2Graph.set_ylabel("X")

        #Final matplotlib/Tkinter setup
        self.canvasGraph = FigureCanvasTkAgg(self.fGraph, master=self.graphFrame)
        self.canvasGraph.show()
        self.canvasGraph.get_tk_widget().pack(side=LEFT, fill=BOTH, expand=1)

        self.canvasBar = FigureCanvasTkAgg(self.fBar, master=buttonsFrameBar)
        self.canvasBar.show()
        self.canvasBar.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=1)

        #Resize the plot to fit all of the labels in
        self.fGraph.subplots_adjust(bottom=0.13, left=0.15, right=0.87)       

    def refreshGraph(self, i):
        '''Redraw the graph with the updated arrays and resize it accordingly''' # <-- docstring used by documentation generator and IDE as help 

        #Update data
        self.yLine.set_data(self.timeList, self.yList)
        self.xLine.set_data(self.timeList, self.xList)

        #Update y axis
        ax = self.canvasGraph.figure.axes[0]
        ax.set_xlim(self.timeList.min(), self.timeList.max())
        ax.set_ylim(self.yList.min(), self.yList.max())

        #Update x axis
        ax2 = self.canvasGraph.figure.axes[1]
        ax2.set_xlim(self.timeList.min(), self.timeList.max())
        ax2.set_ylim(self.xList.min(), self.xList.max())

        #Redraw
        self.canvasGraph.draw()

    def refreshBar(self, i):
        curX = self.x
        dif = curX - self.lastX
        i = [dif]
        for rect, h in zip(self.xBar, i):
            rect.set_height(h)
            if dif > 0:
                rect.set_color('b')
            else:
                rect.set_color('r')

        self.canvasBar.draw()
        self.lastX=curX

    def switchFrame(self):
        '''Switch the current screen. Either x/y buttons or graph'''

        if self.curFrame:
            self.graphFrame.tkraise()
            self.curFrame = 0
        else:
            self.buttonsFrame.tkraise()
            self.curFrame = 1

    def xButtonClick(self):
        self.switchFrame()

    def yButtonClick(self):
        self.close()

    def close(e):  # Exit the program
        sys.exit()

#Initialisation of global variables
lastTime = 0        #Used for the 'last time' iterated
yState = 0       

def updateNumbers():        #Used to generate fake input variables. Will be replaced by ADC values
    global lastTime
    global yState

    curTime = time.time()                                           #Update the time each time the function is called
    if curTime - lastTime > 0.5:                                    #Only update numbers every 0.5 seconds
        gui.x = random.randrange(200000, 230000)                   #Generates x
        if yState:
            gui.y = gui.y - 20                                #Decrease y
            if gui.y < 1:
                yState = 0                                       #Until it gets to a minimum of 0
        else:
            gui.y = gui.y + 20                                #Increase y
            if gui.y > 1300:
                yState = 1                                       #Until it reaches a maximum of 1300
        gui.yList = np.append(gui.yList, gui.y)            #Add the new y values to the array
        gui.xList = np.append(gui.xList, gui.x/10000.0)          #Add the new x values to the array
        lastTime = time.time()                                      #Record the last time iterated for timing purposes
        gui.timeElapsed += 0.5                                      
        gui.timeList = np.append(gui.timeList, gui.timeElapsed)     #Add the latest time to the array

    # run again after 100ms (0.1s)
    root.after(100, updateNumbers)  # <-- run again like in loop       

if __name__ == "__main__":
    root = Tk()
    gui = TkGUI(root)

    aniGraph = animation.FuncAnimation(gui.fGraph,gui.refreshGraph,interval=500,frames=100,repeat=True)
    aniBar = animation.FuncAnimation(gui.fBar,gui.refreshBar,interval=500,frames=100,repeat=True)

    # <-- vvv - without While - vvv

    updateNumbers()     # run first time 

    # <-- ^^^ - without While - ^^^

    root.mainloop()     # Tkinter main loop

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM