I'm designing a GUI with PyQt where I need to display a matplotlib/pylab window when I click on a button that makes the plot of the data from a function I've created. It's like a runtime used in Matlab. I want to keep the matplotlib/pylab window as my window everytime I press that button.
Here is a basic example that will plot three different samples using a QThread:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import random
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
from PyQt4 import QtGui, QtCore
class MatplotlibWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MatplotlibWidget, self).__init__(parent)
self.figure = Figure()
self.canvas = FigureCanvasQTAgg(self.figure)
self.axis = self.figure.add_subplot(111)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.canvas)
class ThreadSample(QtCore.QThread):
newSample = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super(ThreadSample, self).__init__(parent)
def run(self):
randomSample = random.sample(range(0, 10), 10)
self.newSample.emit(randomSample)
class MyWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButtonPlot = QtGui.QPushButton(self)
self.pushButtonPlot.setText("Plot")
self.pushButtonPlot.clicked.connect(self.on_pushButtonPlot_clicked)
self.matplotlibWidget = MatplotlibWidget(self)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.pushButtonPlot)
self.layoutVertical.addWidget(self.matplotlibWidget)
self.threadSample = ThreadSample(self)
self.threadSample.newSample.connect(self.on_threadSample_newSample)
self.threadSample.finished.connect(self.on_threadSample_finished)
#QtCore.pyqtSlot()
def on_pushButtonPlot_clicked(self):
self.samples = 0
self.matplotlibWidget.axis.clear()
self.threadSample.start()
#QtCore.pyqtSlot(list)
def on_threadSample_newSample(self, sample):
self.matplotlibWidget.axis.plot(sample)
self.matplotlibWidget.canvas.draw()
#QtCore.pyqtSlot()
def on_threadSample_finished(self):
self.samples += 1
if self.samples <= 2:
self.threadSample.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.resize(666, 333)
main.show()
sys.exit(app.exec_())
This is code from user1006989 (best answer) adapted to PyQt5, hopefully it will be useful to someone:
Here is a basic example that will plot three different samples using a QThread:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import random
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
from PyQt5 import QtCore #conda install pyqt
from PyQt5 import QtWidgets
class MatplotlibWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MatplotlibWidget, self).__init__(parent)
self.figure = Figure()
self.canvas = FigureCanvasQTAgg(self.figure)
self.axis = self.figure.add_subplot(111)
self.layoutVertical = QtWidgets.QVBoxLayout(self)#QVBoxLayout
self.layoutVertical.addWidget(self.canvas)
class ThreadSample(QtCore.QThread):
newSample = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super(ThreadSample, self).__init__(parent)
def run(self):
randomSample = random.sample(range(0, 10), 10)
self.newSample.emit(randomSample)
class MyWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.pushButtonPlot = QtWidgets.QPushButton(self)
self.pushButtonPlot.setText("Plot")
self.pushButtonPlot.clicked.connect(self.on_pushButtonPlot_clicked)
self.matplotlibWidget = MatplotlibWidget(self)
self.layoutVertical = QtWidgets.QVBoxLayout(self)
self.layoutVertical.addWidget(self.pushButtonPlot)
self.layoutVertical.addWidget(self.matplotlibWidget)
self.threadSample = ThreadSample(self)
self.threadSample.newSample.connect(self.on_threadSample_newSample)
self.threadSample.finished.connect(self.on_threadSample_finished)
#QtCore.pyqtSlot()
def on_pushButtonPlot_clicked(self):
self.samples = 0
self.matplotlibWidget.axis.clear()
self.threadSample.start()
#QtCore.pyqtSlot(list)
def on_threadSample_newSample(self, sample):
self.matplotlibWidget.axis.plot(sample)
self.matplotlibWidget.canvas.draw()
#QtCore.pyqtSlot()
def on_threadSample_finished(self):
self.samples += 1
if self.samples <= 2:
self.threadSample.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.resize(666, 333)
main.show()
sys.exit(app.exec_())
Eli Bendersky has written a code example that uses matplotlib within PyQt: http://eli.thegreenplace.net/2009/01/20/matplotlib-with-pyqt-guis/
Integrating Matplotlib with PyQt takes a little work. Here's an example:
http://sourceforge.net/mailarchive/message.php?msg_id=29086544
However, there are a few plotting libraries designed specifically around PyQt:
pyqwt (edit: no longer maintained)
guiqwt
pyqtgraph
If I understand you correctly you have an application with a GUI and you want to plot a graph in a separate window than the GUI uses. pyqtgraph can do this nicely.
first type pip install pyqtgraph in the command prompt to install pyqtgraph
then
import pyqtgraph as pg
pg.setConfigOption('background', 'w') # sets background to white
pg.setConfigOption('foreground', 'k') # sets axis color to black
pw = pg.plot(x, y, pen='g') # 1st plot (green)
pw.plot(x2, y2, pen='b') # 2nd plot in same figure (blue)
pw.setLabel('bottom', 'x-label') # x-label
pw.setLabel('left', 'y-label') # y-label
more info here:
http://www.pyqtgraph.org/documentation/how_to_use.html
Related
I'm trying to make an app where the matplotlib's tab widget is embedded inside another widget
import os
import matplotlib
import matplotlib.pyplot as pp
import numpy as np
from PySide2 import QtGui
from PySide2.QtCore import QTimer
from PySide2.QtWidgets import *
from layout import Ui_Form
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import sys
graphed = False
res = 180
class VentanaPrincipal(QDialog):
def __init__(self, parent=None):
super(VentanaPrincipal, self).__init__(parent)
self.ventana = Ui_Form()
self.ventana.setupUi(self)
self.timer = QTimer()
self.timer.timeout.connect(self.mainloop)
self.timer.start(20)
self.ancho = self.size().height()
self.alto = self.size().height()
self.ventana.pshBtnCalcularOrbita.clicked.connect(self.truer)
self.ventana.pshBtnCalcularOrbita.clicked.connect(self.graph)
self.oldsize = self.size()
self.figure = Figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
self.button = QPushButton('Plot')
self.button.clicked.connect(self.plot)
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def truer(self):
global graphed
graphed = True
def graph(self):
self.ventana.graph()
def resizeEvent(self,event):
self.oldsize = event.size()
def mainloop(self):
self.ancho = self.size().width()
self.alto = self.size().height()
if self.size() != self.oldsize:
if graphed:
self.graph()
if __name__ == '__main__':
app = QApplication(sys.argv)
aplicacion = VentanaPrincipal()
aplicacion.show()
sys.exit(app.exec_())
Gives me:
TypeError: arguments did not match any overloaded call:
QToolBar(str, parent: QWidget = None): argument 1 has unexpected type 'VentanaPrincipal'
QToolBar(parent: QWidget = None): argument 1 has unexpected type 'VentanaPrincipal'
The Ui_Form is a .py file compiled from the original .ui file from QtDesigner
I've seen an identical example onto another question, the example i grabbed onto was:
import sys
from PySide2.QtWidgets import QDialog,QApplication,QPushButton, QVBoxLayout
from PySide2 import QtGui
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import random
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = Figure()
print(self)
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
print(type(self))
# Just some button connected to `plot` method
self.button = QPushButton('Plot')
self.button.clicked.connect(self.plot)
# set the layout
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def plot(self):
''' plot some random stuff '''
# random data
data = [random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.clear()
# plot data
ax.plot(data, '*-')
# refresh canvas
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
There are some prints i put inside the code to check which kind of object is accepted by the QToolbar init line inside Matplotlib's backend
I have a PyQt file that, on the click of a button, runs a function from another python file (let's call calc.py) that carries out a really advanced calculation and uses matplotlib to plot the result.
I would like to show this plot generated by calc.py in the PyQt window after the calculation is complete/the figure is generated. Although, I'm not sure the best way to carry this out given that the calculation and figure are made in another file.
pyqt.py
import PyQt5 . . .
from calc import do_calc
class Window(QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.title_label = QLabel("Label Text", self) #random label in top of window
###code here to display matplotlib plot in my PyQt app####
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
calc.py
import matplotlib.pyplot as plt
def do_calc():
plt.figure()
for i in x:
... #really complex calculation here
plt.plot()
plt.draw()
I've been looking at other examples of how to display matplotlib plots in Qt but they usually orient around the calculation being done in the PyQt file or widget class which I can't really do in this instance. I can alter the calc.py file to return items or do anything else that might be helpful, but the calculations will likely need to stay in that file so it can be ran independently from the PyQt
The solution is a hack(may fail in the future) that assumes that the backend used by matplotlib in calc.py uses PyQt5, for this it is necessary to import PyQt5 first and then calc.py.
The logic is to make matplotlib not block the eventloop using plt.ion, and then search among the toplevels (windows) that have a FigureCanvas as their centralWidget.
calc.py
import matplotlib.pyplot as plt
import numpy as np
def do_calc():
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(
xlabel="time (s)",
ylabel="voltage (mV)",
title="About as simple as it gets, folks",
)
ax.grid()
plt.show()
main.py
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvas
from calc import do_calc
class Window(QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.title_label = QLabel("Label Text")
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QVBoxLayout(central_widget)
lay.addWidget(self.title_label)
plt.ion()
do_calc()
for tl in QApplication.topLevelWidgets():
if isinstance(tl, QMainWindow) and isinstance(
tl.centralWidget(), FigureCanvas
):
lay.addWidget(tl)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Another better option is to get all the Figures and then the canvas and finally the window of that canvas:
class Window(QMainWindow):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.title_label = QLabel("Label Text")
central_widget = QWidget()
self.setCentralWidget(central_widget)
lay = QVBoxLayout(central_widget)
lay.addWidget(self.title_label)
plt.ion()
do_calc()
for i in plt.get_fignums():
canvas = plt.figure(i).canvas
if isinstance(canvas, QWidget):
lay.addWidget(canvas.window())
I want to generate a scatterplot (up to half a million points) and on top of that, add different statistics (e.g. Q1, median, Q3). The idea is to add/delete those statistics without replotting the scatterplot in order to speed up the process. So far I can add plots independently on the figure but I can't delete a specific plot.
When I uncheck the checkbox, I get the following error:
AttributeError: 'Graphics' object has no attribute 'vline1'
I understand that when I create the plot, I need to store/return the plot in order to call it later when I want to delete it but I don't know how to do that.
Here my current code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class Mainwindow(QMainWindow):
def __init__(self, parent=None):
super(Mainwindow, self).__init__(parent)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.gridLayout = QGridLayout(centralWidget)
self.gridLayout.addWidget(self.canvas)
self.btn_plot = QCheckBox("Plot")
self.btn_line = QCheckBox("Line")
self.gridLayout.addWidget(self.btn_plot, 1,0,1,1)
self.gridLayout.addWidget(self.btn_line, 2,0,1,1)
self.btn_plot.clicked.connect(self.btnPlot)
self.btn_line.clicked.connect(self.btnLine)
def btnPlot(self):
self.checked = self.btn_plot.isChecked()
self.Graphics = Graphics('plot', self.checked, self.axes)
def btnLine(self):
self.checked = self.btn_line.isChecked()
self.Graphics = Graphics('line', self.checked, self.axes)
class Graphics:
def __init__(self, typeGraph, checked, axes):
self.typeGraph = typeGraph
self.checked = checked
self.axes = axes
if self.typeGraph == 'plot': self.drawPlot()
if self.typeGraph == 'line': self.drawLine()
def drawPlot(self):
if self.checked == True:
self.plot = self.axes.plot([10,20,30], [5,10,2], 'o')
else:
self.plot.remove()
self.axes.figure.canvas.draw()
def drawLine(self):
if self.checked == True:
self.vline1 = self.axes.axvline(x=15, linestyle="dashed", color="#595959")
self.vline2 = self.axes.axvline(x=25, linestyle="dashed", color="#595959")
else:
self.vline1.remove()
self.vline2.remove()
self.axes.figure.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
prog = Mainwindow()
prog.show()
sys.exit(app.exec_())
Problem is be because when you click it then it creates always new Graphics (in btnPlot/btnLine) which doesn't have previous values - plot, vline1, vline2. You have to create Graphics only once and later run only drawPlot(checked), drawLine(checked) to add or remove item.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class Mainwindow(QMainWindow):
def __init__(self, parent=None):
super(Mainwindow, self).__init__(parent)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.gridLayout = QGridLayout(centralWidget)
self.gridLayout.addWidget(self.canvas)
self.btn_plot = QCheckBox("Plot")
self.btn_line = QCheckBox("Line")
self.gridLayout.addWidget(self.btn_plot, 1,0,1,1)
self.gridLayout.addWidget(self.btn_line, 2,0,1,1)
self.btn_plot.clicked.connect(self.btnPlot)
self.btn_line.clicked.connect(self.btnLine)
# create only once
self.Graphics = Graphics(self.axes)
def btnPlot(self):
# add or remove
self.Graphics.drawPlot(self.btn_plot.isChecked())
def btnLine(self):
# add or remove
self.Graphics.drawLine(self.btn_line.isChecked())
class Graphics:
def __init__(self, axes):
self.axes = axes
# create at start with default values (but frankly, now I don't need it)
self.plot = None
self.vline1 = None
self.vline2 = None
def drawPlot(self, checked):
if checked:
self.plot = self.axes.plot([10,20,30], [5,10,2], 'o')
else:
for item in self.plot:
item.remove()
self.axes.figure.canvas.draw()
def drawLine(self, checked):
if checked:
self.vline1 = self.axes.axvline(x=15, linestyle="dashed", color="#595959")
self.vline2 = self.axes.axvline(x=25, linestyle="dashed", color="#595959")
else:
self.vline1.remove()
self.vline2.remove()
self.axes.figure.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
prog = Mainwindow()
prog.show()
sys.exit(app.exec())
Here is some code I've written (and/or adapted from other sources):
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import (QGraphicsView, QGraphicsScene, QMainWindow,
QApplication, QWidget, QVBoxLayout,
QDesktopWidget)
from PyQt5.QtGui import QBrush
from PyQt5.QtCore import Qt
import sys
class View(QGraphicsView):
def __init__(self):
super(View, self).__init__()
self.initScene()
def initScene(self):
self.scene = QGraphicsScene()
self.canvas = Fig()
self.setBackgroundBrush(QBrush(Qt.red))
self.canvas.draw()
self.setScene(self.scene)
self.scene.addWidget(self.canvas)
class Fig(FigureCanvas):
def __init__(self, *args,**kwargs):
self.factor = kwargs.pop("factor", 2)
FigureCanvas.__init__(self, Figure(), *args,**kwargs)
self.plot()
def plot(self):
self.ax = self.figure.add_subplot(111)
data = np.random.rand(1000)
self.ax.plot(data, '-')
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
desktop = QDesktopWidget()
rect = desktop.availableGeometry()
self.setGeometry(rect.width()/10, rect.height()/10, rect.width()/1.2,
rect.height()/1.2)
self.view = View()
self.setCentralWidget(self.view)
app = QApplication(sys.argv)
window = Window()
window.show()
app.exec_()
It produces the following window as its output:
I would like the plot in the middle to occupy as much space as possible, so that the red background becomes invisible.
This happens if I exclude the command setting the size of the window, that is indeed what happens. However, the window is then too small - I need to make it bigger.
I have tried using self.view.setGeometry, but it doesn't seem to make a difference. I've had a look at the available modules in the documentation, but can't tell which might help.
If you want to establish that the plot is shown covering all the available space within the window I see it unnecessary to use QGraphicsView since Fig is a QWidget that can be used directly in setCentralWidget():
class Fig(FigureCanvas):
def __init__(self, *args,**kwargs):
self.factor = kwargs.pop("factor", 2)
FigureCanvas.__init__(self, Figure(), *args,**kwargs)
self.plot()
def plot(self):
self.ax = self.figure.add_subplot(111)
data = np.random.rand(1000)
self.ax.plot(data, '-')
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
desktop = QDesktopWidget()
rect = desktop.availableGeometry()
self.setGeometry(rect.width()/10, rect.height()/10, rect.width()/1.2,
rect.height()/1.2)
self.canvas = Fig()
self.canvas.draw()
self.setCentralWidget(self.canvas)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
update: Using QScrollArea
class Fig(FigureCanvas):
def __init__(self, *args,**kwargs):
self.factor = kwargs.pop("factor", 2)
FigureCanvas.__init__(self, Figure(), *args,**kwargs)
self.plot()
def plot(self):
self.ax = self.figure.add_subplot(111)
data = np.random.rand(1000)
self.ax.plot(data, '-')
def showEvent(self, event):
self.setFixedSize(self.size())
FigureCanvas.showEvent(self, event)
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
desktop = QDesktopWidget()
rect = desktop.availableGeometry()
self.setGeometry(rect.width()/10, rect.height()/10, rect.width()/1.2,
rect.height()/1.2)
self.canvas = Fig()
self.canvas.draw()
scrollArea = QScrollArea()
scrollArea.setWidgetResizable(True)
scrollArea.setWidget(self.canvas)
self.setCentralWidget(scrollArea)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have been working on a school project that involves a Qt/PyQt GUI. I am currently trying to get a specific menu to overlay the main window. I would love to add a sort of fading transition when toggled, but I can't seem to find many solid examples. There also seems to be a lack of python documentation in this area. If someone could provide me with some functional code, that would be great!
Heres what I have found so far, overlay:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
import threading
class overlay(QWidget):
def __init__(self, parent=None):
super(overlay, self).__init__(parent)
self.verticalLayout = QVBoxLayout(self)
self.button = QPushButton("Hello world!")
self.verticalLayout.addWidget(self.button)
self.percent = 0
class windowOverlay(QWidget):
def __init__(self, parent=None):
super(windowOverlay, self).__init__(parent)
self.editor = QTextEdit()
self.editor.setPlainText("OVERLAY"*100)
self.button = QPushButton("Toggle Overlay")
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.addWidget(self.editor)
self.verticalLayout.addWidget(self.button)
self.overlay = overlay(self.editor)
self.overlay.hide()
self.button.clicked.connect(lambda: self.overlay.setVisible(False) if self.overlay.isVisible() else self.overlay.setVisible(True))
def resizeEvent(self, event):
self.overlay.resize(event.size())
event.accept()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = windowOverlay()
main.show()
sys.exit(app.exec_())