Python3.6:使用matplotlib在tkinter中正确显示多个动画图形时出错

时间:2017-08-15 14:27:42

标签: python-3.x animation matplotlib tkinter

我正在设计一个tkinter GUI,它将显示与粒子加速器模块相对应的过程变量(温度,湿度,风扇速度等)。我在同时显示多个动画人物时遇到问题。执行下面的代码时,只能正确调用两个动画函数中的一个(注意不会抛出任何错误):

"""
tkinter GUI for module of particle accelerator

author: F. Curtis Mason
email: fcmasoniii@uchicago.edu

Feel free to use or modify this code, but please include the authorship.
"""

# tkinter and ttk are both imported. tkinter is the main GUI library that is to
# be employed in this code. ttk is a module that resides within tkinter, and
# provides many of the same widgets as tkinter, but with different styles.
import tkinter as tk
from tkinter import ttk

# random is imported for use of randomizers in graphing dummy variables. time is
# utilized in order to graph process variables against some measure of time.
import random as rn
import time as tm

# matplotlib is the graphing library employed in this code.
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use("TkAgg")

# These imports allow for the creation of animated (live) graphs via
# matplotlib, and provide for the proper display of such graphs in tk GUIs. 
import matplotlib.animation as animation
from matplotlib.figure import Figure
from matplotlib import style
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, 
NavigationToolbar2TkAgg
from matplotlib.backend_bases import key_press_handler

# The Epics library is imported, for obvious reasons.
# import epics as ep

# A pre-packaged graphing style is used for matplotlib figures.
style.use("seaborn-bright")

# Multiple figures are created; to each, a subplot is assigned.
fig1 = plt.figure()
ax11 = fig1.add_subplot(1, 1, 1)
fig2 = plt.figure()
ax12 = fig2.add_subplot(1, 1, 1)

# Names are assigned to the process variables that will be used. Variables 
are grouped according to their appearance
# on particular tabs of the GUI.
"""pv0_t = ep.PV("TEM:Pixel:Ch0:T")
pv0_rh = ep.PV("TEM:Pixel:Ch0:RH")
pv1_t = ep.PV("TEM:Pixel:Ch1:T")
pv1_rh = ep.PV("TEM:Pixel:Ch1:RH")
pv2_t = ep.PV("TEM:Pixel:Ch2:T")
pv2_rh = ep.PV("TEM:Pixel:Ch2:RH")
pv3_t = ep.PV("TEM:Pixel:Ch3:T")
pv3_rh = ep.PV("TEM:Pixel:Ch3:RH")
pv4_t = ep.PV("TEM:Pixel:Ch4:T")
pv4_rh = ep.PV("TEM:Pixel:Ch4:RH")
pv5_t = ep.PV("TEM:Pixel:Ch5:T")
pv5_rh = ep.PV("TEM:Pixel:Ch5:RH")"""

# These lists are those upon which process variable values will be appended.
# If code calls for plotting empty data sets, an error is thrown. To avoid this
# issue, each list is supplied an appropriate initial value. In case the y-var-
# iable in question is yet to be configured, a default value of "0" is
# supplied.
x_temp_0 = [0]
y_temp_0 = [0]  # change to pv0_t.get()
x_rh_0 = [0]
y_rh_0 = [0]  # change to pv0_rh.get()

x_temp_1 = [0]
y_temp_1 = [0]
x_rh_1 = [0]
y_rh_1 = [0]

x_temp_2 = [0]
y_temp_2 = [0]
x_rh_2 = [0]
y_rh_2 = [0]

x_temp_3 = [0]
y_temp_3 = [0]
x_rh_3 = [0]
y_rh_3 = [0]

x_temp_4 = [0]
y_temp_4 = [0]
x_rh_4 = [0]
y_rh_4 = [0]

x_temp_5 = [0]
y_temp_5 = [0]
x_rh_5 = [0]
y_rh_5 = [0]

def animate_temp(t):
    """ Callback function for plotting pixel temperatures.

    Its single input (i) is the frame number. As of now, x-lists operate as the
    relative timestamp since initial execution of the code. Each time
    animate_temp() is called (frequency = 1 Hz), the last value of the x-list
    in question is incremented by one and appended to that list. Additionally,
    variables associated with pixels 1-5 will host dummy values until those
    pixels are configured. New values are appended to appropriate lists, and old
    values are deleted. It is crucial to clear the plot each time it is desired
    to plot updated list versions; otherwise, old plots will remain on the figure.
"""
    x_temp_0.append(x_temp_0[-1] + 1)
    y_temp_0.append(rn.randrange(1, 51, 1))
    x_temp_1.append(x_temp_1[-1] + 1)
    y_temp_1.append(rn.randrange(1, 51, 1))
    x_temp_2.append(x_temp_2[-1] + 1)
    y_temp_2.append(rn.randrange(1, 51, 1))
    x_temp_3.append(x_temp_3[-1] + 1)
    y_temp_3.append(rn.randrange(1, 51, 1))
    x_temp_4.append(x_temp_4[-1] + 1)
    y_temp_4.append(rn.randrange(1, 51, 1))
    x_temp_5.append(x_temp_5[-1] + 1)
    y_temp_5.append(rn.randrange(1, 51, 1))

    if len(x_temp_0) > 20:
        del x_temp_0[0]
        del y_temp_0[0]
        del x_temp_1[0]
        del y_temp_1[0]
        del x_temp_2[0]
        del y_temp_2[0]
        del x_temp_3[0]
        del y_temp_3[0]
        del x_temp_4[0]
        del y_temp_4[0]
        del x_temp_5[0]
        del y_temp_5[0]

    ax11.clear()
    ax11.plot(x_temp_0, y_temp_0)
    ax11.plot(x_temp_1, y_temp_1)
    ax11.plot(x_temp_2, y_temp_2)
    ax11.plot(x_temp_3, y_temp_3)
    ax11.plot(x_temp_4, y_temp_4)
    ax11.plot(x_temp_5, y_temp_5)
    plt.ylim(ymin = 0, ymax = 50)
    plt.title("TEM:Pixel:Ch[]:T")
    plt.xlabel("Seconds (s) Since Initiation")
    plt.ylabel("Temperature (Degrees Celcius)")
    plt.grid(True)

def animate_rh(i):
    """ Callback function for plotting pixel humidity values.

    This function is the analogue of "animate_temp" for rh values.
    """
    x_rh_0.append(x_rh_0[-1] + 1)
    y_rh_0.append(rn.randrange(1, 51, 1))
    x_rh_1.append(x_rh_1[-1] + 1)
    y_rh_1.append(rn.randrange(1, 51, 1))
    x_rh_2.append(x_rh_2[-1] + 1)
    y_rh_2.append(rn.randrange(1, 51, 1))
    x_rh_3.append(x_rh_3[-1] + 1)
    y_rh_3.append(rn.randrange(1, 51, 1))
    x_rh_4.append(x_rh_4[-1] + 1)
    y_rh_4.append(rn.randrange(1, 51, 1))
    x_rh_5.append(x_rh_5[-1] + 1)
    y_rh_5.append(rn.randrange(1, 51, 1))

    if len(x_rh_0) > 20:
        del x_rh_0[0]
        del y_rh_0[0]
        del x_rh_1[0]
        del y_rh_1[0]
        del x_rh_2[0]
        del y_rh_2[0]
        del x_rh_3[0]
        del y_rh_3[0]
        del x_rh_4[0]
        del y_rh_4[0]
        del x_rh_5[0]
        del y_rh_5[0]

    ax12.clear()
    ax12.plot(x_rh_0, y_rh_0)
    ax12.plot(x_rh_1, y_rh_1)
    ax12.plot(x_rh_2, y_rh_2)
    ax12.plot(x_rh_3, y_rh_3)
    ax12.plot(x_rh_4, y_rh_4)
    ax12.plot(x_rh_5, y_rh_5)
    plt.ylim(ymin = 0, ymax = 100)
    plt.title("TEM:Pixel:Ch[]:RH")
    plt.xlabel("Seconds (s) Since Initiation")
    plt.ylabel("Relative Humidity (%)")
    plt.grid(True)

class Manager():
    """ Creates a tkinter GUI that monitors the module and pixels.

    This class displays data pertinent to the upkeep of the module
    and its pixels. Temperatures of various locations of the
    hardware are (often) graphed, as are relative humidities.
    Fan speeds, electrical properties, and electrical channel
    control are also displayed/made accessible.
    """

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Event Manager")
        self.create_notebook()

    def create_notebook(self):

        self.root["padx"] = 5
        self.root["pady"] = 5

        notebook = ttk.Notebook(self.root)
        frame01 = ttk.Frame(notebook)
        frame02 = ttk.Frame(notebook)
        notebook.add(frame01, text = "Pixel Health")
        notebook.add(frame02, text = "Tab 2")
        notebook.grid(row = 0, column = 0)

        frame1 = ttk.Frame(frame01)
        frame1.grid(row = 0, column = 0)
        canvas11 = FigureCanvasTkAgg(fig1, frame1)
        canvas11.show()
        canvas11.get_tk_widget().grid(row = 0, column = 0)
        canvas12 = FigureCanvasTkAgg(fig2, frame1)
        canvas12.show()
        canvas12.get_tk_widget().grid(row = 0, column = 1)

        quit_button = ttk.Button(self.root, text = "Quit", command = self.root.destroy)
        quit_button.grid(row = 4, column = 3)

program = Manager()
ani_1 = animation.FuncAnimation(fig1, animate_temp, interval = 1000)
ani_2 = animation.FuncAnimation(fig2, animate_rh, interval = 1000)
program.root.mainloop()

但是,我知道如果我将每个图形作为单个图形的子图并将两个动画回调组合成一个图形,则两个图形将根据需要同时更新。这通常不是最佳的,因为它会降低我在第一个选项卡上组织小部件的一般自由度。也就是说,如果我要使两个图形成为同一图形的子图,那么它们必然会在父网格上占据相同的正方形(而我希望每个图形占据网格的单独方格;这将使相应组织的工作,例如,除了其他小部件之外,此页面上只显示三个图表。)

请注意,Epics库对应的部分代码目前已被注释掉。这是因为我一直在无法访问Epics或任何相关网络的平台上构建和测试代码。

我为评论部分代码的程度和方式道歉;我被指示评论,好像读者一般是tkinter和python的新手。另外,如果所采用的评论方式不寻常或者使代码难以阅读,我会道歉。

提前谢谢你, 柯蒂斯M。

1 个答案:

答案 0 :(得分:0)

删除canvas11.show()canvas12.show()

(如果你一次显示一个数字,之后很难操纵。)

然后,您可以修改您的动画功能以实际在轴上工作,它们应该是。例如。 plt.ylim()将设置当前活动轴的限制,这是在这种情况下创建的最后一个轴。而是直接使用轴来操纵其属性:

ax11.set_ylim(ymin = 0, ymax = 50)
ax11.set_title("TEM:Pixel:Ch[]:T")
ax11.set_xlabel("Seconds (s) Since Initiation")
ax11.set_ylabel("Temperature (Degrees Celcius)")
ax11.grid(True)

一般来说,代码中有很多冗余。以下是它的修改版本,还展示了如何更有效地制作动画。

import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
import sys
if sys.version_info >= (3, 0):
    import tkinter as tk
    from tkinter import ttk
else:
    import Tkinter as tk
    import ttk

import random as rn
import matplotlib.animation as animation
from matplotlib import style
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

style.use("seaborn-bright")

fig1, ax11 = plt.subplots()
ax11.set_ylim(ymin = 0, ymax = 50)
ax11.set_title("TEM:Pixel:Ch[]:T")
ax11.set_xlabel("Seconds (s) Since Initiation")
ax11.set_ylabel("Temperature (Degrees Celcius)")
ax11.grid(True)

fig2, ax12 = plt.subplots()
ax12.set_ylim(ymin = 0, ymax = 100)
ax12.set_title("TEM:Pixel:Ch[]:RH")
ax12.set_xlabel("Seconds (s) Since Initiation")
ax12.set_ylabel("Relative Humidity (%)")
ax12.grid(True)

tempx = [[0],[0],[0],[0],[0]]
tempy = [[0],[0],[0],[0],[0]]
rhx =   [[0],[0],[0],[0],[0]]
rhy =   [[0],[0],[0],[0],[0]]

lines_temp = []
for i in range(5):
    lines_temp.append(ax11.plot([],[])[0])

lines_rh = []
for i in range(5):
    lines_rh.append(ax12.plot([],[])[0])


def animate(t, x, y, lines, ax):
    for i in range(5):
        x[i].append(x[i][-1] + 1)
        y[i].append(rn.randrange(1, 51, 1))

    if len(x[0]) > 20:
        for i in range(5):
            del x[i][0]
            del y[i][0]

    for i in range(5):
        lines[i].set_data(x[i],y[i])
        ax.relim()
        ax.autoscale_view()


class Manager():

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Event Manager")
        self.create_notebook()

    def create_notebook(self):

        self.root["padx"] = 5
        self.root["pady"] = 5

        notebook = ttk.Notebook(self.root)
        frame01 = ttk.Frame(notebook)
        frame02 = ttk.Frame(notebook)
        notebook.add(frame01, text = "Pixel Health")
        notebook.add(frame02, text = "Tab 2")
        notebook.grid(row = 0, column = 0)

        frame1 = ttk.Frame(frame01)
        frame1.grid(row = 0, column = 0)
        canvas11 = FigureCanvasTkAgg(fig1, frame1)
        #canvas11.show()
        canvas11.get_tk_widget().grid(row = 0, column = 0)
        canvas12 = FigureCanvasTkAgg(fig2, frame1)
        print canvas12
        #canvas12.show()
        canvas12.get_tk_widget().grid(row = 0, column = 1)

        quit_button = ttk.Button(self.root, text = "Quit", 
                                 command = self.root.destroy)
        quit_button.grid(row = 4, column = 3)

program = Manager()
ani_1 = animation.FuncAnimation(fig1, animate, interval = 1000, 
                                fargs=(tempx,tempy, lines_temp, ax11))
ani_2 = animation.FuncAnimation(fig2, animate, interval = 1000, 
                                fargs=(rhx,rhy, lines_rh, ax12))
program.root.mainloop()