There are a few parts to this problem, so let me summarize. I'm trying to be able to create an animation in a PySide window, and then later destroy and reanimate it based on new information. I've created a sample script to demonstrate this, but it's a bit long, so here's an outline:
Create a QtGui main window using PySide
Create a widget in the window for drawing the animation
Animate the widget using FuncAnimation
On a button press:
Delete the widget and animation
Recreate the widget using different parameters for plotting
Reanimate
Everything works until the button press, and I get the following stack trace:
Traceback (most recent call last):
File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_qt4.py", line 366, in idle_draw
self.draw()
File "/usr/lib/pymodules/python2.7/matplotlib/backends/backend_qt4agg.py", line 149, in draw
self.update()
RuntimeError: Internal C++ object (MplWidget) already deleted.
How can I get rid of the current animation so that I can redraw a separate one? Here's the code.
from matplotlib import pyplot as p
from mpl_toolkits.mplot3d import Axes3D # #UnusedImport
from PySide import QtGui, QtCore
import matplotlib
import matplotlib.animation as animation
import sys
# specify the use of PySide
matplotlib.rcParams['backend.qt4'] = "PySide"
# import the figure canvas for interfacing with the backend
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
from math import pi, cos, sin
class Ui_MainWindow(object):
def setupUi(self, MainWindow, direction, maxRadius):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 500)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton = QtGui.QPushButton(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum,
QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.pushButton.sizePolicy().hasHeightForWidth())
self.pushButton.setSizePolicy(sizePolicy)
self.pushButton.setMaximumSize(QtCore.QSize(150, 16777215))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText("Change Direction")
self.horizontalLayout.addWidget(self.pushButton)
self.frame = QtGui.QFrame(self.centralwidget)
self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame.setFrameShadow(QtGui.QFrame.Raised)
self.frame.setObjectName("frame")
self.gridLayout = QtGui.QGridLayout(self.frame)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout.addWidget(self.frame)
# ------
self.mplWidget = MplWidget(self.frame, direction, maxRadius)
# ------
MainWindow.setCentralWidget(self.centralwidget)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.direction = 'up'
self.maxRadius = 0.3
self.ui.setupUi(self, self.direction, self.maxRadius)
self.ui.pushButton.clicked.connect(self.changeStuff)
self.animation = self.ui.mplWidget.animate()
def changeStuff(self):
del self.animation
self.ui.mplWidget.deleteLater()
dirs = {'up' :'down', 'down':'up'}
rads = {0.3:1, 1:0.3}
self.direction = dirs[self.direction]
self.maxRadius = rads[self.maxRadius]
self.ui.mplWidget = MplWidget(self.ui.frame, self.direction,
self.maxRadius)
self.animation = self.ui.mplWidget.animate()
class MplWidget(FigureCanvas):
def __init__(self, parent=None, direction='up', maxRadius=0.3):
self.figure = Figure()
super(MplWidget, self).__init__(self.figure)
self.setParent(parent)
self.axes = self.figure.add_subplot(111, projection='3d')
self.axes.set_xlabel("x label")
self.axes.set_ylabel("y label")
self.axes.set_zlabel("z label")
self.axes.set_xlim3d([-1, 1])
self.axes.set_ylim3d([-1, 1])
self.axes.set_zlim3d([-1, 1])
self.axes.set_aspect('equal')
if direction == 'up':
self.c = 1
elif direction == 'down':
self.c = -1
else:
self.c = 1
self.maxRadius = maxRadius
self.frames = 50
self.plot_handle = self.func_plot(self.frames)
def func_plot(self, z):
z /= float(self.frames) * self.c
theta = np.arange(0, 2 * pi + pi / 50, pi / 50)
xdata = self.maxRadius * z * np.array([cos(q) for q in theta])
ydata = self.maxRadius * z * np.array([sin(q) for q in theta])
zdata = z * np.ones(np.shape(xdata))
if not hasattr(self, 'plot_handle'):
plot_handle = self.axes.plot(xdata, ydata, zdata)[0]
else:
plot_handle = self.plot_handle
plot_handle.set_data(xdata, ydata)
plot_handle.set_3d_properties(zdata)
return plot_handle
def animate(self):
return animation.FuncAnimation(
fig=self.figure, func=self.func_plot, frames=self.frames,
interval=1000.0 / self.frames, blit=False)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Version information:
matplotlib - 1.2.1
PySide - 1.1.2
Python - 2.7.4
Edit:
I followed the matplotlib Installation Instructions to do a source build from git. I now have version 1.4.x installed. However, the original problem persists.
This is related to a bug in matplotlib qtbackend that tears things down in the wrong order (see here). Adding
self.ui.mplWidget.close_event()
in ChangeStuff takes care of the exception you have, but the newly created canvas isn't shown.
On the other hand, I don't understand why any canvas is shown at all as the widget is never added to a layout. If you tweak the set up a bit and explicitly add/remove the widget to the gridLayout it does what you want:
from mpl_toolkits.mplot3d import Axes3D # #UnusedImport
from PySide import QtGui, QtCore
import matplotlib
import matplotlib.animation as animation
import sys
# specify the use of PySide
matplotlib.rcParams['backend.qt4'] = "PySide"
# import the figure canvas for interfacing with the backend
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg \
as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
from math import pi, cos, sin
class Ui_MainWindow(object):
def setupUi(self, MainWindow, direction, maxRadius):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 500)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton = QtGui.QPushButton(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Maximum,
QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.pushButton.sizePolicy().hasHeightForWidth())
self.pushButton.setSizePolicy(sizePolicy)
self.pushButton.setMaximumSize(QtCore.QSize(150, 16777215))
self.pushButton.setObjectName("pushButton")
self.pushButton.setText("Change Direction")
self.horizontalLayout.addWidget(self.pushButton)
self.frame = QtGui.QFrame(self.centralwidget)
self.frame.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame.setFrameShadow(QtGui.QFrame.Raised)
self.frame.setObjectName("frame")
self.gridLayout = QtGui.QGridLayout(self.frame)
self.gridLayout.setObjectName("gridLayout")
self.horizontalLayout.addWidget(self.frame)
# ------
self.mplWidget = MplWidget(None, direction, maxRadius)
self.gridLayout.addWidget(self.mplWidget)
# ------
MainWindow.setCentralWidget(self.centralwidget)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_MainWindow()
self.direction = 'up'
self.maxRadius = 0.3
self.ui.setupUi(self, self.direction, self.maxRadius)
self.ui.pushButton.clicked.connect(self.changeStuff)
self.animation = self.ui.mplWidget.animate()
def changeStuff(self):
self.ui.mplWidget.close_event() # mpl clean up
self.ui.mplWidget.deleteLater() # QT cleanup
self.ui.gridLayout.removeWidget(self.ui.mplWidget)
dirs = {'up': 'down', 'down': 'up'}
rads = {0.3: 1, 1: 0.3}
self.direction = dirs[self.direction]
self.maxRadius = rads[self.maxRadius]
self.ui.mplWidget = MplWidget(self.ui.frame, self.direction,
self.maxRadius)
self.ui.gridLayout.addWidget(self.ui.mplWidget)
self.animation = self.ui.mplWidget.animate()
print self.ui.frame.children()
print 'finished change stuff'
class MplWidget(FigureCanvas):
def __init__(self, parent=None, direction='up', maxRadius=0.3):
self.figure = Figure()
super(MplWidget, self).__init__(self.figure)
self.setParent(parent)
self.axes = self.figure.add_subplot(111, projection='3d')
self.axes.set_xlabel("x label")
self.axes.set_ylabel("y label")
self.axes.set_zlabel("z label")
self.axes.set_xlim3d([-1, 1])
self.axes.set_ylim3d([-1, 1])
self.axes.set_zlim3d([-1, 1])
self.axes.set_aspect('equal')
if direction == 'up':
self.c = 1
elif direction == 'down':
self.c = -1
else:
self.c = 1
self.maxRadius = maxRadius
self.frames = 50
self.plot_handle = self.func_plot(self.frames)
def func_plot(self, z):
z /= float(self.frames) * self.c
theta = np.arange(0, 2 * pi + pi / 50, pi / 50)
xdata = self.maxRadius * z * np.array([cos(q) for q in theta])
ydata = self.maxRadius * z * np.array([sin(q) for q in theta])
zdata = z * np.ones(np.shape(xdata))
if not hasattr(self, 'plot_handle'):
plot_handle = self.axes.plot(xdata, ydata, zdata)[0]
else:
plot_handle = self.plot_handle
plot_handle.set_data(xdata, ydata)
plot_handle.set_3d_properties(zdata)
return plot_handle
def animate(self):
return animation.FuncAnimation(
fig=self.figure, func=self.func_plot, frames=self.frames,
interval=1000.0 / self.frames, blit=False)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
Related
I am trying to create a gui with several live plot EEG/ECG graphs (each plot on different axes).
From what I understand, I need to create multiple PlotWidgets inside a grid layout.
I have created a live plot using pyqtgraph, below however I am unsure how to merge this into the GUI:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import collections
import random
import time
import math
import numpy as np
class DynamicPlotter:
def __init__(self, sampleinterval=0.1, timewindow=10., size=(600, 350)):
# Data stuff
self.interval = int(sampleinterval * 1000)
self.bufsize = int(timewindow / sampleinterval)
self.databuffer = collections.deque([0.0] * self.bufsize, self.bufsize)
self.x = np.linspace(-timewindow, 0.0, self.bufsize)
self.y = np.zeros(self.bufsize, dtype=float)
# PyQtGraph stuff
self.app = QtGui.QApplication([])
self.plt = pg.plot(title='EEG/ECG Live Plot')
self.plt.resize(*size)
self.plt.showGrid(x=True, y=True)
#self.plt.setXRange(5,20, padding=0)
self.plt.setLabel('left', 'Amplitude', 'uVrms')
self.plt.setLabel('bottom', 'Time', 's')
self.curve = self.plt.plot(self.x, self.y, pen=(255, 0, 0))
# QTimer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateplot)
self.timer.start(self.interval)
def getdata(self):
frequency = 0.5
noise = random.normalvariate(0., 1.)
new = 10. * math.sin(time.time() * frequency * 2 * math.pi) + noise
return new
def updateplot(self):
self.databuffer.append(self.getdata())
self.y[:] = self.databuffer
self.curve.setData(self.x, self.y)
self.app.processEvents()
def run(self):
self.app.exec_()
if __name__ == '__main__':
livePlot = DynamicPlotter(sampleinterval=0.05, timewindow=5.)
livePlot.run()
Here is the basic GUI (3 plot widgets inside grid, and a few labels in mainWindow):
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(845, 727)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.labelTitle = QtWidgets.QLabel(self.centralwidget)
self.labelTitle.setGeometry(QtCore.QRect(280, 0, 291, 51))
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
font.setWeight(75)
self.labelTitle.setFont(font)
self.labelTitle.setObjectName("labelTitle")
self.labelCh1 = QtWidgets.QLabel(self.centralwidget)
self.labelCh1.setGeometry(QtCore.QRect(20, 90, 31, 51))
self.labelCh1.setObjectName("labelCh1")
self.labelCh2 = QtWidgets.QLabel(self.centralwidget)
self.labelCh2.setGeometry(QtCore.QRect(20, 180, 31, 51))
self.labelCh2.setObjectName("labelCh2")
self.labelCh3 = QtWidgets.QLabel(self.centralwidget)
self.labelCh3.setGeometry(QtCore.QRect(20, 260, 31, 51))
self.labelCh3.setObjectName("labelCh3")
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setGeometry(QtCore.QRect(70, 70, 741, 261))
self.widget.setObjectName("widget")
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.ch1PlotWidget = PlotWidget(self.widget)
self.ch1PlotWidget.setObjectName("ch1PlotWidget")
self.gridLayout.addWidget(self.ch1PlotWidget, 0, 0, 1, 1)
self.ch2PlotWidget = PlotWidget(self.widget)
self.ch2PlotWidget.setObjectName("ch2PlotWidget")
self.gridLayout.addWidget(self.ch2PlotWidget, 1, 0, 1, 1)
self.ch3PlotWidget = PlotWidget(self.widget)
self.ch3PlotWidget.setObjectName("ch3PlotWidget")
self.gridLayout.addWidget(self.ch3PlotWidget, 2, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 845, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.labelTitle.setText(_translate("MainWindow", "EEG/ECG Recording GUI"))
self.labelCh1.setText(_translate("MainWindow", "Ch 1"))
self.labelCh2.setText(_translate("MainWindow", "Ch 2"))
self.labelCh3.setText(_translate("MainWindow", "Ch 3"))
from pyqtgraph import PlotWidget
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
My question is how do I integrate these two so I can plot the live graphs in each widget?
Ideally I want to use a super class so I can simply import the unedited gui.
I have tried importing the gui.Ui_MainWindow into the class and then overwriting the self.plt to self.Ch1PlotWidget
from pyqtgraph.Qt import QtGui, QtCore, QtWidgets
import gui as gui
import sys
import pyqtgraph as pg
import collections
import random
import time
import math
import numpy as np
class MainWindow(QtWidgets.QMainWindow, gui.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.setupUi(self)
# Data stuff
self.interval = 100
self.bufsize = int(10 / self.interval)
self.databuffer = collections.deque([0.0] * self.bufsize, self.bufsize)
self.x = np.linspace(-10, 0.0, self.bufsize)
self.y = np.zeros(self.bufsize, dtype=float)
# PyQtGraph stuff
self.app = QtGui.QApplication([])
self.ch1PlotWidget = pg.plot(title='Live Plot')
self.ch1PlotWidget.resize(600, 350)
self.ch1PlotWidget.showGrid(x=True, y=True)
# self.plt.setXRange(5,20, padding=0)
self.ch1PlotWidget.setLabel('left', 'Amplitude', 'uVrms')
self.ch1PlotWidget.setLabel('bottom', 'Time', 's')
self.curve = self.ch1PlotWidget.plot(self.x, self.y, pen=(255, 0, 0))
# QTimer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateplot)
self.timer.start(self.interval)
def getdata(self):
frequency = 0.5
noise = random.normalvariate(0., 1.)
new = 10. * math.sin(time.time() * frequency * 2 * math.pi) + noise
return new
def updateplot(self):
self.databuffer.append(self.getdata())
self.y[:] = self.databuffer
self.curve.setData(self.x, self.y)
self.app.processEvents()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Sorry for all the code, I am just very confused on how to implement logic to the gui.
Here is an option where you can use both classes with minimal changes.
Change the DynamicPlotter constructor to accept a PlotWidget as a argument instead of creating a new one, since they are created and added to a layout in Ui_Mainwindow. In the MainWindow class, create a DynamicPlotter object for each plot (and keep a persistent reference, in this case I added them to a list self.plots).
class DynamicPlotter:
def __init__(self, plot, sampleinterval=0.1, timewindow=10., size=(600, 350)):
# Data stuff
self.interval = int(sampleinterval * 1000)
self.bufsize = int(timewindow / sampleinterval)
self.databuffer = collections.deque([0.0] * self.bufsize, self.bufsize)
self.x = np.linspace(-timewindow, 0.0, self.bufsize)
self.y = np.zeros(self.bufsize, dtype=float)
# PyQtGraph stuff
self.plt = plot
self.plt.setTitle('EEG/ECG Live Plot')
self.plt.resize(*size)
self.plt.showGrid(x=True, y=True)
#self.plt.setXRange(5,20, padding=0)
self.plt.setLabel('left', 'Amplitude', 'uVrms')
self.plt.setLabel('bottom', 'Time', 's')
self.curve = self.plt.plot(self.x, self.y, pen=(255, 0, 0))
# QTimer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updateplot)
self.timer.start(self.interval)
def getdata(self):
frequency = 0.5
noise = random.normalvariate(0., 1.)
new = 10. * math.sin(time.time() * frequency * 2 * math.pi) + noise
return new
def updateplot(self):
self.databuffer.append(self.getdata())
self.y[:] = self.databuffer
self.curve.setData(self.x, self.y)
class MainWindow(QtWidgets.QMainWindow, gui.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent=parent)
self.setupUi(self)
self.plots = []
for plot in (self.ch1PlotWidget, self.ch2PlotWidget, self.ch3PlotWidget):
self.plots.append(
DynamicPlotter(plot, sampleinterval=0.05, timewindow=5.)
)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I have an issue with a matplotlib figure that I've embedded in a pyqt5 QScrollArea.
My issue is when I'm scrolling down, it is like only the top of the matplotlib has been updated.
For instance, in the image below, the curves below the curve number 35 are not updated properly.
This only happens with mac OS but it run well on Windows 10.
I don't find the issue in the code.
Update
To me, it looks like the matplotlib figure doesn't match with the QSrollBar. I try to explain more. When I scroll down with the Qsrollbar, the Matplotlib figure is going down but not as fast as the scrollbar. I see that with the number of curve that is increasing when I scroll down. It is like the Qscrollbar go down too fast with respect to the matplotlib figure update.
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
import time
class Viewer(QMainWindow):
def __init__(self, parent=None):
super(Viewer, self).__init__()
self.parent = parent
#######################################
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainVBOX_param_scene = QVBoxLayout()
self.mascene = plot(self)
self.paramPlotV = QVBoxLayout()
self.horizontalSliders = QScrollBar(Qt.Horizontal)
self.horizontalSliders.setFocusPolicy(Qt.StrongFocus)
self.horizontalSliders.valueChanged.connect(self.update_plot)
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(1)
self.paramPlot = QHBoxLayout()
l_gain = QLabel('Gain')
self.e_gain = QLineEdit('5')
l_win = QLabel('Window')
self.e_win = QLineEdit('10')
l_spacing = QLabel('vertical spacing')
self.e_spacing = QLineEdit('10')
l_linewidth = QLabel('linewidth')
self.e_linewidth = QLineEdit('1')
self.e_gain.returnPressed.connect(self.update_plot)
self.e_win.returnPressed.connect(self.udpate_plot_plus_slider)
self.e_spacing.returnPressed.connect(self.update_plot)
self.e_linewidth.returnPressed.connect(self.update_plot)
self.paramPlot.addWidget(l_gain)
self.paramPlot.addWidget(self.e_gain)
self.paramPlot.addWidget(l_win)
self.paramPlot.addWidget(self.e_win)
self.paramPlot.addWidget(l_spacing)
self.paramPlot.addWidget(self.e_spacing)
self.paramPlot.addWidget(l_linewidth)
self.paramPlot.addWidget(self.e_linewidth)
self.paramPlotV.addWidget(self.horizontalSliders)
self.paramPlotV.addLayout(self.paramPlot)
self.mainVBOX_param_scene.addWidget(self.mascene)
self.mainVBOX_param_scene.addLayout(self.paramPlotV)
self.centralWidget.setLayout(self.mainVBOX_param_scene)
self.Fs = 1024
self.Sigs_dict = np.random.rand(50,20*self.Fs)
self.t = np.arange(self.Sigs_dict.shape[1])/self.Fs
self.parent.processEvents()
self.update()
def updateslider(self):
self.horizontalSliders.setMinimum(0)
self.horizontalSliders.setMaximum(np.ceil(self.t[-1]/int(self.e_win.text()))-1)
self.horizontalSliders.setPageStep(1)
self.horizontalSliders.update()
def udpate_plot_plus_slider(self):
self.updateslider()
self.mascene.update()
def update_plot(self):
t0 = time.time()
self.mascene.update()
print('time old:', time.time()-t0)
def update(self):
self.updateslider()
self.mascene.modify_sigs()
self.mascene.update()
class plot(QGraphicsView):
def __init__(self, parent=None):
super(plot, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')#Figure()
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.widget.layout().setContentsMargins(0, 0, 0, 0)
self.widget.layout().setSpacing(0)
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def modify_sigs(self):
self.Sigs_dict = self.parent.Sigs_dict
self.t = self.parent.t
self.Fs= self.parent.Fs
def update(self):
win_num = self.parent.horizontalSliders.value()
self.figure.clear()
plt.figure(self.figure.number)
plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
self.axes = plt.subplot(1, 1, 1)
gain = float(self.parent.e_gain.text())
win= float(self.parent.e_win.text())
self.spacing = float(self.parent.e_spacing.text())
linewidth = float(self.parent.e_linewidth.text())
ts = int(win*(win_num) * self.Fs)
te = ts + int(win * self.Fs)
if te > len(self.t):
te=len(self.t)
for i in range(self.Sigs_dict.shape[0]):
line, = plt.plot(self.t[ts:te], gain*(self.Sigs_dict[i,ts:te]-np.mean(self.Sigs_dict[i,ts:te]))+i*self.spacing, linewidth=linewidth )
self.axes.autoscale(enable=True, axis='both', tight=True)
self.axes.set_ylim((-self.spacing,(self.Sigs_dict.shape[0]+1)*self.spacing))
self.axes.set_xlim((ts/ self.Fs, ts / self.Fs + win ))
self.axes.set_yticks(np.arange(self.Sigs_dict.shape[0]) * self.spacing)
self.axes.set_yticklabels([str(n) for n in np.arange(self.Sigs_dict.shape[0])])
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*self.spacing)
self.canvas.draw()
def main():
app = QApplication(sys.argv)
app.setStyle('Windows')
ex = Viewer(app)
ex.showMaximized()
sys.exit(app.exec())
if __name__ == '__main__':
main()
I update the version of my matplotlib module from 3.1.1 to 3.2.0rc1 and it solves the issue.
I'm drawing a graph in the matplotlib by converting to FFT with Arduino data. However, the same error does not cause a graph. I think it's because of self in update() what should I do? I don't know how to change when there is a Typeerror in my code. If you find a problem, can you tell me?
+ I solved the error. However, the graph is not printed. The code has been modified as a whole. What happen?
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import random
from PyQt5 import QtCore, QtGui, QtWidgets
import datetime
import serial
import time
import random
import numpy as np
from matplotlib import animation
from collections import deque # import a "circular" list
from threading import Thread, Lock
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1212, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.widget = matplotlibWidget()
self.widget.setObjectName("widget")
self.gridLayout.addWidget(self.widget, 0, 0, 1, 1)
self.centralwidget.setLayout(self.gridLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
#from matplotlibwidgetFile import matplotlibWidget
class matplotlibWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self,parent)
self.i = 0
self.data = []
lock = Lock()
freq = 2000 # 1/T
self.guarda = 100 # 200
r = range(0, int(freq/2+1), int(freq/self.guarda))
self.frequencia = np.fft.fftfreq(self.guarda, d=1/freq)
self.acelx = deque([], maxlen=self.guarda)
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.right_layout = QVBoxLayout()
self.right_layout.addWidget(self.canvas)
self.setLayout(self.right_layout)
self.ax.set_xlim((0,int(freq/2)))
self.ax.set_ylim((0,1000))
self.line, = self.ax.plot([],[])
self.ax.grid(True)
def data_input():
for line in arduinoData:
try:
self.i+=1
self.acelx.append(float(line))
with lock:
if self.i > len(self.acelx):
self.data = np.fft.fft(self.acelx)
except ValueError:
pass
t = Thread(target=data_input)
t.daemon = True
t.start()
S = Scope(self.line,self.frequencia, self.guarda, self.data, self.ax, self.acelx, self.i )
timer = QtCore.QTimer()
timer.timeout.connect(S.update)
timer.start(0)
class Scope(matplotlibWidget) :
def __init__(self, line, frequencia, guarda, data, ax, acelx, i) :
self.line, = line,
self.ax = ax
self.frequencia = frequencia
self.guarda = guarda
self.data = data
self.acelx = acelx
self.i = i
def update(self) :
if self.i > len(self.acelx) :
self.line.set_data(self.frequencia[:int(self.guarda/2)], abs(np.real(self.data[:int(self.guarda/2)])))
self.ax.figure.canvas.draw()
return (self.line,)
if __name__ == "__main__":
import sys
value = [0,0,0,0,0,0,0,0,0,0]
arduinoData = serial.Serial('com5', 9600)
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
You have to create instance:
s = Scope()
timer = QtCore.QTimer()
timer.timeout.connect(s.update)
or
timer = QtCore.QTimer()
timer.timeout.connect(Scope().update)
I have some data plotted which I force to scientific notation to powers of 10 (instead of exponential). Heres a snippet of the code:
import matplotlib.ticker as mticker
formatter = mticker.ScalarFormatter(useMathText=True)
formatter.set_powerlimits((-3,2))
ax.yaxis.set_major_formatter(formatter)
However, the scale factor of x10^-4 appears on the top left hand corner of the graph.
Is there a simple method to force the position of this scale factor next to the y label as I have illustrated in the diagram below?
You may set the offset to invisible, such that it does not appear in its original position.
ax.yaxis.offsetText.set_visible(False)
You may then get the offset from the formatter an update the label with it
offset = ax.yaxis.get_major_formatter().get_offset()
ax.yaxis.set_label_text("original label" + " " + offset)
such that it appears inside the label.
The following automates this using a class with a callback, such that if the offset changes, it will be updated in the label.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
class Labeloffset():
def __init__(self, ax, label="", axis="y"):
self.axis = {"y":ax.yaxis, "x":ax.xaxis}[axis]
self.label=label
ax.callbacks.connect(axis+'lim_changed', self.update)
ax.figure.canvas.draw()
self.update(None)
def update(self, lim):
fmt = self.axis.get_major_formatter()
self.axis.offsetText.set_visible(False)
self.axis.set_label_text(self.label + " "+ fmt.get_offset() )
x = np.arange(5)
y = np.exp(x)*1e-6
fig, ax = plt.subplots()
ax.plot(x,y, marker="d")
formatter = mticker.ScalarFormatter(useMathText=True)
formatter.set_powerlimits((-3,2))
ax.yaxis.set_major_formatter(formatter)
lo = Labeloffset(ax, label="my label", axis="y")
plt.show()
Minimal example for a GUI, where draw() should only be called once per plot refresh. Keeps the correct scale factor. Can also be used with locked exponent, e.g. like here.
As an example I've just added a simple button to refresh the plot, but it could just as well be any other event.
from PyQt5.Qt import *
from PyQt5 import QtWidgets, QtCore # sorry about the Qt Creator mess
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.ticker import ScalarFormatter
import numpy as np
class WidgetPlot(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QVBoxLayout())
self.canvas = PlotCanvas(self)
self.layout().addWidget(self.canvas)
class PlotCanvas(FigureCanvas):
def __init__(self, parent = None, width = 5, height = 5, dpi = 100):
self.fig = Figure(figsize = (width, height), dpi = dpi, tight_layout = True)
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
def majorFormatterInLabel(self, ax, axis, axis_label, major_formatter):
if axis == "x":
axis = ax.xaxis
if axis == "y":
axis = ax.yaxis
axis.set_major_formatter(major_formatter)
axis.offsetText.set_visible(False)
exponent = axis.get_offset_text().get_text()
axis.set_label_text(axis_label + " (" + exponent + ")")
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(674, 371)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.gridLayoutWidget.setGeometry(QtCore.QRect(50, 10, 601, 281))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.mpl_layoutBox = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.mpl_layoutBox.setContentsMargins(0, 0, 0, 0)
self.mpl_layoutBox.setObjectName("mpl_layoutBox")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(280, 300, 113, 32))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.w = WidgetPlot()
self.canvas = self.w.canvas
self.mpl_layoutBox.addWidget(self.w)
self.pushButton.clicked.connect(self.refresh)
def refresh(self):
self.canvas.ax.clear()
# Could've made it more beautiful. In any case, the cost of doing this is sub-ms.
formatter = ScalarFormatter()
formatter.set_powerlimits((-1, 1))
self.canvas.majorFormatterInLabel(self.canvas.ax, "y", "label", major_formatter = formatter)
r = np.random.choice((1e3, 1e5, 1e7, 1e9, 1e100))
self.canvas.ax.plot(r)
self.canvas.draw()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
TraceWindow = QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(TraceWindow)
TraceWindow.show()
sys.exit(app.exec_())
There are a few examples out there of how to do this, but after trying all of them I don't understand how to correctly implement it. I have a program that a button creates a widget with contents inside and assigns it to a grid layout, and it also creates a figure on a canvas. Clicking on the button again creates another widget filled with the same contents and another figure and assigns it to the layout.
One of the contents is a spin box which controls the rotation of the figure. I want each spin box to be able to control the figure that was created with it individually. Here is where I am stuck.
How do I have a general button that can create several widgets, but then on every value change of each spin box, be able to tell which widget it came from so it will rotate the correct figure? I want the widget id or name or however I can access it. Here is what I have so far - Thanks in advance!:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
import math
from scipy.optimize import fsolve
from mplwindow5 import Ui_mplMainWindow
from cU_widget import cU_Widget
class Viewer(QMainWindow, Ui_mplMainWindow):
def __init__(self, parent = None):
super(Viewer, self).__init__(parent)
self.setupUi(self)
self.count = 0
self.cU_widget = []
self.cU_rotate = []
self.lbl_cU_rotate = []
self.lbl_cU = []
self.btn_cU.clicked.connect(self.add_cU)
def add_cU(self):
self.cU_widget.append(int(self.count))
self.cU_widget[self.count] = QWidget(self.scrollAreaWidgetContents)
self.cU_widget[self.count].setMinimumSize(QSize(101, 81))
self.cU_widget[self.count].setMaximumSize(QSize(101, 81))
self.cU_widget[self.count].setObjectName("cU_widget" + str(self.count+1))
self.lbl_cU.append(int(self.count))
self.lbl_cU[self.count] = QLabel("cU " + str(self.count+1), self.cU_widget[self.count])
self.lbl_cU[self.count].setGeometry(QRect(0, 0, 101, 27))
self.lbl_cU[self.count].setObjectName("lbl_cU_" + str(self.count+1))
self.lbl_cU_rotate.append(int(self.count))
self.lbl_cU_rotate[self.count] = QLabel("R", self.cU_widget[self.count])
self.lbl_cU_rotate[self.count].setGeometry(QRect(6, 50, 20, 20))
self.lbl_cU_rotate[self.count].setObjectName("lbl_cU_rotate" + str(self.count+1))
self.cU_rotate.append(int(self.count))
self.cU_rotate[self.count] = QDoubleSpinBox(self.cU_widget[self.count])
self.cU_rotate[self.count].setGeometry(QRect(20, 40, 71, 27))
self.cU_rotate[self.count].setObjectName("cU_rotate" + str(self.count+1))
self.cU_rotate[self.count].valueChanged.connect(self.cU) # ??? What to use here
self.gridLayout.addWidget(self.cU_widget[self.count], self.count, 0)
self.cU()
def cU(self):
self.cU_rotate[self.count] = self.cU_rotate[self.count].value() # ?? What to use here
rotate = 1
tt = np.arange(0,1, 0.001)
lco_x0 = 0
lco_x1 = 4
lco_y0 = 1
lco_y1 = 3
cU_L_x0 = (lco_x0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
cU_L_x1 = (lco_x1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate))) - (lco_y1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate)))
#...
cU_L_y0 = (lco_x0 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y0 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
cU_L_y1 = (lco_x1 * math.sin(math.radians(self.cU_rotate[self.count] + rotate))) + (lco_y1 * math.cos(math.radians(self.cU_rotate[self.count] + rotate)))
#...
cU_L_ax = ( 1 * cU_L_x0)
cU_L_bx = ((-6 * cU_L_x0) +(30 * cU_L_x1))
# ...
cU_L_ay = ( 1 * cU_L_y0)
cU_L_by = ((-6 * cU_L_y0) +(30 * cU_L_y1))
#...
cU_L_xtt = (cU_L_ax * tt**2) + (cU_L_bx * tt) + 1
cU_L_ytt = (cU_L_ay * tt**2) + (cU_L_by * tt) + 1
self.mplContainer.canvas.ax.plot(cU_L_xtt, cU_L_ytt, 'r')
self.mplContainer.canvas.ax.set_ylim([-5, 5])
self.mplContainer.canvas.ax.set_xlim([0, 10])
self.mplContainer.canvas.ax.set_aspect(1)
self.mplContainer.canvas.draw()
self.count += 1
app = QApplication(sys.argv)
viewer = Viewer()
viewer.show()
sys.exit(app.exec_())
here is mplwindow5:
from PyQt4 import QtCore, QtGui
class Ui_mplMainWindow(object):
def setupUi(self, mplMainWindow):
mplMainWindow.setObjectName("mplMainWindow")
mplMainWindow.resize(1171, 826)
self.centralwidget = QtGui.QWidget(mplMainWindow)
self.centralwidget.setObjectName("centralwidget")
self.mplContainer = MplWidget(self.centralwidget)
self.mplContainer.setGeometry(QtCore.QRect(259, 20, 861, 741))
self.mplContainer.setObjectName("mplContainer")
self.inputContainer = QtGui.QWidget(self.centralwidget)
self.inputContainer.setGeometry(QtCore.QRect(10, 20, 251, 741))
self.inputContainer.setObjectName("inputContainer")
self.scrollArea = QtGui.QScrollArea(self.inputContainer)
self.scrollArea.setGeometry(QtCore.QRect(0, 160, 241, 581))
self.scrollArea.setFrameShape(QtGui.QFrame.WinPanel)
self.scrollArea.setLineWidth(1)
self.scrollArea.setMidLineWidth(10)
self.scrollArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtGui.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 226, 577))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents)
self.gridLayout = QtGui.QGridLayout()
self.gridLayout.setObjectName("formLayout")
self.gridLayout.setColumnStretch(0, 0)
self.gridLayout.setColumnStretch(2, 4)
self.scrollLayout.addLayout(self.gridLayout)
self.scrollLayout.addStretch()
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
mplMainWindow.setCentralWidget(self.centralwidget)
self.btn_cU = QtGui.QPushButton("cU", self.inputContainer)
self.btn_cU.setGeometry(QtCore.QRect(0, 0, 31, 27))
self.btn_cU.setObjectName("btn_cU")
QtCore.QMetaObject.connectSlotsByName(mplMainWindow)
from mplwidget import MplWidget
mplwidget:
from PyQt4.QtGui import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Preferred)
FigureCanvas.updateGeometry(self)
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.main_widget = QWidget(self)
self.canvas = MplCanvas()
self.ntb = NavigationToolbar(self.canvas, self.main_widget)
self.vbl = QGridLayout()
self.vbl.addWidget(self.canvas)
self.vbl.addWidget(self.ntb)
self.setLayout(self.vbl)
As it's not necessary to make extensive mathematical manipulations to solve this issue, I ignored that part. So let's assume that we want to change the slope of several lines using SpinBoxes.
An option would be to make the lines be part of a class that controls the SpinBoxes as well as the matplotlib lines. I called it LineWidget in the code below. When the button is pressed a new LineWidget instance is created and added to a scroll area, where you can manipulate the parameter. Once the parameter changes the line is updated.
Here is a full example where I also simplified the rest of the code.
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import numpy as np
from PyQt4 import QtGui , QtCore
class Viewer(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Viewer, self).__init__(parent)
self.setupUI()
self.ax = self.fig.add_subplot(111)
self.count = 0
self.container = []
self.button.clicked.connect(self.addLine)
def setupUI(self):
self.centralwidget = QtGui.QWidget(self)
self.setCentralWidget(self.centralwidget)
self.centralwidget.setLayout(QtGui.QHBoxLayout())
self.leftWidget = QtGui.QWidget(self)
self.leftWidget.setMinimumWidth(200)
self.leftWidget.setLayout(QtGui.QVBoxLayout())
self.mplWidget = QtGui.QWidget(self)
self.mplWidget.setLayout(QtGui.QVBoxLayout())
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.ntb = NavigationToolbar(self.canvas, self.mplWidget)
self.mplWidget.layout().addWidget(self.canvas)
self.mplWidget.layout().addWidget(self.ntb)
self.button = QtGui.QPushButton("Push")
self.scrollWidget = QtGui.QWidget()
self.scrollLayout = QtGui.QVBoxLayout()
self.scrollWidget.setLayout(self.scrollLayout)
self.scrollLayout.addStretch()
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
self.leftWidget.layout().addWidget(self.button)
self.leftWidget.layout().addWidget(self.scrollArea)
self.centralwidget.layout().addWidget(self.leftWidget)
self.centralwidget.layout().addWidget(self.mplWidget)
def addLine(self):
b = LineWidget(self.count, self.ax)
self.container.append(b)
self.scrollLayout.insertWidget(self.scrollLayout.count() - 1, b)
self.count += 1
class LineWidget(QtGui.QWidget):
def __init__( self, number, ax, R=0, parent=None, **kwargs):
super(LineWidget, self).__init__(parent)
self.number = number
label = QtGui.QLabel("cU " + str(self.number))
self.spin = QtGui.QDoubleSpinBox()
self.spin.setSingleStep(0.2)
self.spin.setRange(-100,100)
self.setLayout(QtGui.QHBoxLayout())
self.layout().addWidget(label)
self.layout().addWidget(self.spin)
self.R = R
self.t = np.linspace(0,1)
self.f = lambda t, R: R*t
self.ax = ax
self.line, = self.ax.plot([],[], **kwargs)
self.update()
self.spin.valueChanged.connect(self.changed)
def changed(self):
self.R = self.spin.value()
self.update()
def update(self):
self.line.set_data(self.t, self.f(self.t, self.R))
self.ax.relim()
self.ax.autoscale_view()
self.ax.figure.canvas.draw_idle()
app = QtGui.QApplication(sys.argv)
viewer = Viewer()
viewer.show()
sys.exit(app.exec_())