无法使用按钮和自定义数据更新 Tkinter matplotlib 图

Posted

技术标签:

【中文标题】无法使用按钮和自定义数据更新 Tkinter matplotlib 图【英文标题】:Unable to update Tkinter matplotlib graph with buttons and custom data 【发布时间】:2016-01-20 15:13:36 【问题描述】:

我正在创建一个使用Tkintermatplotlib 的程序。我有 2 个列表列表(一个用于 x 轴,一个用于 y 轴),我希望有一个可以在列表中的列表之间切换的按钮。我从问题Interactive plot based on Tkinter and matplotlib 中获取了大部分代码,但是当按下按钮时我无法更新图表。我对使用类很陌生,理解它们有点困难。

tft 是 x 数据 tf1 是我代码中的 y 数据。

数据示例:

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]]

我下面的代码将绘制列表中的一个列表,但在按下按钮时不会在该列表中的列表之间切换。 event_num 的正确值也会打印在命令窗口中(event_num 的问题在之前的问题 here 中已解决)。

没有出现错误,程序仅打印数字(按下按钮时),但不会使用列表中的新数据更新图表。

初步代码(没有问题——或者不应该有)

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

代码的延续/问题所在的代码部分:

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

【问题讨论】:

您介意在代码顶部包含您的导入吗? 这个问题是***.com/questions/33222030/…的续集 【参考方案1】:

问题在于,您不断地在一组与您想象的不同的轴上绘图。 self.line, = ax.plot(tft[self.event_num],tf1[self.event_num],'.') 指的是您在类之外创建的轴,而不是您在 App 类中创建的图中的轴。该问题可以通过创建self.figself.ax 属性来解决:

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

另一个(可能的)问题是数据被附加到绘图而不是被替换。有两种方法可以解决此问题:

    在绘制任何内容之前关闭 holdself.ax.hold(False)__init__() 中的某个位置。

    实际替换绘图数据:替换行

    self.line, = self.ax.plot(tft[self.event_num],tf1[self.event_num],'.')
    

    self.line.set_xdata(tft[self.event_num])
    self.line.set_ydata(tf1[self.event_num])
    

【讨论】:

我更改了变量以包含 self. 并使用了您的选项 2。我添加了来自 this question 的轴修饰符。它的工作方式和我想象的完全一样。谢谢。 很高兴有帮助。您选择选项 2 而不是 1 的任何原因? 选项 2 是我相当熟悉并且玩过的东西。我不确定选项 1 中发生了什么。 选项 1 是来自 MATLAB 时执行此操作的标准方法。坐标区的hold 属性确定坐标区是否在每次调用plot() 时重置。当holdTrue 时,调用plot() 会将它们创建的线对象附加到绘图的当前列表中。当holdFalse 时,新的线对象会覆盖之前的对象。我认为颜色序列和其他一些设置也被重置了。经过考虑,我也更喜欢选项 2,因为它可以让您更好地控制显示的内容。【参考方案2】:

您正在绘制到全局命名空间中的轴,但在另一个不相关的全局对象上重新绘制画布。我认为您的问题是关于全局与本地范围以及面向对象编程中的 self 对象。

Python 作用域

在您的文件中创建的没有缩进的变量在文件的全局范围内。 classdef 语句创建 local 范围。通过按以下顺序检查范围来解析对变量名的引用:

本地范围 封闭本地范围(仅封闭defs,不封闭classes) 全球范围 内建

该级联仅用于引用分配到一个变量名,分配给当前的全局或局部范围,句号。如果该名称的变量尚不存在,则将在当前范围内创建该名称的变量。

当函数返回时,局部变量的绑定被删除。未绑定的值最终会被垃圾回收。如果赋值将全局变量绑定到局部变量的值,则该值将与全局变量保持一致。

将此应用于您的代码

在您的代码中创建了两组图形/轴对象。

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

您可以通过始终引用与您的 Tkinter 根窗口关联的 Axes 来解决此问题:

self.fig, self.ax = plt.subplots(1, 1) 创造持久的斧头。 self.ax.cla() 删除之前选择的数据 self.ax.plot()在当前self.event_num索引处添加数据 self.ax.set_xlim(x_min*0.9, x_max*0.9)self.ax.set_ylim(y_min*1.1, y_max*1.1) 保持轴窗口一致,以便可视化数据平均值在内部列表之间的移动。 x_maxy_maxx_miny_min 可以在主事件循环之前通过展平 x 和 y 列表然后使用内置函数 max()min() 来确定。

【讨论】:

以上是关于无法使用按钮和自定义数据更新 Tkinter matplotlib 图的主要内容,如果未能解决你的问题,请参考以下文章

Python / Tkinter:使用按钮更新类变量

在运行时无法更改和自定义自定义 UITableViewCell 中的 UIButtons 的标题

PayPal 结帐按钮和自定义变量

单击时如何将按钮值从自定义小部件传递到 tkinter 中的主应用程序

单击后更新按钮的位置? (Tkinter Python GUI)

使用 ProgressBar 和自定义按钮显示 TaskDialog 时出错