此问题源于某项目中一部分功能需要实时(为呈现良好的视觉效果,实际上是间隔几秒刷新)显示各变量之间的相关性。前期偷懒直接使用从服务器获取的相关度数据绘制相关性热度图并保存为本地图像文件,之后在PyQt
界面中加载图像并显示。一番操作之后,虽然实现了实时刷新显示的功能,但在软件实际使用过程中出现软件内存占用率随运行时间增加,直至最后卡死的现象。初步分析可能是显示图像时每刷新一次就加载一幅图像,从而导致的内存占用越来越大,查了PyQt5
中与QPixmap
相关的方法依然没有解决,最终下定决心重写这部分功能的相关代码,但即便使用实时绘图的方式依然没有解决内存占用逐步增加的问题,最终发现解决这一问题的关键步骤是将控件的创建等相关代码放到初始化过程中。修改完成后,软件运行时内存占用稳定,特此记录此次代码修改,主要解决以下三个问题:
(1) 定时刷新图像
使用从服务器获取的数据在画布上进行绘图,数据更新则画布同步更新,此代码段中没有实现更新数据相关代码
(2) 调整颜色条位置
使用add_axes()
添加绘图区域,绘制颜色条可精细调控其位置
(3) 解决内存占用逐步增加的问题
将控件创建等相关代码放到初始化过程中,使图像显示控件只初始化一次
代码示例效果
点击更新图像
按钮,可刷新显示图片(代码示例中没有更新数据,所以图片是一样的)

功能简化后的代码如下:
"""
数据定时更新的情况下,绘制热度图。使用按钮模拟数据更新
"""
import sys
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QHBoxLayout, QWidget
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.resize(1400, 1400)
self.setWindowTitle('PyQt5 更新绘制图像')
# 创建一个按钮,点击时更新图像
self.button = QtWidgets.QPushButton(self)
self.button.setText('更新图像')
self.button.clicked.connect(self.update_image)
# 创建一个垂直布局,将 QLabel 和按钮添加到布局中
self.layout1 = QVBoxLayout()
self.layout1.addWidget(self.button)
self.heatmap_fig = plt.figure(figsize=(6, 4), dpi=300)
self.layout2 = QHBoxLayout()
self.layout = QVBoxLayout(self) # 至少主布局要在UI界面上,最好每个子布局也在,因此继承自self
self.layout.addStretch()
self.layout.addLayout(self.layout1)
self.layout.addStretch()
self.layout.addLayout(self.layout2)
self.layout.setStretch(0, 1)
self.layout.setStretch(1, 2)
self.layout.setStretch(2, 1)
self.layout.setStretch(3, 20)
self.canvas = FigureCanvas(self.heatmap_fig)
self.layout2.addWidget(self.canvas)
def update_image(self):
# plt.cla() # 清除axes
plt.clf() # 每次调用绘图函数时先清空上一次的画布figure
# 随机生成一个新的图像
self.data = np.array([[0, 0.8, 0.5, 0.7, 0.2, 0.6, 0.3, 0.4, 0.6]])
xstick = ['sirox出口物料温度', '烘梗丝(滚筒)-热风温度', '烘梗丝(滚筒)-热风风速',
'烘梗丝(滚筒)-热风开度',
'烘梗丝(滚筒)-出料罩压力',
'烘梗丝(滚筒)-筒壁温度', '烘梗丝(滚筒)-排潮开度', '烘梗丝(滚筒)-出口物料温度',
'入口物料含水率']
ystick = ['相关度']
colormap = plt.cm.jet
plt.rcParams['font.sans-serif'] = ['SimHei']
# self.heatmap_fig = plt.figure(figsize=(6, 4), dpi=400)
plt.title('相关度分析热度图', y=-1, size=15, color='white')
ax = sns.heatmap(self.data.astype(float), linewidths=0.1, vmax=1.0, vmin=0, square=True, cmap=colormap,
linecolor='#274E91', annot=True, cbar=False)
# 设置画布背景颜色
self.heatmap_fig.patch.set_facecolor('#274E91')
### 设置x轴位置,原来x轴在中间,现将x轴望下移,靠经底部
heatmap_Box = ax.get_position()
# print(heatmap_Box.x0,heatmap_Box.y0,heatmap_Box.width,heatmap_Box.height)
ax.set_position([0.14, 0.008, 0.8, 0.8])
### heatmap 自带的颜色条不能精细调整位置,故重新绘制颜色条
### 关闭 cb.ax.set_xticklabels的警告信息
import warnings
warnings.filterwarnings("ignore")
position = ax.figure.add_axes([0.12, 0.08, 0.78, 0.085], ) # 设置颜色条位置[左,下,右,上]
cb = ax.figure.colorbar(ax.collections[0], cax=position, orientation='horizontal') # 显示colorbar
cb.ax.tick_params(labelsize=15, color='white') # 设置colorbar刻度字体大小颜色
cb.ax.set_xticklabels(labels=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0], color='white', rotation=0) # 设置colorbar标签名称
# 控制标签上下左右,坐标轴刻度的颜色
ax.tick_params(color='white', right=False, top=True, labelright=False, labeltop=True, left=True, bottom=False,
labelleft=True, labelsize=11, labelbottom=False)
# 控制刻度名称的颜色
ax.set_xticklabels(xstick, color='white', rotation=80)
ax.set_yticklabels(ystick, color='white', rotation=0)
self.canvas.draw() # 绘制图像并显示
plt.savefig('tmp.jpg')
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
参考
Python可视化matplotlib&seborn14-热图heatmap