Unable to update Tkinter matplotlib graph with but

2020-02-11 11:10发布

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

2条回答
forever°为你锁心
2楼-- · 2020-02-11 11:42

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:

  1. Turn hold off: self.ax.hold(False) somewhere in __init__() before you plot anything.
  2. 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])
    
查看更多
甜甜的少女心
3楼-- · 2020-02-11 11:49

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().
查看更多
登录 后发表回答