我正在尝试使用tkinter进行绘图,但出现此错误,并且无法追溯:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python37\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:\Python37\lib\tkinter\__init__.py", line 749, in callit
func(*args)
File "C:\Python37\lib\site-packages\matplotlib\backends\_backend_tk.py", line 346, in idle_draw
self.draw()
File "C:\Python37\lib\site-packages\matplotlib\backends\backend_tkagg.py", line 9, in draw
super(FigureCanvasTkAgg, self).draw()
File "C:\Python37\lib\site-packages\matplotlib\backends\backend_agg.py", line 402, in draw
self.figure.draw(self.renderer)
File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "C:\Python37\lib\site-packages\matplotlib\figure.py", line 1649, in draw
renderer, self, artists, self.suppressComposite)
File "C:\Python37\lib\site-packages\matplotlib\image.py", line 138, in _draw_list_compositing_images
a.draw(renderer)
File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "C:\Python37\lib\site-packages\matplotlib\axes\_base.py", line 2628, in draw
mimage._draw_list_compositing_images(renderer, self, artists)
File "C:\Python37\lib\site-packages\matplotlib\image.py", line 138, in _draw_list_compositing_images
a.draw(renderer)
File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 50, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File "C:\Python37\lib\site-packages\matplotlib\text.py", line 709, in draw
bbox, info, descent = textobj._get_layout(renderer)
File "C:\Python37\lib\site-packages\matplotlib\text.py", line 286, in _get_layout
key = self.get_prop_tup(renderer=renderer)
File "C:\Python37\lib\site-packages\matplotlib\text.py", line 871, in get_prop_tup
x, y = self.get_unitless_position()
File "C:\Python37\lib\site-packages\matplotlib\text.py", line 854, in get_unitless_position
y = float(self.convert_yunits(self._y))
File "C:\Python37\lib\site-packages\matplotlib\artist.py", line 195, in convert_yunits
return ax.yaxis.convert_units(y)
File "C:\Python37\lib\site-packages\matplotlib\axis.py", line 1530, in convert_units
ret = self.converter.convert(x, self.units, self)
File "C:\Python37\lib\site-packages\matplotlib\category.py", line 53, in convert
unit.update(values)
AttributeError: 'NoneType' object has no attribute 'update'
这是我的代码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Graphic user interface that can be used with the experiment to write values and give live graphical feedback to the user
"""
import time
from collections import deque
try:
# for Python2
import Tkinter as tk
import ttk
except ImportError:
# Python 3
import tkinter as tk
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from matplotlib.gridspec import GridSpec
from crio.communication_handler import Handler
bitfile = "../LabVIEW/FPGA Bitfiles/Live_bitfile.lvbitx"
compactRIO = "rio://169.254.47.210/RIO0"
steps = "../Examples/Step_Setter/Steps.yml"
test = "../Examples/test.yml"
config = "../Examples/YAML_Config.yml"
cm = Handler(config, test, steps, bitfile, compact_rio=None)
cm.verbose = False
cm.start()
# Setting constants
HISTORY_LEN = 200 # Number of data points to show in the plot
GRAPH_INTERVAL = 100 # milliseconds between graph updates
XLABEL = "Time (s)"
# For real time graphing
PAUSE = "Pause Graph"
UNPAUSE = "Unpause Graph"
START = "Start Graphing"
CLEAR = "Reset Graphs"
# GUI Colors
background_color = "#ffffff"
button_color = "cadet blue"
button_txt_color = "snow"
button_opts = dict( # general button settings
width=30,
font=("MS Serif", 12),
borderwidth=3.5,
relief="raised",
bg=button_color,
fg=button_txt_color)
# Container for the plots so graphing is simplified
gs = GridSpec(3, 6) # (x,y)
PLOTS = [
# (ylabel, title, YLIM_HIGH, YLIM_LOW, plot position, register)
('Phi', 'McKenna: Actual Equivalence Ratio (Phi)', 6, 0, gs[0, 0:-4],
'Actual_Phi'),
('Velocity (cm/s)', 'McKenna: Actual Mixture Velocity', 500, 0,
gs[0, 2:-2], 'Actual_Velocity'),
('Air, LPM', 'McKenna Air Flow rate (LPM)', 600, 0, gs[0, 4:], 'MFC_Air'),
('Hydrogen, LPM', 'Hydrogen Fuel Flow Rate (LPM)', 500, 0, gs[2, 0:-4],
'MFC_Fuel'),
('Air SCCM', 'Tube Air Flow Rate (SCCM)', 75, 0, gs[1, 2:-2],
'MFC_Tube_Air'),
('N₂, SCCM', 'Tube N₂ Flow Rate (SCCM)', 50, 0, gs[1, 4:], 'MFC_Tube_N2'),
('Temperature', 'Temperature Large', 1700, 0, gs[2, 4:],
'Temperature_Large'),
('Amplitude', 'Microphone Readings', 15, -15, gs[2, 2:-2], 'Mic_Voltage'),
('Pressure (PSI)', 'Pressure Reading', 500, 0, gs[1, 0:-4],
'Pressure_Reading')
]
COLUMNS = 2 # Determines entries in a row
# Fields will correspond with the Dynamic User Input entries i.e. the set points
FIELDS = (("Dynamic User Inputs", (
cm.set_point
)), )
class GUI(tk.Tk):
"""GUI created from Tkinter
Note:
The __init__ method creates the GUI window with using a
notebook tabbed style and initializes each tab.
The input fields tab is created to allow for input values
for the experiment.
The graph window tab is where the live plotting of the
results are to be displayed
"""
def __init__(self):
try:
super(tk.Tk, self).__init__(self)
except TypeError:
super().__init__()
# GUI window
self.title("Python Graphic User Interface")
# Maximizes window when exiting fullscreen mode
# self.state('zoomed') # <-- not supported in Linux
self.attributes("-fullscreen", True)
self.configure(background=background_color)
self.bind("<Escape>", self.end_fullscreen)
# Title
title = tk.Label(
self,
text="Microcombustion Project",
bg=background_color,
font=("Bodoni bold", 45))
title.pack(fill=tk.BOTH)
style = ttk.Style()
style.theme_create(
"MyStyle",
parent="alt",
settings={
"TNotebook": {
"configure": {
"tabmargins": [0, 5, 0, 0],
"background": "#ffffff"
}
},
"TNotebook.Tab": {
"configure": {
"padding": [100, 8],
"font": ("MS Serif, bold", 14)
}
}
})
style.theme_use("MyStyle")
nb = ttk.Notebook(self)
nb.pack(expand=True, fill=tk.BOTH)
# Input fields
input_fields = EntryWindow(self, background=background_color)
nb.add(input_fields, text='Values')
# Graph
graph_window = GraphWindow(self)
nb.add(graph_window, text='Graphs')
def end_fullscreen(self, event=None):
"""Toggle full screen"""
self.state = False
self.attributes("-fullscreen", False)
return "break"
# Creating labels and entries out of a tuple
class Entry:
"""Creating labels and entries out of a tuple
Note:
The Entry class is created to store/set dynamic inputs on the GUI
Args:
master: The main GUI
text: Input field names
value: Initial values
idx: Index
"""
instances = []
def __init__(self, master, text, value, idx):
self.text = text
row, column = divmod(idx, COLUMNS)
lbl = tk.Label(
master,
text=text.replace("_", " ") + ":",
bg=background_color,
font=("Bodoni bold", 15))
lbl.grid(row=row, column=column * 2, sticky='e')
self.ent = ttk.Entry(master, font="Bodoni 14", justify="right")
self.ent.insert(0, '{}'.format(value))
self.ent.grid(row=row, column=column * 2 + 1)
self.instances.append(self)
def get(self):
"""Retrieves the values
Returns: The text formatted properly and entries
"""
return self.text, self.ent.get()
class EntryWindow(tk.Frame):
"""The entry window for the GUI
Args:
master: The main GUI
**kwargs: Tkinter's Frame method takes keyword arguments
"""
def __init__(self, master=None, **kwargs):
self.frame = tk.Frame.__init__(self, master, **kwargs)
master.bind("<Return>", self.fetch_values)
# Input fields
for name, properties in FIELDS:
namelbl = tk.Label(
self,
text=name,
bg=background_color,
font=("Bodoni bold", 30),
pady=25)
namelbl.pack()
input_fields = tk.Frame(self, background=background_color)
for idx, (field, initVal) in enumerate(properties.items()):
Entry(input_fields, field, initVal, idx)
input_fields.pack()
# Buttons
self.set_button = tk.Button(
self, text='Set Values', command=self.fetch_values, **button_opts)
self.set_button.pack()
def fetch_values(self, event=None):
"""Fetching input entries
Args:
event: None
Returns: Sets the TCP data to the inputted parameters
"""
# Dictionary that will act like a JSON file
data = dict(elem.get() for elem in Entry.instances)
print('Values have been set.')
class GraphWindow(tk.Frame):
"""Frame containing graphed data"""
def __init__(self, master=None, **kwargs):
"""
Args:
master: The main GUI
**kwargs: Tkinter's Frame method takes keyword arguments
"""
self.frame = tk.Frame.__init__(self, master, **kwargs)
self.start_time = time.time()
self.running = False
self.ani = None
# Graphing plots from predefined PLOTS
self.fig = plt.Figure()
self.plots = [] # a container for the plots
for ylabel, title, ylim_high, ylim_low, position, register in PLOTS:
ax = self.fig.add_subplot(position)
line, = ax.plot([], [], lw=2)
ax.set_ylim(ylim_low, ylim_high)
ax.set_xlabel(XLABEL)
ax.set_ylabel(ylabel)
ax.set_title(title)
bbox_props = dict(
boxstyle="round,pad=0.3", fc="cyan", ec="b", lw=2)
t = ax.text(
0, 0, "0", ha="right", va="center",
bbox=bbox_props) # Indicator
self.plots.append((ax, line, register, t))
# Adjusting subplot placement
self.fig.subplots_adjust(
bottom=.075, left=.05, right=.99, top=.9, hspace=.35, wspace=0.35)
self.canvas = FigureCanvasTkAgg(self.fig, master=self)
self.canvas.draw()
self.canvas.get_tk_widget().pack(expand=True, fill=tk.BOTH)
# Start, pause, unpause button that is called from on_click()
btn_frame = tk.Frame(self)
self.btn = tk.Button(
btn_frame, text=START, command=self.on_click, **button_opts)
self.btn.pack(side=tk.LEFT)
btn = tk.Button(btn_frame, text=CLEAR, command=cm.clear(), **button_opts)
btn.pack(side=tk.LEFT)
btn_frame.place(relx=0.5, rely=0, anchor='n')
def on_click(self):
"""How buttons in the GraphWindow respond to being pressed"""
if self.ani is None:
return self.start() # animation is not running; start it
if self.running:
self.ani.event_source.stop # animation is running; pause it
self.btn.config(text=UNPAUSE)
else:
self.ani.event_source.start() # animation is paused; unpause it
self.btn.config(text=PAUSE)
self.running = not self.running
def start(self):
"""Start graphing"""
self.ani = animation.FuncAnimation(self.fig, self.update_graph, interval=GRAPH_INTERVAL)
self.running = True
self.btn.config(text=PAUSE)
self.ani._start()
def update_graph(self, i):
"""Function to constantly plot new data"""
# If statement to check that data is being read
if cm.set_point:
dt = 10
if self.start_time < dt:
second_time = '00:00:00'
else:
hours, rem = divmod(time.time() - self.start_time - dt, 3600)
minutes, seconds = divmod(rem, 60)
second_time = '{:0>2}:{:0>2}:{:02.0f}'.format(
int(hours), int(minutes), seconds)
"""
Run time and data needs to be set from cm
"""
x_lim = second_time, max(cm.fpga.run_time)
last_x = cm.fpga.run_time[-1]
for ax, line, register, text in self.plots:
line.set_data(cm.fpga.run_time, cm.fpga.result[register])
ax.set_xlim(*x_lim) # rescale X
last_y = cm.fpga.run_time[-1]
text.set_text(str(last_y))
text.set_position((last_x, last_y))
class Plotting:
"""Plotting the data"""
def __init__(self):
self.output_data = {} # initial data value
self.xdata = deque([], maxlen=HISTORY_LEN)
self.ydata = {} # container for the ydata
self.indicators = [] # container for the indicator functions
for ylabel, title, ylim_high, ylim_low, position, register in PLOTS:
self.ydata[register] = deque([], maxlen=HISTORY_LEN)
def clear(self):
"""Clears the data read"""
self.xdata.clear()
for ydata in self.ydata.values():
ydata.clear()
self.start_time = time.time()
def run_gui():
root = GUI()
root.iconbitmap(r'../docs/lsu_tiger.ico')
root.mainloop()
我只是调用run_gui函数来获取AttributeError: 'NoneType' object has no attribute 'update'
,但我不确定是什么导致了错误,或者如何追溯到错误。我已经坚持了很长时间了,真的可以使用一些帮助