Gleeson9 Gleeson9 - 13 days ago 3
Python Question

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.

Answer

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