无法使用按钮和自定义数据更新 Tkinter matplotlib 图
Posted
技术标签:
【中文标题】无法使用按钮和自定义数据更新 Tkinter matplotlib 图【英文标题】:Unable to update Tkinter matplotlib graph with buttons and custom data 【发布时间】:2016-01-20 15:13:36 【问题描述】:我正在创建一个使用Tkinter
和matplotlib
的程序。我有 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.fig
和self.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()
另一个(可能的)问题是数据被附加到绘图而不是被替换。有两种方法可以解决此问题:
-
在绘制任何内容之前关闭
hold
:self.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()
时重置。当hold
是True
时,调用plot()
会将它们创建的线对象附加到绘图的当前列表中。当hold
为False
时,新的线对象会覆盖之前的对象。我认为颜色序列和其他一些设置也被重置了。经过考虑,我也更喜欢选项 2,因为它可以让您更好地控制显示的内容。【参考方案2】:
您正在绘制到全局命名空间中的轴,但在另一个不相关的全局对象上重新绘制画布。我认为您的问题是关于全局与本地范围以及面向对象编程中的 self
对象。
Python 作用域
在您的文件中创建的没有缩进的变量在文件的全局范围内。 class
和 def
语句创建 local 范围。通过按以下顺序检查范围来解析对变量名的引用:
def
s,不封闭class
es)
全球范围
内建
该级联仅用于引用。 分配到一个变量名,分配给当前的全局或局部范围,句号。如果该名称的变量尚不存在,则将在当前范围内创建该名称的变量。
当函数返回时,局部变量的绑定被删除。未绑定的值最终会被垃圾回收。如果赋值将全局变量绑定到局部变量的值,则该值将与全局变量保持一致。
将此应用于您的代码
在您的代码中创建了两组图形/轴对象。
# 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_max
、y_max
、x_min
和 y_min
可以在主事件循环之前通过展平 x 和 y 列表然后使用内置函数 max()
和 min()
来确定。
【讨论】:
以上是关于无法使用按钮和自定义数据更新 Tkinter matplotlib 图的主要内容,如果未能解决你的问题,请参考以下文章
在运行时无法更改和自定义自定义 UITableViewCell 中的 UIButtons 的标题
单击时如何将按钮值从自定义小部件传递到 tkinter 中的主应用程序