使用带有 pylab/matplotlib 嵌入的 Tkinter 播放、暂停、停止功能的彩色绘图动画:无法更新图形/画布?
Posted
技术标签:
【中文标题】使用带有 pylab/matplotlib 嵌入的 Tkinter 播放、暂停、停止功能的彩色绘图动画:无法更新图形/画布?【英文标题】:color plot animation with play, pause, stop cabability using Tkinter with pylab/matplotlib embedding: can't update figure/canvas? 【发布时间】:2011-03-18 17:20:52 【问题描述】:我查看过之前的问题,但没有找到足够具体的问题,如果重复出现,非常抱歉。 目标:GUI 使用 pylab 的 pcolor 绘制的不同矩阵数据不断更新图形,以便有一个正在运行的动画。但用户应该能够通过 Tkinter 小部件按钮播放、暂停、停止动画。
在我使用 set_array()、draw() 和 canvas.manager.after() 得到 matplotlib 的答案之前...,我有可以启动动画的工作代码,但我不知道如何在仅使用 matplotlib 功能时停止或暂停它,所以我决定直接使用 Tkinter 而不是 matplotlib 的 Tcl/Tk 包装器。这是工作代码,只是为了防止有人有任何想法。但继续真正的问题。
# mouse click the "Play" button widget to play animation
# PROBLEMS:
# 1. can't pause or stop animation. once loop starts it cant be broken
# 2. set_array attribute for pcolor PolyCollection object only updates the matrix
# C of pcolor, however for my actual application, I will be using pcolor(x,y,C)
# and will have new x,y, and C per plot. Unlike line object, where set_xdata and
# set_ydata are used, I can't find an analogy to pcolor. If I were updating an
# image I could use set_data(x,y,C), but i am not importing an image. I assume
# pcolor is still my best bet unless (as in Matlab) there is an equivalent
# image(x,y,C) function?
import time as t
from pylab import *
from matplotlib.widgets import Button
ion()
def pressPlay(event):
#fig=figure()
ax = subplot(111)
subplots_adjust(bottom=0.2)
c=rand(5,5)
cplot=pcolor(c)
draw()
for i in range(5):
c=rand(5,5)
cplot.set_array(c.ravel())
cplot.autoscale()
title('Ionosphere '+str(i+1))
t.sleep(3)
draw()
axPlay = axes([0.7, 0.05, 0.1, 0.075])
bPlay = Button(axPlay, 'Play')
bPlay.on_clicked(pressPlay)
顺便说一句:在导入 pylab 时,TkAgg 后端会自动设置为在 matplotlib 中使用……我想。或者不知何故我自动使用 TkAgg。我正在运行 Linux、Python 2.6.4、Ipython 0.10。
我操纵了从 Daniweb IT 讨论社区中找到的代码,以便使用 Tkinter 和 update_idletasks() 函数我可以播放、鼓掌、停止标签小部件的颜色变化。只要安装了 Tkinter,它就可以单独在 python 上运行。没有使用 matplotlib 或 pylab。这是工作代码,也是我质疑的最终代码的主干。
# This is meant to draw Start and Stop buttons and a label
# The Start button should start a loop in which the label
# is configured to change color by looping through a color list.
# At each pass through the loop the variable self.stop is checked:
# if True the loop terminates.
# The Stop button terminates the loop by setting the
# variable self.stop to True.
# The Start button restarts the animation from the beginning
# if Stop was hit last, or restarts the animation from where it left off
# if pause was hit last.
# The loop also terminates on the last color of the list, as if stop were hit
from Tkinter import *
colors = ['red','green','blue','orange','brown','black','white','purple','violet']
numcol=len(colors)
class SGWidget(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.top_frame = Frame(bg='green')
self.top_frame.grid()
# enter event loop until all idle callbacks have been called.
self.top_frame.update_idletasks()
self.makeToolbar()
# construct a label widget with the parent frame top_frame
self.label = Label(self.top_frame,text = 'Text',bg='orange')
self.label.grid()
# initialize (time index t)
self.t=0
def makeToolbar(self):
self.toolbar_text = ['Play','Pause','Stop']
self.toolbar_length = len(self.toolbar_text)
self.toolbar_buttons = [None] * self.toolbar_length
for toolbar_index in range(self.toolbar_length):
text = self.toolbar_text[toolbar_index]
bg = 'yellow'
button_id = Button(self.top_frame,text=text,background=bg)
button_id.grid(row=0, column=toolbar_index)
self.toolbar_buttons[toolbar_index] = button_id
def toolbar_button_handler(event, self=self, button=toolbar_index):
return self.service_toolbar(button)
# bind mouse click on start or stop to the toolbar_button_handler
button_id.bind("<Button-1>", toolbar_button_handler)
# call blink() if start and set stop when stop
def service_toolbar(self, toolbar_index):
if toolbar_index == 0:
self.stop = False
print self.stop
self.blink()
elif toolbar_index == 1:
self.stop = True
print self.stop
elif toolbar_index == 2:
self.stop = True
print self.stop
self.t=0
# while in start, check if stop is clicked, if not, call blink recursivly
def blink(self):
if not self.stop:
print 'looping',self.stop
self.label.configure(bg=colors[self.t])
self.t += 1
if self.t == numcol: # push stop button
self.service_toolbar(2)
self.label.update_idletasks()
self.after(500, self.blink)
if __name__ == '__main__':
SGWidget().mainloop()
然后,在 matplotlib 示例 embedding_in_tk.html,http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_tk.html 的帮助下,我操纵了前面的代码来动画连接到 pcolor 图形的画布。但是,使用 canvas.get_tk_widget() 更新画布并不能达到我假设的效果,因为它之前的命令会重新绘制 pcolor()。所以我猜我每次重新绘制时都必须重新连接画布和图形?但我不知道怎么做。我希望我什至使用 update_idletasks() 走在正确的轨道上???
那么,使用下面的代码,当代码在 Play 中循环时,我看到的只是相同的图,而不是更新的图形?这是我的主要问题和疑问。
from pylab import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Tkinter import *
colors=[None]*10
for i in range(len(colors)):
colors[i]=rand(5,5)
#colors = ['red','green','blue','orange','brown','black','white','purple','violet']
numcol=len(colors)
class App(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.top=Frame()
self.top.grid()
self.top.update_idletasks()
self.makeWidgets()
self.makeToolbar()
def makeWidgets(self):
# figsize (w,h tuple in inches) dpi (dots per inch)
#f = Figure(figsize=(5,4), dpi=100)
self.f = Figure()
self.a = self.f.add_subplot(111)
self.a.pcolor(rand(5,5))
# a tk.DrawingArea
self.canvas = FigureCanvasTkAgg(self.f, master=self.top)
self.canvas.get_tk_widget().grid(row=3,column=0,columnspan=3)
self.bClose = Button(self.top, text='Close',command=self.top.destroy)
self.bClose.grid()
#self.label = Label(self.top, text = 'Text',bg='orange')
#self.label.grid()
# initialize (time index t)
self.t=0
def makeToolbar(self):
self.toolbar_text = ['Play','Pause','Stop']
self.toolbar_length = len(self.toolbar_text)
self.toolbar_buttons = [None] * self.toolbar_length
for toolbar_index in range(self.toolbar_length):
text = self.toolbar_text[toolbar_index]
bg = 'yellow'
button_id = Button(self.top,text=text,background=bg)
button_id.grid(row=0, column=toolbar_index)
self.toolbar_buttons[toolbar_index] = button_id
def toolbar_button_handler(event, self=self, button=toolbar_index):
return self.service_toolbar(button)
button_id.bind("<Button-1>", toolbar_button_handler)
# call blink() if start and set stop when stop
def service_toolbar(self, toolbar_index):
if toolbar_index == 0:
self.stop = False
print self.stop
self.blink()
elif toolbar_index == 1:
self.stop = True
print self.stop
elif toolbar_index == 2:
self.stop = True
print self.stop
self.t=0
# while in start, check if stop is clicked, if not, call blink recursivly
def blink(self):
if not self.stop:
print 'looping',self.stop
self.a.pcolor(colors[self.t])
#draw()
#self.label.configure(bg=colors[self.t])
self.t += 1
if self.t == numcol: # push stop button
self.service_toolbar(2)
self.canvas.get_tk_widget().update_idletasks()
#self.label.update_idletasks()
self.after(500, self.blink)
#root = Tk()
app=App()
app.mainloop()
感谢您的帮助!
【问题讨论】:
【参考方案1】:在你的眨眼函数中,在调用空闲任务之前添加一个self.canvas.show()
:
self.canvas.show() # insert this line
self.canvas.get_tk_widget().update_idletasks()
【讨论】:
哇,这很简单!我在脚本末尾多次使用 show() ,但我没有意识到它也是一个单独的画布方法。谢谢! ars,我将重新发布另一个问题,并附上指向此问题的链接,但如果你能回应...如果我想为轮廓设置动画而不是彩色图,如果我只是将绘图命令更改为: self.a.contour(colors[self.t]) 等高线图显示,但不是像 pcolor 那样每次都将画布更新为新图,而是在自己身上过度绘制。您知道如何清除以前的等高线图,而不使用明显的 cla() 以使我看不到白色吗?谢谢 好吧,如果我 self.a.cla() 就在 self.a.contour() 之前,我实际上并没有在下一个情节之前看到画布清晰,所以它可以工作。但是,如果有比 cla() 更好的动画设置方法,我会更喜欢它,因为我的绘图中还会有其他数据,并且不一定要清除它。另外,我也将使用 self.a.plot() 但这似乎是轮廓()的确切场景 AmyS,听起来不错。唯一的问题是性能以及您的动画需要有多流畅。如果它看起来不错,请不要担心。否则,您可以查看“blit”操作,例如matplotlib.sourceforge.net/examples/animation/… 这是另一个页面,其中包含您可能会发现有用的食谱:scipy.org/Cookbook/Matplotlib/Animations以上是关于使用带有 pylab/matplotlib 嵌入的 Tkinter 播放、暂停、停止功能的彩色绘图动画:无法更新图形/画布?的主要内容,如果未能解决你的问题,请参考以下文章
使用 UITableViewAutomaticDimension 制作带有嵌入式 UICollectionView 的 UITableView
带有嵌入对象/结构的 cudaMalloc/cudaMemcpy