一、pyqt5的UI中嵌入matplotlib的方法
1、导入模块
导入模块比较简单,首先声明使用pyqt5,通过FigureCanvasQTAgg创建画布,可以将画布的图像显示到UI,相当于pyqt5的一个控件,后面的绘图就建立在这个画布上,然后把这个画布当中pyqt5的控件添加到pyqt5的UI上,其次要导入matplotlib.figure的Figure ,这里要注意的是matplotlib.figure中的Figure,不是matplotlib.pyplot模块中的Figure,要区分清楚。
import matplotlib matplotlib.use("Qt5Agg") # 声明使用pyqt5 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布 import matplotlib.pyplot as plt # matplotlib.figure 模块提供了顶层的Artist(图中的所有可见元素都是Artist的子类),它包含了所有的plot元素 from matplotlib.figure import Figure
2、创建pyqt5画布,并简单设置样式
创建一个画布类,继承上面导入的FigureCanvasQTAgg,通过Figure 创建画布,并且作为参数传递给父类FigureCanvasQTAgg(这里是关键一步!没有这一步后面一切都是白费,不会添加成功!),最后一步添加绘图区self.axes
class MyMatplotlibFigure(FigureCanvasQTAgg): """ 创建一个画布类,并把画布放到FigureCanvasQTAgg """ def __init__(self, width=10, heigh=10, dpi=100): plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色 plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色 self.width = width self.heigh = heigh self.dpi = dpi self.figs = Figure(figsize=(self.width, self.heigh), dpi=self.dpi) super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像 self.axes = self.figs.add_subplot(111)
3、填上创建pyqt5画布挖的坑
上面自定义的画布类MyMatplotlibFigure写的时候不会提示错误,但是当你绘图的时候会傻眼了,因为没有报错但是闪退了!!!然后逐个把可疑的类和方法try… except … print(er),希望python能告诉你原因,抱歉!最终结果是什么都没有得到!使用debug单步调试慢慢分析,累死累活的一步一步看到最后添加画布到pyqt5时,跳到一个模块backend_qt5.py文件的第500行:if self.height() < 0 or self.width() < 0:从debug的变量分析中看到“(<class ‘TypeError'>, TypeError("‘int' object is not callable"), <traceback object at 0x000001C3E0397F08>)这是什么鬼?
其实这是一个很简单的错误,但是不小心犯了排查起来很麻烦!!!错误的原因就是有些程序员在自定义类内接收外部传参时经常把传递的参数转换为全局变量,比如这个例子中初始化__init__方法接收的三个参数 width、 heigh、 dpi,顺手写了个
self.width = width self.heigh = heigh self.dpi = dpi
然后接着调用!问题就在这里了,其实不管你后面有没有调用,都会闪退!!!!!为什么呢?
因为FigureCanvasQTAgg父类中导入了backend_qt5.py模块,而backend_qt5模块内部也使用了相同的变量名self.width和self.heigh,所以呢,在这里用上面的写法就造成了对父类变量的覆盖。正确的写法:
class MyMatplotlibFigure(FigureCanvasQTAgg): """ 创建一个画布类,并把画布放到FigureCanvasQTAgg """ def __init__(self, width=10, heigh=10, dpi=100): plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色 plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色 # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure # 这里还要注意,width, heigh可以直接调用参数,不能用self.width、self.heigh作为变量获取,因为self.width、self.heigh 在模块中已经FigureCanvasQTAgg模块中使用,这里定义会造成覆盖 self.figs = Figure(figsize=(width, heigh), dpi=dpi) super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像(就是在画板上放置画布) self.axes = self.figs.add_subplot(111) # 添加绘图区
这里直接使用传参字符就可以了,这几个参数后面用不到了,如果你能用到就随便改个名字,比如self.w = width self.h = heigh
4、把画布添加到pyqt5的UI中
这里就比较简单了,创建一个简单的窗口,添加label,实例化上面创建的自定义画布类,用变量self.canvas接收实例,这就相当于pyqt5的控件了,在label上创建布局,布局中添加画布self.canvas
如果仅仅是把matplotlib的图像添加到Ui中,plotcos这个绘图方法放哪里都行,也可以在上面的自定义类中添加这个方法,只是最后绘图的两行简单修改即可:
class MainDialogImgBW_(QtWidgets.QMainWindow): """ 创建UI主窗口,使用画板类绘图。 """ def __init__(self): super(MainDialogImgBW_, self).__init__() self.setWindowTitle("显示matplotlib") self.setObjectName("widget") self.resize(800, 600) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100) self.plotcos() self.hboxlayout = QtWidgets.QHBoxLayout(self.label) self.hboxlayout.addWidget(self.canvas) def plotcos(self): # plt.clf() t = np.arange(0.0, 5.0, 0.01) s = np.cos(2 * np.pi * t) self.canvas.aexs.plot(t, s) self.canvas.figs.suptitle("sin") # 设置标题
二、实时刷新matplotlib图像的坑
实时刷新图像如果通过网络查询,基本千篇一律的结果都是先clean清除之前的图像、重新plot、加上重绘draw(),从其它帖子找了最具代表性的三部曲步骤如下:
self.axes.cla() self.axes.plot(x, y, 'o',xx,yy) self.draw()
这三部曲是没错,但是只是他们说的有点简单了,有些细节需要注意,否则一样不会刷新或者报错闪退。需要注意的坑是draw(),因为他们帖子上写的简单,实在不知道他们的self有几个意思,一般情况下这么写是错的。**cla()清空了绘图区,plot()重新绘制了图像,这两个都是对绘图区的操作,但是要draw()要重绘的是画布层不是绘图区。而且仅仅draw()是不够的,还要flush_events()否则可能在刷新画布过程中中途偶然闪退。**完整的正确代码如下(上面的第4条例子大的写法是绘图方法在UI类内,下面的例子用另外一种写法,在画布类中创建绘图方法):
class MyMatplotlibFigure(FigureCanvasQTAgg): """ 创建一个画布类,并把画布放到FigureCanvasQTAgg """ def __init__(self, width=10, heigh=10, dpi=100): plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色 plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色 # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure self.figs = Figure(figsize=(width, heigh), dpi=dpi) super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, self.axes = self.figs.add_subplot(111) # 添加绘图区 def mat_plot_drow_axes(self, t, s): """ 用清除画布刷新的方法绘图 :return: """ self.axes.cla() # 清除绘图区 self.axes.spines['top'].set_visible(False) # 顶边界不可见 self.axes.spines['right'].set_visible(False) # 右边界不可见 # 设置左、下边界在(0,0)处相交 # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0 self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0 self.axes.plot(t, s, 'o-r', linewidth=0.5) self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas self.figs.canvas.flush_events() # 画布刷新self.figs.canvas class MainDialogImgBW(QtWidgets.QMainWindow): """ 创建UI主窗口,使用画板类绘图。 """ def __init__(self): super(MainDialogImgBW_, self).__init__() self.setWindowTitle("显示matplotlib") self.setObjectName("widget") self.resize(800, 600) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100) self.plotcos() self.hboxlayout = QtWidgets.QHBoxLayout(self.label) self.hboxlayout.addWidget(self.canvas) def plotcos(self): # plt.clf() t = np.arange(0.0, 5.0, 0.01) s = np.cos(2 * np.pi * t) self.canvas.mat_plot_drow_axes(t, s) self.canvas.figs.suptitle("sin") # 设置标题 if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) main = MainDialogImgBW() main.show() sys.exit(app.exec_())
**需要注意的地方就是画布重绘的写法,既不是self.draw()也不是self.axes.draw()或self.figs.draw(),而是self.figs.canvas.draw()和self.figs.canvas.flush_events(),这里比较坑的是写这两句代码时没有智能提醒!(我用的pycharm,也有可能是被这个坑了,不知道其他IDE是否会提醒)**这里还要提醒的是只有self.figs.canvas.draw()没有self.figs.canvas.flush_events()时也会重绘,但是有可能在运行过程中闪退,所以还是加上比较安全。
三、实时更新matplotlib的另一种方法
上面是使用axes.cla()的方式刷新图表,但是你有可能会遇到,你要展示的下一个图形于前面一次图表完全不同,包括画布背景色等都不同,那么用上面的axes.cla()只清理绘图区就不够了,需要用得到清理画布figure.clf(),这个地方你要看清楚,清理绘图区方法是cla(),而清理画布是clf()一字之差。另外一个需要注意的地方就是,清理画布后之前画布上的绘图区axes也清理了,需要重新添加axes,完整代码如下:
class MyMatplotlibFigure(FigureCanvasQTAgg): """ 创建一个画布类,并把画布放到FigureCanvasQTAgg """ def __init__(self, width=10, heigh=10, dpi=100): # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure self.figs = Figure(figsize=(width, heigh), dpi=dpi) super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, def mat_plot_drow(self, t, s): """ 用清除画布刷新的方法绘图 :return: """ self.figs.clf() # 清理画布,这里是clf() self.axes = self.figs.add_subplot(111) # 清理画布后必须重新添加绘图区 self.axes.patch.set_facecolor("#01386a") # 设置ax区域背景颜色 self.axes.patch.set_alpha(0.5) # 设置ax区域背景颜色透明度 self.figs.patch.set_facecolor('#01386a') # 设置绘图区域颜色 self.axes.spines['bottom'].set_color('r') # 设置下边界颜色 self.axes.spines['top'].set_visible(False) # 顶边界不可见 self.axes.spines['right'].set_visible(False) # 右边界不可见 # 设置左、下边界在(0,0)处相交 # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0 self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0 self.axes.plot(t, s, 'o-r', linewidth=0.5) self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas self.figs.canvas.flush_events() # 画布刷新self.figs.canvas class MainDialogImgBW(QtWidgets.QMainWindow): """ 创建UI主窗口,使用画板类绘图。 """ def __init__(self): super(MainDialogImgBW_, self).__init__() self.setWindowTitle("显示matplotlib") self.setObjectName("widget") self.resize(800, 600) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100) self.plotcos() self.hboxlayout = QtWidgets.QHBoxLayout(self.label) self.hboxlayout.addWidget(self.canvas) def plotcos(self): # plt.clf() t = np.arange(0.0, 5.0, 0.01) s = np.cos(2 * np.pi * t) self.canvas.mat_plot_drow(t, s) self.canvas.figs.suptitle("sin") # 设置标题 if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) main = MainDialogImgBW() main.show() sys.exit(app.exec_())
四、animation的方式刷新matplotlib
如果你在UI中的刷新频率非常高,比如股票或期货的tick数据,上面的刷新方式就有点不够用了,虽然也能刷新但是又可能会闪屏的情况很不舒服,高频刷新还是用animation方式刷新。
使用animation 需要增加导入matplotlib.animation模块的FuncAnimation方法,全部导入模块如下:
import matplotlib matplotlib.use("Qt5Agg") # 声明使用pyqt5 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布 import matplotlib.pyplot as plt from matplotlib.figure import Figure from matplotlib.animation import FuncAnimation
FuncAnimation的基础使用这里就不赘述了,论坛内搜索就可以找到,只提一个可能存在坑的地方,数据更新函数是嵌套在绘图方法plot_tick内的(可不要以为这是格式错误)。这里直接上代码:
class MyMatPlotAnimation(FigureCanvasQTAgg): """ 创建一个画板类,并把画布放到容器(画板上)FigureCanvasQTAgg,再创建一个画图区 """ def __init__(self, width=10, heigh=10, dpi=100): # 创建一个Figure,该Figure为matplotlib下的Figure,不是matplotlib.pyplot下面的Figure self.figs = Figure(figsize=(width, heigh), dpi=dpi) super(MyMatPlotAnimation, self).__init__(self.figs) self.figs.patch.set_facecolor('#01386a') # 设置绘图区域颜色 self.axes = self.figs.add_subplot(111) def set_mat_func(self, t, s): """ 初始化设置函数 """ self.t = t self.s = s self.axes.cla() self.axes.patch.set_facecolor("#01386a") # 设置ax区域背景颜色 self.axes.patch.set_alpha(0.5) # 设置ax区域背景颜色透明度 # self.axes.spines['top'].set_color('#01386a') self.axes.spines['top'].set_visible(False) # 顶边界不可见 self.axes.spines['right'].set_visible(False) # 右边界不可见 self.axes.xaxis.set_ticks_position('bottom') # 设置ticks(刻度)的位置为下方 self.axes.yaxis.set_ticks_position('left') # 设置ticks(刻度) 的位置为左侧 # 设置左、下边界在(0,0)处相交 # self.axes.spines['bottom'].set_position(('data', 0)) # 设置x轴线再Y轴0位置 self.axes.spines['left'].set_position(('data', 0)) # 设置y轴在x轴0位置 self.plot_line, = self.axes.plot([], [], 'r-', linewidth=1) # 注意‘,'不可省略 def plot_tick(self): plot_line = self.plot_line plot_axes = self.axes t = self.t def upgrade(i): # 注意这里是plot_tick方法内的嵌套函数 x_data = [] # 这里注意如果是使用全局变量self定义,可能会导致绘图首位相联 y_data = [] for i in range(len(t)): x_data.append(i) y_data.append(self.s[i]) plot_axes.plot(x_data, y_data, 'r-', linewidth=1) return plot_line, # 这里也是注意‘,'不可省略,否则会报错 ani = FuncAnimation(self.figs, upgrade, blit=True, repeat=False) self.figs.canvas.draw() # 重绘还是必须要的 class MainDialogImgBW(QtWidgets.QMainWindow): def __init__(self): super(MainDialogImgBW_, self).__init__() self.setWindowTitle("显示matplotlib") self.setObjectName("widget") self.resize(800, 600) self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) self.canvas = MyMatPlotAnimation(width=5, heigh=4, dpi=100) self.plotcos() self.hboxlayout = QtWidgets.QHBoxLayout(self.label) self.hboxlayout.addWidget(self.canvas) def plotcos(self): t = np.arange(0.0, 5.0, 0.01) s = np.cos(2 * np.pi * t) self.canvas.set_mat_func(t, s) self.canvas.plot_tick() if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) main = MainDialogImgBW() main.show() sys.exit(app.exec_())
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]