目标:
我想在pyQt4 GUI窗口中嵌入一个Matplotlib图。情节必须及时更新。
问题:
窗口冻结,直到绘图完成。我希望情节能够实时更新。
上下文:
我们有一些数据算法正在处理某些数据,我希望该图显示数据集如何受算法影响。算法完成迭代约0.5秒 - 每次迭代都必须更新图。
测试代码:
该算法被test()
取代,后者将随机点绘制100次。下面的代码说明了问题:
import sys
from PlotGUI import *
import threading
from random import randint
import time
class GUIForm(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self,parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.startSim)
self.cPlot = None # custom plotter
self.instantiatePlot()
def instantiatePlot(self):
self.cPlot = CustomPlotter(self.ui.widget.canvas)
self.cPlot.prepareFigure()
def startSim(self):
self.cPlot.clear();
draw_thread = threading.Thread(target=self.cPlot.test())
draw_thread.start()
class CustomPlotter():
def __init__(self, canvas):
print 'constructor'
self.canvas = canvas
def prepareFigure(self):
ax = self.canvas.ax
ax.set_ylim([-1,101])
#ax.set_xlim([dt[0],dt[1]])
ax.set_ylim([-1, 10])
self.canvas.draw()
def clear(self):
self.canvas.ax.clear()
def test(self):
canvas = self.canvas
ax = canvas.ax
for x in range(0,100):
y = randint(0,9)
ax.plot(x, y, 'ro')
print x
canvas.draw()
time.sleep(1)
#canvas.show()
#canvas.update()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = GUIForm()
myapp.show()
sys.exit(app.exec_())
提前致谢。这是针对某些原型设计的,因此我对所有提供快速解决方案的选项/替代方案持开放态度。
答案 0 :(得分:1)
尝试实例化新线程时出错:
draw_thread = threading.Thread(target=self.cPlot.test())
这将立即在当前线程中执行测试方法,然后将结果(None
)作为target
传递。你可能想做的是:
draw_thread = threading.Thread(target=self.cPlot.test)
Thread(target=None)
只创建一个不执行任何操作的线程,只是立即退出,因此是有效的,并且不会产生任何表明此问题的异常。
因为test()
方法是在GUI线程中启动的,所以GUI被阻塞,直到方法返回。
答案 1 :(得分:1)
PySide / Qt无法从另一个线程更新小部件(QPixmap /显示项)。您不能直接在另一个线程中调用GUI项目的绘图。
请参阅matplotlib.animation.FuncAnimation。 http://matplotlib.org/api/animation_api.html
import numpy as np
# ========== Matplotlib [PySide] ==========
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
import matplotlib.animation as mplanimation
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class InteractivePlotWidget(FigureCanvas):
def __init__(self):
super().__init__(Figure(tight_layout=True))
self.axes = self.figure.add_subplot(111)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.name = ""
self.data = np.zeros(shape=(100, 2))
# end __init__
def plot_data(self, interval=0):
data = np.array([(i, np.sin(i)) for i in range(interval)])
try:
self.axes.lines[0].set_data(data[:,0], data[:,1])
except IndexError:
self.axes.plot(data, label=self.name) # Lots of overhead. Do once.
self.axes.relim()
self.axes.autoscale_view(True, True, True)
return self.axes.lines # animation handles draw
# manually trigger draw
# self.draw_idle()
# self.flush_events()
# end plot_data
# end class InteractivePlotWidget
if __name__ == "__main__":
QtGui.QApplication([])
w = InteractivePlotWidget()
w.show()
# Create and start the animation (timer)
anim = mplanimation.FuncAnimation(w.figure, w.plot_data, interval=0)
anim._start()
sys.exit(QtGui.qApp.exec_())
答案 2 :(得分:0)
来自matplotlib图库的embedding_in_qt4.py示例应该足够了,对吗?
# ...
class MyMplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
self.compute_initial_figure()
#
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
# ...
class MyDynamicMplCanvas(MyMplCanvas):
"""A canvas that updates itself every second with a new plot."""
def __init__(self, *args, **kwargs):
MyMplCanvas.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_figure)
timer.start(1000)
def compute_initial_figure(self):
self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'r')
def update_figure(self):
# Build a list of 4 random integers between 0 and 10 (both inclusive)
l = [random.randint(0, 10) for i in range(4)]
self.axes.plot([0, 1, 2, 3], l, 'r')
self.draw()
# ...