how matplotlib in pyqt5 using qt designer [duplicate] - python

I want to plot a figure with embedded matplotlib in PyQt.
I am using Qt Designer for the main window, and writing python code for the signal and slots connexion part.
So my code looks like this :
import sys
from PyQt4 import QtCore, QtGui, uic
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
qtCreatorFile = "main.ui" # my Qt Designer file
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.csvbutton.clicked.connect(self.plot)
def plot(self):
filePath="/path to csv file here"
df= pd.read_csv(str(filePath),index_col='date')
df.index = pd.to_datetime(df.index, unit='s')
ax = self.figure.add_subplot(111)
ax.hold(False)
ax.plot(df, '*-')
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
My main problem is the connexion between the Qt Designer file and the python code, I couldn't set a canvas widget directly in Qt Designer and I'm still struggling to find where the error lays in my code.
Your help is very appreciated, thank you.

In order to use matplotlib in Qt Designer can not be done directly, for this we must promote a QWidget to use FigureCanvas or better a class that inherits from it as I show below, first we create a class called Canvas in file called canvas.py:
canvas.py
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
class Canvas(FigureCanvas):
def __init__(self, parent=None):
self.figure = plt.figure()
FigureCanvas.__init__(self, self.figure)
self.setParent(parent)
After creating the design through Qt Designer, we have all the elements we want, but where we want to place the argument we use the Widget element that is in Containers, and we name it canvas:
Then we promote it by right click and choose the option promoted to ...:
Obtaining what is shown in the following image, in Promoted Class Name we place Canvas as the name of the class, and in Header File we place canvas.h (in Header File the file.py file is placed, for example package.subpackage.file.h), then press Add and after Promote:
At the end we get a file structure similar to the following:
.
├── canvas.py
└── main.ui
Then we create the file main.py where we place your code with small variations:
main.py
import matplotlib
matplotlib.use('Qt4Agg')
import sys
from PyQt4 import QtCore, QtGui, uic
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
qtCreatorFile = "main.ui" # my Qt Designer file
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.csvbutton.clicked.connect(self.plot)
def plot(self):
filePath="data.csv"
df= pd.read_csv(str(filePath),index_col='date')
ax = self.canvas.figure.add_subplot(111)
ax.hold(False)
ax.plot(df, '*-')
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
In the end we get the following:
You can find the complete project here
If you want to add the NavigationToolbar you can use the following code:
...
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
...
class MyApp(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.addToolBar(NavigationToolbar(self.canvas, self))
...

Related

Matplotlib callbacks don't work when matplotlib.FigureCanvas is embedded in a PyQt5 application?

Prithee, wizened elders of StackOverflow,
I am trying to make a small GUI application using PyQt5, with matplotlib.FigureCanvas objects used as widgets to display data which I also want to make more interactive through the use of matplotlib's callback functions. However, these callbacks don't appear to work for a FigureCanvas embedded in the PyQt5 window.
I'm thinking it's likely that instantiating the Qt5 application interferes with matplotlib's event handler, but I'm not sure how to proceed. Is there a way to make the matplotlib events raise Qt5 signals? Could I have two separate threads in which both event handlers can run?
The example below is the simplest thing I could pare down which illustrates the issue.
Case A: Running in Matplotlib Window
First run as-is noting the desired behavior: the onclick function is called upon a click in the figure once it pops up. (also note that the second code block, Case B won't run after the first)
Case B: Running in PyQt5
Now, comment out the Case A code block, and run: clicking in the figure doesn't call the callback function.
import sys
import numpy as np
from PyQt5 import QtCore, QtWidgets, uic
import matplotlib
matplotlib.use('QT5Agg')
import matplotlib.pylab as plt
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
matplotlib.rcParams["toolbar"] = "toolmanager"
def onclick(event):
'''example callback function'''
print('got click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
( event.button, event.x, event.y, event.xdata, event.ydata))
class MPLWidget(QtWidgets.QWidget):
'''
Widget which should act like a matplotlib FigureCanvas, but embedded in PyQt5
'''
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
# Making Matplotlib figure object with data
fig, ax = plt.subplots()
fig.suptitle("Plot in PyQt5: click and look in console")
ax.plot(np.linspace(0,1,2))
# Attaching matplotlib callback function which doesnt seem to work
cid = fig.canvas.mpl_connect('button_press_event', onclick)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.plotWidget = FigureCanvas(fig)
self.toolbar = NavigationToolbar(self.plotWidget, self)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.plotWidget)
class TestWindow(QtWidgets.QMainWindow):
'''Window which is a stand in for the larger PyQt5 application which needs a plot embedded'''
def __init__(self):
super().__init__()
self.mplwidget = MPLWidget(self)
self.setCentralWidget(self.mplwidget)
if __name__ == '__main__':
# Case A: Using matplotlib without PyQt5,
# Note that the callback is triggered and something is printed when you click in the figure
fig, ax = plt.subplots()
fig.suptitle("Standalone matplotlib: click and look in console")
ax.plot(np.linspace(0,1,2))
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
# Case B: Running PyQt5 + Matplotlib widget
# Note that nothing is printed when you click in the figure
app = QtWidgets.QApplication(sys.argv)
window = TestWindow()
window.show()
sys.exit(app.exec_())
You don't have to use "plt" if you're going to use FigureCanvas from pyqt5 but build using Figure() as the official example shows:
import sys
import numpy as np
from PyQt5 import QtCore, QtWidgets
import matplotlib
matplotlib.use("QT5Agg")
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
matplotlib.rcParams["toolbar"] = "toolmanager"
from matplotlib.figure import Figure
def onclick(event):
"""example callback function"""
print(
"got click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f"
% (event.button, event.x, event.y, event.xdata, event.ydata)
)
class MPLWidget(QtWidgets.QWidget):
"""
Widget which should act like a matplotlib FigureCanvas, but embedded in PyQt5
"""
def __init__(self, parent=None, **kwargs):
super().__init__(parent, **kwargs)
# Making Matplotlib figure object with data
self.canvas = FigureCanvas(Figure())
ax = self.canvas.figure.add_subplot(111)
ax.set_title("Plot in PyQt5: click and look in console")
ax.plot(np.linspace(0, 1, 2))
cid = self.canvas.mpl_connect("button_press_event", onclick)
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.toolbar = NavigationToolbar(self.canvas, self)
self.layout.addWidget(self.toolbar)
self.layout.addWidget(self.canvas)
class TestWindow(QtWidgets.QMainWindow):
"""Window which is a stand in for the larger PyQt5 application which needs a plot embedded"""
def __init__(self):
super().__init__()
self.mplwidget = MPLWidget(self)
self.setCentralWidget(self.mplwidget)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = TestWindow()
window.show()
sys.exit(app.exec_())

AttributeError: 'QWheelEvent' object has no attribute 'delta'

I am making a simple GUI using pyqt5. It runs fine, but when I open it and try to use the mouse's wheel, it crashes with the following error:
AttributeError: 'QWheelEvent' object has no attribute 'delta'.
Here is the code to reproduce the problem:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
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 matplotlib.pyplot as plt
import numpy as np
class View(QGraphicsView):
def __init__(self):
super(View, self).__init__()
self.setRenderHint(QPainter.Antialiasing)
self.initScene(5)
def initScene(self,h):
self.scene = QGraphicsScene()
#self.setSceneRect(0, 100, 1400, 700) #this controls where the scene begins relative to the window
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.canvas.setGeometry(0,0,900,700)
#self.setSceneRect(0, 0, 2607, 700)
self.figure.subplots_adjust(left=0,right=1,bottom=0,top=1,wspace=0, hspace=0)
axes1 = self.figure.add_subplot(3, 1, 1)
axes2 = self.figure.add_subplot(3, 1, 2)
axes3 = self.figure.add_subplot(3, 1, 3)
axes1.yaxis.set_ticks([5,6])
axes1.set_yticklabels([5,6])
#axes.yaxis.set_offset_position('right')
axes1.yaxis.set_tick_params(color='red',labelcolor='red',direction='in',labelright = 'on',labelleft='off')
axes1.plot(np.linspace(0,10,10), np.linspace(0,10,10))
axes2.plot(np.linspace(0,10,10), np.linspace(0,10,10))
axes3.plot(np.linspace(0,10,10), np.linspace(0,10,10))
axes1.spines['bottom'].set_color('red')
axes2.spines['top'].set_color('red')
self.canvas.draw()
self.setScene(self.scene)
self.scene.addWidget(self.canvas)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.setGeometry(150, 150, 1424, 750) #the first two arguments control where the window will appear on the screen, the next
self.view = View()
self.view.setGeometry(0,0,1400,700)
self.setCentralWidget(self.view)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Other details which might be relevant: I saved the code as a .py file and I run it from the 'Anaconda Prompt' command line.
If I don't connect the mouse click event to anything, then it doesn't crash, so I can't understand why it crashes I when I use the mouse's wheel (even if not connected to anything).
You are using PyQt5 but then you import the matplotlib backend for PyQt4, so I guess this is where the error comes from.
Qt4 had a delta attribute in the class QWheelEvent but now in Qt5 this has been replaced by two different properties angleDelta and pixelDelta so that's why you get the error.
To solve it simply replace your import as follows (replace the 4 for a 5):
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
References
https://doc-snapshots.qt.io/qt5-dev/qwheelevent.html

How to embed a plot directly into a Window (python; QT;)

I wanna embed a Matplotlib plot directly into a window, QMainWindow.
It should be part of my program with a more complex GUI. ;)
The only way I found was to add the figure as widget into a QTabWidget.
See sample code below.
I lost the link to the webpage what inspired me.
Is there any way to embed the figure directly into the windows like other elements (buttons, textfield, textarea, ...)?
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QDockWidget, QVBoxLayout,QTabWidget, QWidget
from matplotlib import pyplot
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
a = QApplication(sys.argv)
w = QMainWindow()
t = QTabWidget(w)
Tab1 = QWidget()
t.addTab(Tab1, '1st Plot')
t.resize(1280, 300)
x = [1, 2, 3]
Fig1 = pyplot.Figure();
Plot = Fig1.add_subplot(111);
Plot.plot(x)
Plot.grid();
layout = QVBoxLayout();
layout.addWidget(FigureCanvasQTAgg(Fig1));
Tab1.setLayout(layout);
w.showMaximized()
sys.exit(a.exec_())
Thank you very much.
FigureCanvasQtAgg is just a QWidget like any of the other controls you mentioned. The main difference being it doesn't allow you to pass a parent in the constructor like when you write
t = QTabWidget(w)
You can achieve the same with FigureCanvasQtAgg by calling setParent
canvas = FigureCanvasQtAgg(Fig1)
canvas.setParent(w)
You can also use QMainWindow's setCentralWidget method to add the matplotlib FigureCanvas directly to your main window. However, if you want a more complex gui with other controls I don't see any real problems with your current approach.
Lastly, you shouldn't really be using pyplot when embedding matplotlib. Stick with the object oriented API. Take a look at this example.
This is what I use for PySide. FigureCanvasQTAgg is a qt widget, so you don't need the PlotWidget class. I just find it useful, because it creates the figure for me. It also makes it useful for other projects, because you don't have to deal with importing the right matplotlib objects. From the PlotWidget just call the axes for all of your plotting needs.
from PySide import QtGui
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
class PlotWidget(FigureCanvas):
'''Plotting widget that can be embedded in a PySide GUI.'''
def __init__(self, figure=None):
if figure is None:
figure = Figure(tight_layout=True)
super().__init__(figure)
self.axes = self.figure.add_subplot(111)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
# end class
if __name__ == "__main__":
import sys
a = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
w.show()
p = PlotWidget()
p.axes.plot([])
nav = NavigationToolbar(p, w)
w.addToolBar(nav)
w.setCentralWidget(p)
# or
# container = QtGui.QWidget()
# layout = QtGui.QHBoxLayout()
# container.setLayout(layout)
# w.setCentralWidget(container)
# layout.addWidget(p)
sys.exit(a.exec_())

matplotlib and pyqt4 transparent background [duplicate]

I am working with the matplotlib library and PyQt5 with Python 3.6. I add a figure in a window I create, and I wish to set transparent the background of this figure because I add an image to the background of the window. But, the figure is not really transparent, it duplicates the background image of the window.
For example, someone deals with the same problem two years ago :
matplotlib and pyqt4 transparent background
Here is a working example (with a background which is black but the figure is not black) :
import sys, os
from PyQt5.QtCore import Qt
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import matplotlib
matplotlib.use('Qt5Agg') # Make sure that we are using QT5
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
class SecondWindow(QWidget):
def __init__(self, parent=None):
super(SecondWindow, self).__init__(parent)
self.setupUi(self)
def setupUi(self, Form):
# WINDOW SETTINGS
Form.setWindowTitle('Hello')
self.p = QPalette()
self.pixmap = QPixmap(os.getcwd() + "/logo.png").scaled(self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
self.p.setBrush(QPalette.Background, QBrush(self.pixmap))
self.setPalette(self.p)
# CREATE FIGURE AND SETTINGS
self.figure = plt.figure()
self.figure.patch.set_facecolor('None')
self.figure.patch.set_alpha(0)
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
# WINDOW LAYOUT (with H1 and H2)
self.setLayout(QVBoxLayout())
self.layout().addWidget(self.canvas,1)
self.layout().setContentsMargins(50, 50, 50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = SecondWindow()
form.show()
sys.exit(app.exec_())
I search for answer during long hours but didn't find a solution yet. Thanks for any help you can bring !
Operating System: Windows 7 Pro
Matplotlib Version: 2.0.2 (installed via Anaconda, conda install matplotlib --channel conda-forge)
Python Version: Python 3.6
Anaconda 3
The problem occurs because the background image is set as a palette to the widget. This causes the canvas to inherit the palette and hence the canvas will also have the image as background, somehow overlaying the widget's background.
A solution would be to set the background of the canvas transparent. An easy way to do so are style sheets.
self.canvas.setStyleSheet("background-color:transparent;")
Note that this is not the same as setting the patches' facecolor to none. The figure has a background, which is controlled inside matplotlib, but the canvas, being a PyQt object also has a background.
Complete example:
import sys, os
from PyQt4.QtCore import Qt
from PyQt4.QtGui import *
import matplotlib
matplotlib.use('Qt4Agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
plt.rcParams['xtick.color'] ="w"
plt.rcParams['ytick.color'] ="w"
plt.rcParams['font.size'] = 14
class SecondWindow(QWidget):
def __init__(self, parent=None):
super(SecondWindow, self).__init__(parent)
# CREATE FIGURE AND SETTINGS
self.figure = plt.figure()
self.figure.patch.set_facecolor("None")
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.patch.set_alpha(0.5)
###### Make the background of the canvas transparent
self.canvas.setStyleSheet("background-color:transparent;")
self.p = QPalette()
self.p.setBrush(QPalette.Background, QBrush(QPixmap("house.png")))
self.setPalette(self.p)
self.setLayout(QVBoxLayout())
self.layout().addWidget(self.canvas,1)
self.layout().setContentsMargins(50, 50, 50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
form = SecondWindow()
form.show()
sys.exit(app.exec_())
which might then look like

Creating Matplotlib Toolbar in Python Traits Editor

I'm working on a QT4 Traits-GUI in Python using Matplotlib for image display, and so far I have this editor modified in an attempt to include a toolbar.
from traitsui.qt4.editor import Editor
from pyface.qt import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
class _MPLFigureEditor(Editor):
"""
Building custom figure editor for displaying matplotlib figures with a qt4-based Traits interface
"""
scrollable = True
def init(self, parent):
self.control = self._create_canvas(parent)
self.set_tooltip()
def update_editor(self):
pass
def _create_canvas(self, parent):
mpl_canvas = FigureCanvas(self.value) #self.value is the figure linked to the editor factory
mpl_toolbar = NavigationToolbar(mpl_canvas,???)
layout = QtGui.QVBoxLayout()
layout.addWidget(mpl_toolbar)
layout.addWidget(mpl_canvas)
return layout
Is this the correct place to create the toolbar? What would be second argument to NavigationToolbar in this case? I'm trying to follow the examples I've found on adding toolbars as QWidgets to figures, but these all create a main_frame within a QMainWindow-based class, not a Traits Editor. Thanks in advance.
Found the answer. Since parent is a QGridLayout itself, we can add the toolbar widget directly to the layout in row 2, column 1 (to place it below the canvas, which I believe is added as a widget in the view-editor creation).
from traitsui.qt4.editor import Editor
from pyface.qt import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
class _MPLFigureEditor(Editor):
"""
Building custom figure editor for displaying matplotlib figures with a qt4-based Traits interface
"""
scrollable = True
def init(self, parent):
self.control = self._create_canvas(parent)
mpl_toolbar = NavigationToolbar(self.control,None)
parent.addWidget(mpl_toolbar,2,1)
self.set_tooltip()
return
def update_editor(self):
pass
def _create_canvas(self, parent):
""" Create the MPL canvas. """
mpl_canvas = FigureCanvas(self.value)
return mpl_canvas
Image zoom/pan does seem to run a bit slow, but that could be another issue altogether.

Categories

Resources