Tkinter规模小部件不实时更新

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tkinter规模小部件不实时更新相关的知识,希望对你有一定的参考价值。

我正在尝试使用Tkinter.Scale来生成一个更改matplotlib中数据的滑块,但是我在实时更新绘图时遇到问题,而不必每次都重新生成绘图窗口。

如果我这样运行我的代码,那么它运行良好,但每次我移动滑块时都会创建一个新窗口,这很难在视觉上聚焦。

import matplotlib.pyplot as plt
import numpy as np
import tkinter as tk

master = tk.Tk()

def update(val):
    plt.close()

    global idx
    idx = np.array(w.get())

    t1 = np.arange(0.0, 5.0, 0.1)
    a1 = np.sin(idx*np.pi *t1)
    a2 = np.sin((idx/2)*np.pi*t1)
    a3 = np.sin((idx/4)*np.pi*t1)
    a4 = np.sin((idx/8)*np.pi*t1)

    """Plotting of data"""  
    fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, sharey = False) # create figure
    plt.tight_layout()
    ax1.plot(t1, a1) 
    ax2.plot(t1, a2) 
    ax3.plot(t1, a3) 
    ax4.plot(t1, a4) 

w = tk.Scale(master, from_=0, to=10, command = update)
w.pack()
tk.mainloop()

我希望滑块每次只是简单地重新绘制数据,但是当我移动命令以创建函数前面的图形时,如下所示,当我移动滑块时它不再更新图形,而是仅在滑块时关闭了。

import matplotlib.pyplot as plt
import numpy as np
import tkinter as tk


fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, sharey = False) # create figure
plt.tight_layout()

master = tk.Tk()

def update(val):

    ax1.cla() # clears the entire current figure but leaves the window
    ax2.cla()
    ax3.cla()
    ax4.cla()

    global idx
    idx = np.array(w.get())

    t1 = np.arange(0.0, 5.0, 0.1)
    a1 = np.sin(idx*np.pi *t1)
    a2 = np.sin((idx/2)*np.pi*t1)
    a3 = np.sin((idx/4)*np.pi*t1)
    a4 = np.sin((idx/8)*np.pi*t1)

    """Plotting of data"""  
    ax1.plot(t1, a1) 
    ax2.plot(t1, a2) 
    ax3.plot(t1, a3) 
    ax4.plot(t1, a4) 

w = tk.Scale(master, from_=0, to=10, command = update)
w.pack()
tk.mainloop()

有没有人有任何关于如何获取数据的想法,而不是整个窗口,在滑块移动时更新?如果已经问过这个问题,我会道歉,但如果是这样的话,我就找不到了。我不能在实际脚本中使用matplotlib滑块选项我正在扫描从.txt文件中提取的字符串变量,而不是整数。

答案

对现有代码的一个简单修复是实际显示图形(fig.show)并确保每次移动滑块时重绘(fig.canvas.draw_idle()

import matplotlib.pyplot as plt
import numpy as np
import tkinter as tk


fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, sharey = False) # create figure
plt.tight_layout()

master = tk.Tk()

def update(val):

    ax1.cla() # clears the entire current figure but leaves the window
    ax2.cla()
    ax3.cla()
    ax4.cla()

    global idx
    idx = np.array(w.get())

    t1 = np.arange(0.0, 5.0, 0.1)
    a1 = np.sin(idx*np.pi *t1)
    a2 = np.sin((idx/2)*np.pi*t1)
    a3 = np.sin((idx/4)*np.pi*t1)
    a4 = np.sin((idx/8)*np.pi*t1)

    """Plotting of data"""  
    ax1.plot(t1, a1) 
    ax2.plot(t1, a2) 
    ax3.plot(t1, a3) 
    ax4.plot(t1, a4)
    fig.canvas.draw_idle()

fig.show()
w = tk.Scale(master, from_=0, to=10, command = update)
w.pack()
tk.mainloop()

如果目标不一定是提供tk滑块,则在内置的Slider中使用适当的解决方案。这具有完全平台和后端独立的优点。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

fig, axes = plt.subplots(4, sharey = False) # create figure
plots = [ax.plot([])[0] for ax in axes]

fig.tight_layout()
fig.subplots_adjust(bottom=0.12)

t1 = np.arange(0.0, 5.0, 0.1)

def update(idx):

    a1 = np.sin(idx*np.pi *t1)
    a2 = np.sin((idx/2)*np.pi*t1)
    a3 = np.sin((idx/4)*np.pi*t1)
    a4 = np.sin((idx/8)*np.pi*t1)

    for plot, a in zip(plots, [a1,a2,a3,a4]):
        plot.set_data(t1, a)
        plot.axes.relim()
        plot.axes.autoscale()

    fig.canvas.draw_idle()

update(5)
slider = Slider(fig.add_axes([.1,.04,.6,.03]), "Label", 0,10,5)
slider.on_changed(update)
plt.show()
另一答案

从你的问题的描述我假设你想要一些图表更新与滑块的每个刻度,你不希望每次重绘的情节。你提供的代码甚至没有创建一个情节,因为你没有对fig做任何事情。那说你需要改变一些。

首先在tkinter IMO中创建一个绘图时,最好在matplotlib中使用FugureCanvasTkAgg方法。这将允许我们将所有内容绘制到画布上。我们也希望cla()每个轴而不是close()的情节。如果您关闭绘图,则需要重建它,因此最好的办法是每次更新时清除轴。

在这里,我首先从滑块构建值为零的图,然后让函数仅更新子图。这允许我们保持绘图完整并且仅更改子图中的线,因此我们不需要每次都重新创建绘图。

看看下面的代码。

更新:在工具栏中添加了“TkAgg”每个ImportanceOfBeingErnest评论。

import numpy as np
import tkinter as tk
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg


master = tk.Tk()

def update(val):
    global idx, chart_frame, ax1, ax2, ax3, ax4
    ax1.cla()
    ax2.cla()
    ax3.cla()
    ax4.cla()
    idx = np.array(w.get())
    t1 = np.arange(0.0, 5.0, 0.1)
    a1 = np.sin(idx*np.pi *t1)
    a2 = np.sin((idx/2)*np.pi*t1)
    a3 = np.sin((idx/4)*np.pi*t1)
    a4 = np.sin((idx/8)*np.pi*t1)
    ax1.plot(t1, a1) 
    ax2.plot(t1, a2) 
    ax3.plot(t1, a3) 
    ax4.plot(t1, a4) 
    canvas.draw()

w = tk.Scale(master, from_=0, to=10, command = update)
w.grid(row=0, column=0)

idx = np.array(w.get())

fig, (ax1, ax2, ax3, ax4) = plt.subplots(4, sharey = False)
plt.tight_layout()

canvas = FigureCanvasTkAgg(fig, master)
canvas.get_tk_widget().grid(row=0, column=1)
toolbar_frame = tk.Frame(master)
toolbar_frame.grid(row=1, column=1, sticky="w")
NavigationToolbar2TkAgg(canvas, toolbar_frame)
canvas.draw()

master.mainloop()

我个人觉得用非OOP方法编写代码是令人沮丧和难以管理的。我认为这会更好用OOP编写,所以这是我的例子。

import numpy as np
import tkinter as tk
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg


class MyPlot(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.w = tk.Scale(self, from_=0, to=10, command = self.update)
        self.w.grid(row=0, column=0)

        fig, (self.ax1, self.ax2, self.ax3, self.ax4) = plt.subplots(4, sharey = False)
        plt.tight_layout()

        self.canvas = FigureCanvasTkAgg(fig, self)
        self.canvas.get_tk_widget().grid(row=0, column=1)
        toolbar_frame = tk.Frame(self)
        toolbar_frame.grid(row=1, column=1, sticky="w")
        NavigationToolbar2TkAgg(self.canvas, toolbar_frame)
        self.canvas.draw()

    def update(self, val):
        self.ax1.cla()
        self.ax2.cla()
        self.ax3.cla()
        self.ax4.cla()
        idx = np.array(self.w.get())
        t1 = np.arange(0.0, 5.0, 0.1)
        a1 = np.sin(idx*np.pi *t1)
        a2 = np.sin((idx/2)*np.pi*t1)
        a3 = np.sin((idx/4)*np.pi*t1)
        a4 = np.sin((idx/8)*np.pi*t1)
        self.ax1.plot(t1, a1) 
        self.ax2.plot(t1, a2) 
        self.ax3.plot(t1, a3) 
        self.ax4.plot(t1, a4) 
        self.canvas.draw()

if __name__ == '__main__':
    app = MyPlot()
    app.mainloop()

我会使用draw_idle()作为ImportanceOfBeingErnest提到,但我目前看到一个错误,可能会导致绘图没有绘制正确的值。

以此屏幕截图为例。在快速滑动Bar并快速释放鼠标按钮后,绘图的值不会正确更新。如果我在向上滑动杆的同时松开鼠标按钮,则只会发生接缝。

这可能只是与Tkinter或matplotlib和Tkinter的某种组合有关的问题。我想它没有绘制最后一个滑块值,因为Tkinter的主循环由于我的鼠标释放而没有空闲,因为这是Tkinter中的一个事件,可能是干扰。

enter image description here

以上是关于Tkinter规模小部件不实时更新的主要内容,如果未能解决你的问题,请参考以下文章

如何更新 Tkinter 标签小部件的图像?

在 tkinter 小部件中显示子进程的实时输出

tkinter Text 小部件不打印输入数据

如何在 tkinter 中找出当前小部件的大小?

在图像框架上叠加tkinter小部件

如何在tkinter中找出当前的小部件大小?