Python 使用PyQt和matplotlib快速重绘
我正在尝试创建带有实时数据可视化的GUI应用程序 我提出的解决方案是使用著名的局部重画技巧:Python 使用PyQt和matplotlib快速重绘,python,matplotlib,pyqt,blit,Python,Matplotlib,Pyqt,Blit,我正在尝试创建带有实时数据可视化的GUI应用程序 我提出的解决方案是使用著名的局部重画技巧: fig, axes = plt.subplots(nrows=2, ncols=1) axes[0].set_{labels, limits} axes[1].set_{labels, limits} fig.show() fig.canvas.draw() N = 2 graph_0 = axes[0].scatter([] * N, [] * N, s=[np.pi*5**2] * N,
fig, axes = plt.subplots(nrows=2, ncols=1)
axes[0].set_{labels, limits}
axes[1].set_{labels, limits}
fig.show()
fig.canvas.draw()
N = 2
graph_0 = axes[0].scatter([] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
graph_1 = axes[1].scatter([] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
bg_0 = fig.canvas.copy_from_bbox(axes[0].bbox)
bg_1 = fig.canvas.copy_from_bbox(axes[1].bbox)
while True:
# Acquire new data
tmp = [1, 2] # *For example*
# Clear plots
fig.canvas.restore_region(bg_0)
fig.canvas.restore_region(bg_1)
# Set data to be visualized
graph_0.set_offsets([(i+1, d) for i, d in enumerate(tmp)])
graph_1.set_offsets([(i+1, d) for i, d in enumerate(tmp)])
# Redraw
axes[0].draw_artist(graph_0)
axes[1].draw_artist(graph_1)
fig.canvas.blit(axes[0].bbox)
fig.canvas.blit(axes[1].bbox)
这个解决方案很好,一切正常
之后,我尝试实现相同的方法,但使用PyQt后端(添加一些控制元素)
代码如下:
import sys
from PyQt4 import QtGui
from PyQt4.uic import loadUiType
import numpy as np
import matplotlib
import matplotlib.style
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import (
FigureCanvasQTAgg as FigureCanvas)
Ui_MainWindow, QMainWindow = loadUiType('dialog.ui')
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, ):
super(Main, self).__init__()
self.setupUi(self)
self.layout_mpl = QtGui.QVBoxLayout()
self.widget_mpl.setLayout(self.layout_mpl)
# Assign callbacks
self.button.clicked.connect(self.update_figure)
self.prepare_figure()
def prepare_figure(self):
self.fig, self.axes = plt.subplots(nrows=2, ncols=1)
self.canvas = FigureCanvas(self.fig)
self.layout_mpl.addWidget(self.canvas)
# self.canvas.draw()
self.axes[0].set_{labels, limits}
self.axes[1].set_{labels, limits}
self.graph = [None] * 2
N = 5
self.graph[0] = self.axes[0].scatter(
[] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
self.graph[1] = self.axes[1].scatter(
[] * N, [] * N, s=[np.pi*5**2] * N, animated=True)
self.canvas.draw()
self.bg = self.canvas.copy_from_bbox(self.fig.bbox)
# self.bg = [None] * 2
# self.bg[0] = self.canvas.copy_from_bbox(self.fig.axes[0].bbox)
# self.bg[1] = self.canvas.copy_from_bbox(self.fig.axes[1].bbox)
def update_figure(self):
# self.canvas.restore_region(self.bg[0])
# self.canvas.restore_region(self.bg[1])
self.canvas.restore_region(self.bg)
# Set data to be visualized
self.graph[0].set_offsets([(i+1, d) for i, d in
enumerate(np.random.rand(5)*100+100)])
self.graph[1].set_offsets([(i+1, d) for i, d in
enumerate(np.random.rand(5)*100+100)])
# Redraw
self.axes[0].draw_artist(self.graph[0])
self.axes[1].draw_artist(self.graph[1])
self.canvas.blit(self.axes[0].bbox)
self.canvas.blit(self.axes[1].bbox)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
您可能会看到我在注释行下使用的不同调用组合
因此,我希望每次点击按钮
都会更新绘图。它实际上是有效的,除了一个阻塞问题:
a) 申请刚刚开始
b) 按钮已单击一次
c) 将焦点切换到另一个窗口并返回(此图是带有偏移的图,但点放置在旧坐标中)
有人能给我指路吗
提前谢谢
使用的版本:Python 3.4.3、matplotlib 1.4.3、PyQt 4.10.4。解决方案是不断检查和更新轴。bbox大小:
问题在于,在完全重新调整背景大小之前,您会在某个时间点抓取背景,以便最终显示在屏幕上。你需要做的是
- 使用
让Qt决定何时实际触发完全重画draw_idle
- 使用mpl事件堆栈在绘制之后获取背景
def prepare_figure(self):
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.axes.set_xlim((0, 6))
self.axes.set_xlabel('seq.index')
self.axes.set_ylim((0, 100))
self.axes.set_ylabel('observation')
N = 5
self.graph = self.axes.scatter(
[] * N, [] * N, s=[np.pi*5**2] * N,
color='seagreen', animated=True)
self.canvas = FigureCanvas(self.fig)
# w = self.widget_mpl.width()
# h = self.widget_mpl.height()
# self.canvas.setFixedSize(w, h)
self.layout_mpl.addWidget(self.canvas)
# hook up to mpls event handling framework for draw events
# this is emitted after the canvas has finished a full redraw
self.canvas.mpl_connect('draw_event', self._draw_event)
# ask the canvas to kindly draw it self some time in the future
# when Qt thinks it is convenient
self.canvas.draw_idle()
def _draw_event(self, evt):
# after drawing, grab the new background
self.bg = self.canvas.copy_from_bbox(self.axes.bbox)
查看
动画
模块中的blit代码,了解执行此操作所需的全套功能(还有一个重新调整大小的事件),在强制重新绘制后,您需要重新blit上一次的数据。您所做的一切看起来都是正确的。我不能100%确定您的问题来自何处,但可能导致此问题的一个问题是,当/如果调整画布大小时,您没有更新您的背景(self.bg
)。这将导致还原错误的区域,如果图形的大小已经调整。@JoeKington我的主窗口(对话框
)大小固定,但必须检查小部件
。。。问题是,当我多次单击按钮时,点仍在旧坐标中生成,而栅格、轴和标签则在带有偏移的坐标中重新绘制。这就是为什么我认为这很可能是一个matplotlib
问题。我很困惑,这实际上在1.5中没有解决。这种方法不是比old_animation
示例中的方法更复杂吗?在您的案例中,什么作为重新绘制的触发器?这确保您仅在正确地重新绘制新背景后才能获取新背景。在这种情况下,完全绘制由Qt中的重新绘制逻辑触发(例如,图形大小调整)。