Tkinter规模小部件不实时更新

时间:2018-07-23 13:19:35

标签: python matplotlib 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文件提取的字符串变量,而不是整数。

2 个答案:

答案 0 :(得分:0)

从对问题的描述中,我假设您希望某些图随着滑块的每个刻度线而更新,并且您不希望每次都重新创建该图。您提供的代码甚至无法创建图,因为您对fig不做任何事情。也就是说,您需要进行一些更改。

首先在tkinter IMO中创建图时,最好在matplotlib中使用FugureCanvasTkAgg方法。这将使我们能够将所有内容绘制到画布上。我们还希望cla()而不是close()每个坐标轴。如果您关闭该图,则需要对其进行重建,因此最好的方法是每次更新时都要清除轴。

在这里,我将首先使用滑块的零值构建图,然后让该函数仅更新子图。这使我们可以保持绘图完整无缺,只更改子图中的线条,因此我们不必每次都重新创建绘图。

看看下面的代码。

更新:在工具栏中添加了内容,并为ImportanceOfBeingErnest条注释添加了“ TkAgg”。

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的说法,但是我目前看到的是一个错误,该错误可能导致绘图无法正确绘制。

以该屏幕截图为例。在快速向上滑动小节并释放鼠标键之后,该图的值通常无法正确更新。如果我在向上滑动滑杆的同时放开了鼠标按钮,只会发生接缝。

这可能是仅与Tkinter或matplotlib和Tkinter的某种组合有关的问题。我想象它没有绘制最后一个滑块值,因为Tkinter的mainloop由于我释放了鼠标而没有处于空闲状态,因为这是Tkinter中的一个事件,可能正在干扰。

enter image description here

答案 1 :(得分:0)

现有代码的一个简单解决方法是实际显示图形(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()