I am currently trying to embed a graph I want to plot in a pyqt4 user interface I designed. As I am almost completely new to programming - I do not get how people did the embedding in the examples I found - this one (at the bottom) and that one.
It would be awesome if anybody could post a step-by-step explanation or at least a very small, very simple code only creating e.g. a graph and a button in one pyqt4 GUI.
It is not that complicated actually. Relevant Qt widgets are in matplotlib.backends.backend_qt4agg. FigureCanvasQTAgg and NavigationToolbar2QT are usually what you need. These are regular Qt widgets. You treat them as any other widget. Below is a very simple example with a Figure, Navigation and a single button that draws some random data. I've added comments to explain things.
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import random
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = Figure()
# 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)
# Just some button connected to `plot` method
self.button = QtGui.QPushButton('Plot')
self.button.clicked.connect(self.plot)
# set the layout
layout = QtGui.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 = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
Edit:
Updated to reflect comments and API changes.
NavigationToolbar2QTAgg changed with NavigationToolbar2QT
Directly import Figure instead of pyplot
Replace deprecated ax.hold(False) with ax.clear()
Below is an adaptation of previous code for using under PyQt5 and Matplotlib 2.0.
There are a number of small changes: structure of PyQt submodules, other submodule from matplotlib, deprecated method has been replaced...
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import random
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# 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)
# 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)]
# instead of ax.hold(False)
self.figure.clear()
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
# ax.hold(False) # deprecated, see above
# 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_())
For those looking for a dynamic solution to embed Matplotlib in PyQt5 (even plot data using drag and drop). In PyQt5 you need to use super on the main window class to accept the drops. The dropevent function can be used to get the filename and rest is simple:
def dropEvent(self,e):
"""
This function will enable the drop file directly on to the
main window. The file location will be stored in the self.filename
"""
if e.mimeData().hasUrls:
e.setDropAction(QtCore.Qt.CopyAction)
e.accept()
for url in e.mimeData().urls():
if op_sys == 'Darwin':
fname = str(NSURL.URLWithString_(str(url.toString())).filePathURL().path())
else:
fname = str(url.toLocalFile())
self.filename = fname
print("GOT ADDRESS:",self.filename)
self.readData()
else:
e.ignore() # just like above functions
For starters the reference complete code gives this output:
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 am currently trying to embed a graph I want to plot in a pyqt4 user interface I designed. As I am almost completely new to programming - I do not get how people did the embedding in the examples I found - this one (at the bottom) and that one.
It would be awesome if anybody could post a step-by-step explanation or at least a very small, very simple code only creating e.g. a graph and a button in one pyqt4 GUI.
It is not that complicated actually. Relevant Qt widgets are in matplotlib.backends.backend_qt4agg. FigureCanvasQTAgg and NavigationToolbar2QT are usually what you need. These are regular Qt widgets. You treat them as any other widget. Below is a very simple example with a Figure, Navigation and a single button that draws some random data. I've added comments to explain things.
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import random
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = Figure()
# 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)
# Just some button connected to `plot` method
self.button = QtGui.QPushButton('Plot')
self.button.clicked.connect(self.plot)
# set the layout
layout = QtGui.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 = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
Edit:
Updated to reflect comments and API changes.
NavigationToolbar2QTAgg changed with NavigationToolbar2QT
Directly import Figure instead of pyplot
Replace deprecated ax.hold(False) with ax.clear()
Below is an adaptation of previous code for using under PyQt5 and Matplotlib 2.0.
There are a number of small changes: structure of PyQt submodules, other submodule from matplotlib, deprecated method has been replaced...
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import random
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# 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)
# 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)]
# instead of ax.hold(False)
self.figure.clear()
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
# ax.hold(False) # deprecated, see above
# 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_())
For those looking for a dynamic solution to embed Matplotlib in PyQt5 (even plot data using drag and drop). In PyQt5 you need to use super on the main window class to accept the drops. The dropevent function can be used to get the filename and rest is simple:
def dropEvent(self,e):
"""
This function will enable the drop file directly on to the
main window. The file location will be stored in the self.filename
"""
if e.mimeData().hasUrls:
e.setDropAction(QtCore.Qt.CopyAction)
e.accept()
for url in e.mimeData().urls():
if op_sys == 'Darwin':
fname = str(NSURL.URLWithString_(str(url.toString())).filePathURL().path())
else:
fname = str(url.toLocalFile())
self.filename = fname
print("GOT ADDRESS:",self.filename)
self.readData()
else:
e.ignore() # just like above functions
For starters the reference complete code gives this output:
I have to use MatPlotlib in my project. However, I need to design QML Application in PyQt5 and as I understand matplotlib plots are Widgets. So, I need to use matplotlib plots inside QML.
My question is that Is there a way to display an interactive matplotlib plot in QML? (by interactive I mean not just a figure that has been saved as an image, ideally with the standard toolbar for zoom etc.)
In here, it asked before but it isn't answered. Can someone help me?
If you can live with widgets, it is not that much different from PyQT4. This answer has been greatly inspired by this thread.
The PyQT5-compatible code:
import sys, random
from PyQt5.QtWidgets import (
QMainWindow,
QApplication,
QWidget,
QVBoxLayout,
QPushButton,
QHBoxLayout,
)
import matplotlib
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class AppForm(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
# The main_frame keeps all widgets on itself
self.main_frame = QWidget()
# Create the interactive matplotlib figure
self.fig = Figure((10.0, 16.0), dpi=100)
# Create figure canvas that gets a reference to self.fig
# in its __init__
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111)
# Create the matplotlib navigation toolbar
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
# VBox (V for vertical) where the canvas is placed above the toolbar
plotVbox = QVBoxLayout()
plotVbox.addWidget(self.canvas)
plotVbox.addWidget(self.mpl_toolbar)
# Another VBox with a button, could eventually hold more
# vertically ordered buttons
controls = QVBoxLayout()
self.button = QPushButton("Refresh")
self.button.clicked.connect(self.refresh)
controls.addWidget(self.button)
# HBox (h for horizontal) where the
# controls VBox with buttons is on the left
# and the VBox with the plot is on the riht
hbox = QHBoxLayout()
hbox.addLayout(controls)
hbox.addLayout(plotVbox)
self.main_frame.setLayout(hbox)
self.setCentralWidget(self.main_frame)
def refresh(self):
"""
Here, the functionality of the 'Refresh' button is implemented.
Note that we do not return anything, instead we modify the state
of the AppForm instance.
"""
self.ax.clear()
x = [random.random() for i in range(10)]
y = [random.random() for i in range(10)]
self.ax.plot(x, y, "o")
self.canvas.draw()
def main():
"""
Open the main window.
"""
app = QApplication(sys.argv)
form = AppForm()
form.show()
app.exec_()
if __name__ == "__main__":
main()
How do you send variables through events? I want to be able to return the x,y coordinates of a scatterplot datapoint that was clicked. Currently the program is able to perform the click event but I cant pull out the x, y data to use later on in the program. I added a Coordinate button as an example of using the data afterwards. Thanks in advance.
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
def onclick(event):
ind = event.ind[0]
data = event.artist.get_offsets()
xdata, ydata = data[ind,:]
print ((xdata, ydata))
return (xdata,ydata)# i want to return these
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# 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)
# Just some button connected to `plot` method
self.button = QPushButton('Plot')
self.button.clicked.connect(self.plot)
self.button2 = QPushButton('Coordinate')
self.button2.clicked.connect(self.coordinate)
# set the layout
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
layout.addWidget(self.button2)
self.setLayout(layout)
self.canvas.mpl_connect('pick_event', onclick)
def coordinate(self):
print(xdata, ydata)#these variables are not being returned
def plot(self):
x = [10,20,30,40,50]
y = [100,50,150,200,75]
size = 100
# instead of ax.hold(False)
self.figure.clear()
# create an axis
self.axes = self.figure.add_subplot(111)
# discards the old graph
self.axes.scatter(x,y,s=size,color='blue', picker=1)
self.axes.patch.set_facecolor('None')
# refresh canvas
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
So I've been doing a great deal of research in to PyQt and MPL, and I'm still having some issues that I can't get past.
The first is this: How do you change the FigureCanvas background color (it's the gray part in this image). FigureCanvas does not have attributes for facecolor or backgroundcolor or anything else I can think of or find. I can change color on the axes, the plots (that's how I got the black background you can see), but not the canvas itself.
Second problem: How can I get rid of the mouse pointer that is still visible over the top of the crosshairs? Or better yet change the cursor when it enters the mpl FigureCanvas widget (and consequently back again when it leaves).
EDIT: Here is some of my code as requested. I am calling MPL figures from Qt via the following.
My main window:
from PyQt5.QtCore import pyqtSlot as Slot
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from PyQt5 import QtGui
from PyQt5 import uic
# MPL class
from pyqt5_mpl import mpl_figure
# Select Qt5 user interface.
qtCreatorFile = '<local filepath>'
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class main(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
# Inititate UI.
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Create data structures.
global a
a = a_dataclass()
# Create MPL Canvases (Layout->CreateMPLCanvas->AddWidget).
# Create class instances for each figure.
a.im1 = mpl_figure()
a.im2 = mpl_figure()
# Create Layouts and add widgets.
a_layout = QtWidgets.QHBoxLayout(self.tab1)
a_layout.addWidget(a.im1.canvas)
a_layout.addWidget(a.im2.canvas)
# Load images.
a.im1.loadimage('image file path',a.pix)
a.im2.loadimage('image file path',a.pix)
# a.pix is dimensions for extent in plot.
if __name__ == "__main__":
# QApp
app = QtWidgets.QApplication(sys.argv)
# QWidget (MainWindow)
window = main()
window.show()
sys.exit(app.exec_())
My mpl_figure class:
import matplotlib as mpl
mpl.use('Qt5Agg')
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class mpl_figure:
def __init__(self):
# Set up empty plot variables.
self.image = None
# Create a figure to plot on.
fig = plt.figure()
fig.set_facecolor('black') # This does nothing...
# Create an axis in the figure.
self.ax = fig.add_subplot(111, axisbg='black')
# Create a canvas widget for Qt to use.
self.canvas = FigureCanvas(fig)
# Create cursor widget.
self.cursor = mpl.widgets.Cursor(self.ax)
# Refresh the canvas.
self.canvas.draw()
def loadimage(self,fn,pix):
# Read in image.
self.data = plt.imread(fn)
# Calculate the extent.
self.dims = np.array([0, pix[0]*np.shape(self.data)[1],0,pix[1]*np.shape(self.data)[0]])
# Display the image.
self.image = self.ax.imshow(self.data, cmap='gray', extent=self.dims)
self.ax.set_autoscale_on(False)
# Refresh the canvas.
self.canvas.draw()
# Start Callback ID
self.cid = self.canvas.mpl_connect('button_press_event', self.onclick)
def onclick(self,event):
# Do stuff...
pass
All my attempts to try and make changes to self.canvas or fig. in terms of its styling are rendered mute and it does nothing. And as far as changing the cursor (or hiding it, it being the pointer) when you enter an mpl_widget I'm just not sure on how to do it. I remember seeing something within mpl that was to the effect of connect "event_leave_figure", but couldn't make a connection between that and PyQt <- that's the important bit. Any ideas?
Thanks for providing a MINIMAL WORKING example and for googling before asking a question. That always saves a lot of time.
So the following code produces the image shown below.
import sys
from PyQt4 import QtGui, QtCore
import matplotlib
import numpy as np
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
class ApplicationWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("application main window")
self.main_widget = QtGui.QWidget(self)
self.setCentralWidget(self.main_widget)
l = QtGui.QVBoxLayout(self.main_widget)
self.fig = Figure(figsize=(5,3), dpi=100)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
l.addWidget(self.canvas)
t = np.arange(0.0, 3.0, 0.01)
s = np.sin(2*np.pi*t)*np.exp(-t/7/np.pi)
self.ax.plot(t, s, color="#0095a2", lw=3)
#set some alpha
self.fig.patch.set_alpha(0.5)
#set color
self.fig.patch.set_facecolor('#cd0e49')
self.cursor = matplotlib.widgets.Cursor(self.ax)
#set cursor
#self.canvas.setCursor(QtGui.QCursor(QtCore.Qt.BlankCursor))
#since I find Blank cursor very confusing, I will use a better one:
self.canvas.setCursor(QtGui.QCursor( QtCore.Qt.SizeAllCursor))
qApp = QtGui.QApplication(sys.argv)
aw = ApplicationWindow()
aw.show()
sys.exit(qApp.exec_())