Tkinter and pyplot running out of memory - python

I'm running a Tkinter script that updates a plot every 5 seconds. It calls the function that plots it every 5 seconds. After not that long python starts using a lot of memory, I checked in task manager. The memory usage keeps increasing really fast. It starts a new file every 24 hours so there is a limit to the number of lines in the file.
The file starts empty.
I tried increasing the 5s time span but it does the same thing. Maybe a little slower,
also tried tried plotting every 3 rows or so but the same thing happened again.
Any idea what is causing such high memory usage and how to fix?
Thanks!
data = np.genfromtxt(filename)
time_data = data[:,0]
room_temp_data_celsius = data[:,1]
rad_temp_data_celsius = data[:,2]
fan_state_data = data[:,3]
threshold_data = data[:,4]
hysteresis_data = data[:,5]
threshold_up = [] #empty array
threshold_down = []#empty array
for i in range(0,len(threshold_data)):
threshold_up.append(threshold_data[i]+hysteresis_data[i])
threshold_down.append(threshold_data[i]-hysteresis_data[i])
# Time formatting
dts = map(datetime.datetime.fromtimestamp, time_data)
fds = matplotlib.dates.date2num(dts)
hfmt = matplotlib.dates.DateFormatter('%H:%M')
# Temperature conversion
room_temp_data_fahrenheit = map(celsius_to_fahrenheit, room_temp_data_celsius)
rad_temp_data_fahrenheit = map(celsius_to_fahrenheit, rad_temp_data_celsius)
threshold_data_fahrenheit = map(celsius_to_fahrenheit, threshold_data)
threshold_up_fahrenheit = map(celsius_to_fahrenheit, threshold_up)
threshold_down_fahrenheit = map(celsius_to_fahrenheit, threshold_down)
f = plt.figure()
a = f.add_subplot(111)
a.plot(fds,room_temp_data_fahrenheit, fds, rad_temp_data_fahrenheit, 'r')
a.plot(fds,fan_state_data*(max(rad_temp_data_fahrenheit)+4),'g_')
a.plot(fds, threshold_up_fahrenheit, 'y--')
a.plot(fds, threshold_down_fahrenheit, 'y--')
plt.xlabel('Time (min)')
plt.ylabel('Temperature '+unichr(176)+'F')
plt.legend(["Room Temperature","Radiator","Fan State","Threshold Region"], loc="upper center", ncol=2)
plt.ylim([min(room_temp_data_fahrenheit)-5, max(rad_temp_data_fahrenheit)+5])
plt.grid()
a.xaxis.set_major_formatter(hfmt)
data_graph = FigureCanvasTkAgg(f, master=root)
data_graph.show()
data_graph.get_tk_widget().grid(row=6,column=0, columnspan=3)
root.after(WAIT_TIME, control)

It's not clear to me from your code how your plots are changing with time. So I don't have any specific suggestion for your existing code. However, here is a basic example of how to embed an animated matplotlib figure in a Tkinter app. Once you grok how it works, you should be able to adapt it to your situation.
import matplotlib.pyplot as plt
import numpy as np
import Tkinter as tk
import matplotlib.figure as mplfig
import matplotlib.backends.backend_tkagg as tkagg
pi = np.pi
sin = np.sin
class App(object):
def __init__(self, master):
self.master = master
self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
self.ax = self.fig.add_subplot(111)
self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
toolbar.update()
self.update = self.animate().next
master.after(10, self.update)
canvas.show()
def animate(self):
x = np.linspace(0, 6*pi, 100)
y = sin(x)
line1, = self.ax.plot(x, y, 'r-')
phase = 0
while True:
phase += 0.1
line1.set_ydata(sin(x + phase))
newx = x+phase
line1.set_xdata(newx)
self.ax.set_xlim(newx.min(), newx.max())
self.ax.relim()
self.ax.autoscale_view(True, True, True)
self.fig.canvas.draw()
self.master.after(10, self.update)
yield
def main():
root = tk.Tk()
app = App(root)
tk.mainloop()
if __name__ == '__main__':
main()
The main idea here is that plt.plot should only be called once. It returns a Line2D object, line1. You can then manipulate the plot by calling line1.set_xdata and/or line1.set_ydata. This "technique" for animation comes from the Matplotlib Cookbook.
Technical note:
The generator function, animate was used here to allow the state of the plot to be saved and updated without having to save state information in instance attributes. Note that it is the generator function's next method (not the generator self.animate) which is being called repeatedly:
self.update = self.animate().next
master.after(10, self.update)
So we are advancing the plot frame-by-frame by calling the generator, self.animate()'s, next method.

Related

Why are my plots not appearing with set_data using Tkinter?

I am trying to improve my plotting function. I want to plot data using my plotGraph function coming from an EEG board in real-time, pulling samples from an LSL # 250Hz. Previously, I had a functional version using the regular self.ax.plot(x,y), clearing the data with self.ax.clear() every time the plot needed to refresh. Nonetheless, some profiling showed that my code was taking way too much time to plot in comparison to the rest of it.
One of the suggestions I got was to use set_data instead of plot and clear. I have multiple lines of data that I want to plot simultaneously, so I tried following Matplotlib multiple animate multiple lines, which you can see below (adapted code). Also, I was told to use self.figure.canvas.draw_idle(), which I tried, but I'm not sure if I did it correctly.
Unfortunately, it didn't work, the graph is not updating and I can't seem to find why. I'm aware that the source I just mentioned uses animation.FuncAnimation but I'm not sure that would be the problem. Is it?
Any ideas of why none of my lines are showing in my canvas' graph?
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class AppWindow:
def plotGraph(self, x, y):
for lnum,line in enumerate(self.lines):
line.set_data(x[:], y[:, lnum])
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def __init__(self):
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg =
bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=2, color=tx_color)[0]
self.lines.append(lobj)
for line in self.lines:
line.set_data([],[])
def start(self):
self.root.mainloop()
You chart is empty because you are plotting empty arrays:
line.set_data([],[])
If you fill in the line arrays, the chart plots correctly.
Try this code. It updates the chart with new random data every second.
import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random
bg_color='grey'
tx_color='green'
sc_color='linen'
numchan = 8
chlen = 100
xvals=[(x-40)/20 for x in range(chlen)] # X coordinates
chcolors= ['gold','blue','green','maroon','red','brown','purple','cyan']
class AppWindow:
def plotGraph(self):
self.figure.canvas.draw_idle()
plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
plt.xlabel('Freq', fontsize = 9, color = tx_color)
self.figure.canvas.draw()
def UpdateChannelData(self): # callback with new data
# fake random data
for i,ch in enumerate(self.chdata):
for p in range(len(ch)):
ch[p] += (random.random()-.5)/100
self.lines[i].set_data(xvals, ch)
self.plotGraph()
self.root.after(100, self.UpdateChannelData) # simulate next call
def __init__(self):
global chzero
self.root = tk.Tk() #start of application
self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = bg_color, highlightthickness=0)
self.canvas.pack(fill = 'both', expand = True)
self.figure = plt.figure(figsize = (5,6), dpi = 100)
self.figure.patch.set_facecolor(sc_color)
self.ax = self.figure.add_subplot(111)
self.ax.clear()
self.line, = self.ax.plot([], [], lw=1, color = tx_color)
self.line.set_data([],[])
#place graph
self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
self.chart_type.get_tk_widget().pack()
self.lines = []
#numchan = 8 #let's say I have 8 channels
for index in range(numchan):
lobj = self.ax.plot([],[], lw=1, color=chcolors[index])[0]
self.lines.append(lobj)
# set flat data
self.chdata = [[0 for x in range(chlen)] for ch in range(numchan)]
self.root.after(1000, self.UpdateChannelData) # start data read
def start(self):
self.root.mainloop()
AppWindow().start()
Output:

Tcl_AsyncDelete error when displaying live updating plots

I want to display a plot which updates every second (alongside other stuff) on a Tkinter window. I simply need to get a line from a data matrix and plot it, then go to the next line, and so on.
Since I need a Start/Stop button, I'm using threading.
In order to do so, I followed this post which basically does what I need.
However, after a while Python crashes and Spyder displays this error:
An error occurred while starting the kernel
Tcl_AsyncDelete: async handler deleted by the wrong thread
I tried reading about it but I didn't really find a solution or an explaination to this.
Here's a sample code:
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
continuePlotting = False
line = 0
data = np.random.rand(100, 500)
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
global line
global data
l = line % len(data) - 1
r = data[l]
line = line+1
return r
def app():
root = tk.Tk()
root.configure(background='white')
# First Plot
top = tk.Frame(root)
top.pack(fill='both')
fig = Figure()
ax = fig.add_subplot(111)
graph = FigureCanvasTkAgg(fig, master=top)
graph.get_tk_widget().pack(fill='both')
def plotter():
while continuePlotting:
ax.cla()
dpts = data_points()
y = dpts[0:-1]
x = np.linspace(0,len(y),len(y))
ax.plot(x, y)
ax.grid(True)
graph.draw()
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
b = tk.Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()
Any help would be much appreciated
The basic problem is you are calling Tk functions from a non-GUI thread. Don't do that. Tk is not designed to be called from random threads. The general solution is described as an answer to a question on tkinter thread communication on this site. In short, push your calculated data onto a Queue and raise a Tk event to let the UI thread know that there is more data ready. The event handler can then fetch the new value from the queue and do UI things with it.
Attached is a modified version of your script using this mechanism.
import tkinter as tk
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import time
import threading
from queue import Queue
DATA_READY_EVENT = '<<DataReadyEvent>>'
continuePlotting = False
line = 0
data = np.random.rand(100, 500)
def change_state():
global continuePlotting
if continuePlotting == True:
continuePlotting = False
else:
continuePlotting = True
def data_points():
global line
global data
l = line % len(data) - 1
r = data[l]
line = line+1
return r
def app():
root = tk.Tk()
root.configure(background='white')
queue = Queue()
# First Plot
top = tk.Frame(root)
top.pack(fill='both')
fig = Figure()
ax = fig.add_subplot(111)
graph = FigureCanvasTkAgg(fig, master=top)
graph.get_tk_widget().pack(fill='both')
def plot(ev):
x,y = queue.get()
ax.plot(x, y)
ax.grid(True)
graph.draw()
def plotter():
global continuePlotting
while continuePlotting:
ax.cla()
dpts = data_points()
y = dpts[0:-1]
x = np.linspace(0,len(y),len(y))
queue.put((x,y))
graph.get_tk_widget().event_generate(DATA_READY_EVENT)
time.sleep(1)
def gui_handler():
change_state()
threading.Thread(target=plotter).start()
graph.get_tk_widget().bind(DATA_READY_EVENT, plot)
b = tk.Button(root, text="Start/Stop", command=gui_handler, bg="red", fg="white")
b.pack()
root.mainloop()
if __name__ == '__main__':
app()

How to update full chart after 5 seconds using matplotlib and tkinter?

What this code should do: Draw the data using function get_latest_data(1) with parameter number 1 and after 5 seconds redraw data using function get_latest_data(2) with parameter number 2
What this code do already: Draw the data using function get_latest_data(1) with parameter number 1
This is semi-functional code(works only first chart drawing)
from threading import Thread
from queue import Empty, Queue
import time
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg#, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
class tkChartGUI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def get_latest_data(self, dataid):
x_array=[]
y_array=[]
if (dataid == 1):
x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
y_array=[0.5,0.7,0.3,1.0,0.6,0.9,0.5,0.2,0.1,0.5,0.33,0.55,0.3,0.6]
if (dataid == 2):
x_array=[1,2,3,4,5,6,7,8,9,10,11,12,13,14];
y_array=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.1,1.2,1.3]
return (x_array, y_array)
def initUI(self):
self.parent.title("Simple chart")
self.parent.geometry("800x600+300+100")
result_queue = Queue()
Thread(target=self.get_latest_data, args=[result_queue], daemon=True).start()
x_array, y_array = self.get_latest_data(1)
f = Figure(figsize=(5, 3), dpi=150)
a = f.add_subplot(111)
a.set_xlabel("Values_X")
a.set_ylabel("Values_Y")
a.yaxis.grid(True, which='major')
a.xaxis.grid(True, which='major')
a.plot(x_array, y_array)
canvas = FigureCanvasTkAgg(f, master=self.parent)
canvas.show()
canvas.get_tk_widget().grid(row=0,column=0)
def display_result(a, q):
x_array = []
y_array = []
try:
x_array = q.get(block=False) # get data
y_array = q.get(block=False)
except Empty:
#a.clear()
timeout_millis = round(100 - (5000 * time.time()) % 100)
self.parent.after(timeout_millis, display_result, a, q)
a.plot(x_array, y_array)
canvas.draw()
def get_result(q):
x_array, y_array = self.get_latest_data(2)
q.put(x_array) # put data in FIFO queue x coords array
q.put(y_array) # put data in FIFO queue y coords array
display_result(a, result_queue)
def onExit(self):
self.quit()
def main():
root = tk.Tk()
my_gui = tkChartGUI(root)
root.mainloop()
if __name__ == '__main__':
main()
To run the function get_latest_data() after five secoonds, do the following:
root.after(5000, get_latest_data)
root.after : A tkinter function to execute a function after some time has elapsed.
5000 : 5000 milliseconds, i.e. 5 seconds.
get_latest_data : The function we are calling without the parentheses. To pass it arguments, use lambda, like this:
root.after(5000, lambda: get_latest_data(variable))

Animated plot using matplotlib with gtk

I did the following example code just to test how to integrate an animated matplotlib plot with pygtk. However, I get some unexpected behaviors when I run it.
First, when I run my program and click on the button (referred to as button1 in the code), there is another external blank window which shows up and the animated plot starts only after closing this window.
Secondly, when I click on the button many times, it seems that there is more animations which are created on top of each other (which gives the impression that the animated plot speeds up). I have tried to call animation.FuncAnimation inside a thread (as you can see in the comment at end of the function on_button1_clicked), but the problem still the same.
Thirdly, is it a good practice to call animation.FuncAnimation in a thread to allow the user to use the other functions of the gui ? Or should I rather create a thread inside the method animate (I guess this will create too many threads quickly) ? I am not sure how to proceed.
Here is my code:
import gtk
from random import random
import numpy as np
from multiprocessing.pool import ThreadPool
import matplotlib.pyplot as plt
import matplotlib.animation as animation
#from matplotlib.backends.backend_gtk import FigureCanvasGTK as FigureCanvas
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
#from matplotlib.backends.backend_gtkcairo import FigureCanvasGTKCairo as FigureCanvas
class HelloWorld:
def __init__(self):
interface = gtk.Builder()
interface.add_from_file('interface.glade')
self.dialog1 = interface.get_object("dialog1")
self.label1 = interface.get_object("label1")
self.entry1 = interface.get_object("entry1")
self.button1 = interface.get_object("button1")
self.hbox1 = interface.get_object("hbox1")
self.fig, self.ax = plt.subplots()
self.X = [random() for x in range(10)]
self.Y = [random() for x in range(10)]
self.line, = self.ax.plot(self.X, self.Y)
self.canvas = FigureCanvas(self.fig)
# self.hbox1.add(self.canvas)
self.hbox1.pack_start(self.canvas)
interface.connect_signals(self)
self.dialog1.show_all()
def gtk_widget_destroy(self, widget):
gtk.main_quit()
def on_button1_clicked(self, widget):
name = self.entry1.get_text()
self.label1.set_text("Hello " + name)
self.ani = animation.FuncAnimation(self.fig, self.animate, np.arange(1, 200), init_func=self.init, interval=25, blit=True)
'''
pool = ThreadPool(processes=1)
async_result = pool.apply_async(animation.FuncAnimation, args=(self.fig, self.animate, np.arange(1, 200)), kwds={'init_func':self.init, 'interval':25, 'blit':True} )
self.ani = async_result.get()
'''
plt.show()
def animate(self, i):
# Read XX and YY from a file or whateve
XX = [random() for x in range(10)] # just as an example
YY = [random() for x in range(10)] # just as an example
self.line.set_xdata( XX )
self.line.set_ydata( YY )
return self.line,
def init(self):
self.line.set_ydata(np.ma.array(self.X, mask=True))
return self.line,
if __name__ == "__main__":
HelloWorld()
gtk.main()

Unable to update Tkinter matplotlib graph with buttons and custom data

I'm working on creating a program that utilizes Tkinter and matplotlib. I have 2 lists of lists (one for x-axis, one for y-axis) and I'm looking to have a button that can switch between the lists within the list. I took much of the code from the question Interactive plot based on Tkinter and matplotlib, but I can't get the graph to update when the button is pressed. I'm quite new to using classes and having a bit of difficulty understanding them.
tft is the x-data tf1 is the y-data in my code.
Example of data:
x-data = [[1,2,3,4,5],[10,11,13,15,12,19],[20,25,27]]
y-data = [[5.4,6,10,11,6],[4,6,8,34,20,12],[45,25,50]]
My code below will graph one of the lists within a list, but won't switch between the lists within that list when the button is pressed. The correct value for event_num also prints in the command window (the issue of event_num was solved in a previous questions here).
There is no error that appears, the program only prints the number (when the button is pressed), but doesn't update the graph with the new data from the list.
Preliminary Code (no issues--or there shouldn't be any)
#Importing Modules
import glob
from Tkinter import *
from PIL import Image
from Text_File_breakdown import Text_File_breakdown
import re
import matplotlib.pyplot as plt
from datetime import datetime
#Initializing variables
important_imgs=[]
Image_dt=[]
building=[]
quick=[]
num=0
l=0
match=[]
#Getting the names of the image files
image_names=glob.glob("C:\Carbonite\EL_36604.02_231694\*.jpeg")
#image= Image.open(images_names[1])
#image.show()
#Text_File_breakdown(file,voltage limit,pts after lim, pts before lim)
tft,tf1,tf2=Text_File_breakdown('C:\Carbonite\EL_36604.02_231694.txt',3.0,5,5)
#tft= time of voltages tf1=Voltage signal 1 tf2=Voltage signal 2
#Test Settings: 'C:\Carbonite\EL_36604.02_231694.txt',3.0,5,5
#Getting the Dates from the image names
for m in image_names:
Idt=re.search("([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}.[0-9]{2}.[0-9]{2})", m)
Im_dat_tim=Idt.group(1)
Im_dat_tim=datetime.strptime(Im_dat_tim, '%Y-%m-%d %H.%M.%S')
Image_dt.append(Im_dat_tim)
Im_dat_tim=None
#Looking for the smallest difference between the voltage and image dates and associating an index number (index of the image_names variable) with each voltage time
for event in range(len(tft)):
for i in range(len(tft[event])):
diff=[tft[event][i]-Image_dt[0]]
diff.append(tft[event][i]-Image_dt[0])
while abs(diff[l])>=abs(diff[l+1]):
l=l+1
diff.append(tft[event][i]-Image_dt[l])
match.append(l)
l=0
#Arranging the index numbers (for the image_names variable) in a list of lists like tft variable
for count in range(len(tft)):
for new in range(len(tft[count])):
quick.append(match[num])
num=num+1
building.append(quick)
quick=[]
plt.close('all')
fig, ax = plt.subplots(1)
ax.plot(tft[1],tf1[1],'.')
# rotate and align the tick labels so they look better
fig.autofmt_xdate()
# use a more precise date string for the x axis locations in the
# toolbar
import matplotlib.dates as mdates
ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d')
plt.title('Single Event')
Continuation of code/Portion of code where the issue is:
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import numpy as np
class App:
def __init__(self, master):
self.event_num = 1
# Create a container
frame = Frame(master)
# Create 2 buttons
self.button_left = Button(frame,text="< Previous Event",
command=self.decrease)
self.button_left.grid(row=0,column=0)
self.button_right = Button(frame,text="Next Event >",
command=self.increase)
self.button_right.grid(row=0,column=1)
fig = Figure()
ax = fig.add_subplot(111)
fig.autofmt_xdate()
import matplotlib.dates as mdates
ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d')
self.line, = ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas = FigureCanvasTkAgg(fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=1,column=0)
frame.grid(row=0,column=0)
def decrease(self):
self.event_num -= 1
print self.event_num
self.line, = ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas.draw()
#self.canvas.draw(tft[self.event_num],tf1[self.event_num],'.')
#self.line.set_xdata(tft[event_num])
#self.line.set_ydata(tf1[event_num])
def increase(self):
self.event_num += 1
print self.event_num
self.line, = ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas.draw()
#self.canvas.draw(tft[self.event_num],tf1[self.event_num],'.')
#self.set_xdata(tft[event_num])
#self.set_ydata(tf1[event_num])
root = Tk()
app = App(root)
root.mainloop()
The problem is that you are constantly plotting on a different set of axes than you think. self.line, = ax.plot(tft[self.event_num],tf1[self.event_num],'.') refers to the axes that you created outside your class, not the axes in the figure you created in the App class. The problem can be remedied by creating self.fig and self.ax attributes:
class App:
def __init__(self, master):
self.event_num = 1
# Create a container
frame = Frame(master)
# Create 2 buttons
self.button_left = Button(frame,text="< Previous Event",
command=self.decrease)
self.button_left.grid(row=0,column=0)
self.button_right = Button(frame,text="Next Event >",
command=self.increase)
self.button_right.grid(row=0,column=1)
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
self.fig.autofmt_xdate()
import matplotlib.dates as mdates
self.ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d')
self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas = FigureCanvasTkAgg(self.fig,master=master)
self.canvas.show()
self.canvas.get_tk_widget().grid(row=1,column=0)
frame.grid(row=0,column=0)
def decrease(self):
self.event_num -= 1
print self.event_num
self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas.draw()
def increase(self):
self.event_num += 1
print self.event_num
self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
self.canvas.draw()
Another (possible) problem is that the data gets appended to the plot instead of being replaced. There are two ways to fix this:
Turn hold off: self.ax.hold(False) somewhere in __init__() before you plot anything.
Actually replace the plot data: Replace the line
self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
with
self.line.set_xdata(tft[self.event_num])
self.line.set_ydata(tf1[self.event_num])
You are plotting to an axes in the global namespace but redrawing the canvas on another, unrelated global object. I think your issues are about global vs. local scope and the self object in object-oriented programming.
Python scoping
Variables created in your file without indentation are in a file's global scope. class and def statements create local scopes. References to a variable name are resolved by checking scopes in this order:
Local scope
Enclosing local scope (enclosing defs only, not classes)
Global scope
Builtins
That cascade is only for references. Assignments to a variable name assign to the current global or local scope, period. A variable of that name will be created in the current scope if it doesn't already exist.
The bindings of local variables are deleted when a function returns. Values that are not bound are eventually garbage collected. If an assignment binds a global variable to a local variable's value, the value will persist with the global.
Applying this to your code
There are two sets of figure/axes objects created in your code.
# From your top chunk of code
from Tkinter import *
import matplotlib.pyplot as plt
# This line creates a global variable ax:
fig, ax = plt.subplots(1)
# From your bottom chunk of code
class App(self, master):
def __init__(self, master):
fig = Figure()
# This line creates a local variable ax:
ax = fig.add_subplot(111)
# This line references the local variable ax:
ax.fmt_xdata = mdates.DateFormatter('%Y-%m-%d')
# This line references the local variable ax, and binds its value
# to the attribute line of the self object,
# which is the global variable app:
self.line, = ax.plot(x[self.event_n],y[self.event_n])
def increase(self):
self.event_num += 1
# This line references the global variable ax
self.line, = ax.plot(x[self.event_n],y[self.event_n])
# This line updates the object's canvas but the plot
# was made on the global axes:
self.canvas.draw()
app = App(root)
You can fix this up by always referencing an Axes associated with your Tkinter root window:
self.fig, self.ax = plt.subplots(1, 1) to create enduring Axes.
self.ax.cla() to remove the previous selection's data
self.ax.plot() to add the data at the current self.event_num index
self.ax.set_xlim(x_min*0.9, x_max*0.9) and self.ax.set_ylim(y_min*1.1, y_max*1.1) to keep the axes window consistent so as to visualize movement of the mean of data among inner lists. The x_max, y_max, x_min, and y_min can be determined before the main event loop by flattening the x and y lists and then using builtins max() and min().

Categories