Matplotlib FigureCanvasQT刷新后图形部分超出屏幕如何解决?
问题描述
使用FigureCanvasQTAgg和PySide6重复绘制Matplotlib多子图时,每次刷新子图数量和布局会变化,因此选择移除所有轴后完全重绘。首次绘制图形能正常适配父窗口,但刷新后图形部分超出屏幕,调整窗口大小后又能重新适配。需要实现刷新后图形自动适配父窗口。
复现代码
import sys import matplotlib.pyplot as plt import numpy as np from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from PySide6 import QtCore, QtWidgets class MplCanvas(FigureCanvasQTAgg): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = plt.figure(figsize=(width, height), dpi=dpi) super(MplCanvas, self).__init__(self.fig) def setup(self, nrows: int, ncols: int) -> None: self.fig.set_size_inches(4 * ncols, 6 * nrows) for sample_idx in range(nrows * ncols): ax = self.fig.add_subplot(nrows, ncols, sample_idx + 1) rand_image = np.random.randint(100, size=(100, 200)) im = ax.imshow(rand_image, origin="lower") self.fig.subplots_adjust( left=0.05, right=0.95, bottom=0.05, top=0.95, wspace=0.2, hspace=0.2 ) class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.canvas = MplCanvas(self, width=5, height=4, dpi=100) self.setCentralWidget(self.canvas) self.canvas.setup(2, 2) self.canvas.draw() self.show() # Setup a timer to trigger the redraw by calling update_plot. self.timer = QtCore.QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self.update_plot) self.timer.start() def update_plot(self): for ax in self.canvas.fig.get_axes(): ax.cla() ax.remove() rows = np.random.randint(1, 3) cols = np.random.randint(1, 4) self.canvas.setup(rows, cols) self.canvas.draw() app = QtWidgets.QApplication(sys.argv) w = MainWindow() app.exec_()
解决方案
问题根源在于每次刷新时手动设置了fig.set_size_inches(),强制修改图形尺寸导致其与Qt控件实际尺寸不匹配。窗口调整时Matplotlib会自动触发控件尺寸适配逻辑,所以图形恢复正常。
解决思路是让图形尺寸自动跟随Qt控件尺寸,而非手动固定:
- 移除
setup方法中手动设置图形尺寸的代码,避免强制修改尺寸。 - 使用
tight_layout替代subplots_adjust,让Matplotlib自动优化子图布局,确保所有元素在控件范围内。 - 刷新后同步图形尺寸与控件尺寸,触发适配逻辑。
修改后的代码
import sys import matplotlib.pyplot as plt import numpy as np from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg from PySide6 import QtCore, QtWidgets class MplCanvas(FigureCanvasQTAgg): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig = plt.figure(figsize=(width, height), dpi=dpi) super(MplCanvas, self).__init__(self.fig) def setup(self, nrows: int, ncols: int) -> None: for sample_idx in range(nrows * ncols): ax = self.fig.add_subplot(nrows, ncols, sample_idx + 1) rand_image = np.random.randint(100, size=(100, 200)) im = ax.imshow(rand_image, origin="lower") # 用tight_layout自动调整布局,替代手动subplots_adjust self.fig.tight_layout(pad=1.0, w_pad=0.2, h_pad=0.2) class MainWindow(QtWidgets.QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.canvas = MplCanvas(self, width=5, height=4, dpi=100) self.setCentralWidget(self.canvas) self.canvas.setup(2, 2) self.canvas.draw() self.show() self.timer = QtCore.QTimer() self.timer.setInterval(1000) self.timer.timeout.connect(self.update_plot) self.timer.start() def update_plot(self): for ax in self.canvas.fig.get_axes(): ax.remove() # 直接移除轴,无需先清空 rows = np.random.randint(1, 3) cols = np.random.randint(1, 4) self.canvas.setup(rows, cols) # 同步图形尺寸与控件尺寸 widget_size = self.centralWidget().size() self.canvas.figure.set_size_inches(widget_size.width()/self.canvas.dpi, widget_size.height()/self.canvas.dpi) self.canvas.draw() app = QtWidgets.QApplication(sys.argv) w = MainWindow() app.exec_()
额外说明
- 移除
ax.cla()是因为已经要删除轴,清空操作多余。 tight_layout的参数可按需调整:pad控制整体边距,w_pad和h_pad控制子图间的间距。- 如果需要保留自定义布局,也可以继续用
subplots_adjust,但必须确保不手动设置图形尺寸。
内容的提问来源于stack exchange,提问作者jonas87
相关产品推荐
相关产品推荐

