I'm currently learning how can I use pyqt5 with matplotlib together so implement this code which will popup after I press a pushbutton in another window
I create another class called canvas in order to create multiple graphs in the future like bar chart and histogram
when I run this code the windows will appear and the pushButton also will appear but not the canvas that I did create
how can I show the canvas ( pie chart ) in the main window
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class window(QtWidgets.QMainWindow):
def setupUi(self,Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(900, 500)
self.pushButton = QtWidgets.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(0, 380, 141, 61))
self.pushButton.setObjectName("pushButton")
self.canvas = Canvas(self, width=8, height=4)
self.canvas.move(0,0)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "output window"))
class Canvas(FigureCanvas):
def __init__(self, parent = None, width = 5, height = 5, dpi = 100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = np.array([50, 30,40])
labels = ["Apples", "Bananas", "Melons"]
ax = self.figure.add_subplot(111)
ax.pie(x, labels=labels)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = window()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
The problem is caused because you are modifying the code generated by Qt Designer without understanding the consequences of it. The class generated by QtDesigner is not the window but a class that just populates the window. For example in your case with the window modification it is QMainWindow that has the canvas but you have created another widget called Dialog which is the one you show (if you check the QPushButton it is a child of Dialog so it is also shown). On the other hand, you only have to create a single axes.
Therefore, you should not modify the class generated by QtDesigner but rather create another class that inherits from a widget, and use the previous class to fill it.
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
import PyQt5
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
class Ui_Dialog:
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(900, 500)
self.pushButton = QtWidgets.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(0, 380, 141, 61))
self.pushButton.setObjectName("pushButton")
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "output window"))
class Canvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = np.array([50, 30, 40])
labels = ["Apples", "Bananas", "Melons"]
self.axes.pie(x, labels=labels)
self.draw()
class Dialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.canvas = Canvas(self, width=8, height=4)
self.canvas.move(0, 0)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
Related
I am willing to integrate a matplotlib figure into a GUI designed with pyqt5:
I wrote the code above:
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QMainWindow
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 Ui_MainWindow(QMainWindow):
def setupUi(self, MainWindow):
self.vbox = QtWidgets.QVBoxLayout()
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1056, 600)
self.vbox2 = MainWindow.layout()
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.lineEdit_x = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_x.setGeometry(QtCore.QRect(550, 430, 40, 20))
self.lineEdit_x.setObjectName("lineEdit_x")
self.lineEdit_x.setText(str(1))
self.lineEdit_y = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_y.setGeometry(QtCore.QRect(595, 430, 40, 20))
self.lineEdit_y.setObjectName("lineEdit_y")
self.lineEdit_y.setText(str(1))
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(490, 430, 66, 13))
self.label_3.setObjectName("label_3")
self.figure = plt.figure()#figsize=(40, 10), dpi=18)
self.canvas = FigureCanvas(self.figure)
#self.canvas.mpl_connect("button_release_event", self.on_release)
self.canvas.mpl_connect("button_press_event", self.on_press)
self.canvas.setGeometry(QtCore.QRect(0,30, 400, 400))
self.toolbar = NavigationToolbar(self.canvas, self)
self.vbox2.addWidget(self.toolbar)
self.vbox2.addWidget(self.canvas)
self.plot()
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 1056, 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 plot(self):
data = [random.random() for i in range(23)]
ax = self.figure.add_subplot(111)
ax.plot(data, 'r-', linewidth=0.5)
ax.set_title('PyQt Matplotlib Example')
self.canvas.draw()
def on_press(self, event):
self.lineEdit_x.setText(str(event.x))
self.lineEdit_y.setText(str(event.y))
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label_3.setText(_translate("MainWindow", "Center (x,y)"))
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_())
as one can see I have 2 issues:
the menu bar is detached from the figure and reduced
the axes are smaller than the figure field.
so can someone please check my code to address these two issues with minimum modifications
thanks
There are many issues with your code, and most of them are caused by the fact that you're editing a pyuic file, which is considered a bad practice, and one of the many reasons of that consideration is that the class pyuic provides is often being used in the wrong way, like in this case.
First of all, you're creating instances for both QMainWindow and Ui_MainWindow, which already inherits from QMainWindow, so the first one is completely pointless.
Then, you're trying to access the main window layout, but that's also wrong, as QMainWindow has its own private layout. The only proper way to add widgets to a main window is using the provided API, and, in your case:
setCentralWidget() to set the central widget, which is the main content of the window, and set its layout to which the actual widgets are being added;
addToolBar() to add tool bars;
So, this is a proper rewriting of your expected result.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.resize(1056, 600)
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.canvas.mpl_connect('button_press_event', self.on_press)
self.toolbar = NavigationToolbar(self.canvas, self)
self.addToolBar(self.toolbar)
self.label = QtWidgets.QLabel('Center (x,y)')
self.lineEdit_x = QtWidgets.QLineEdit()
self.lineEdit_x.setText(str(1))
self.lineEdit_y = QtWidgets.QLineEdit()
self.lineEdit_y.setText(str(1))
central = QtWidgets.QWidget(self)
self.setCentralWidget(central)
mainLayout = QtWidgets.QVBoxLayout(central)
mainLayout.addWidget(self.canvas)
bottomLayout = QtWidgets.QHBoxLayout()
mainLayout.addLayout(bottomLayout)
bottomLayout.addWidget(self.label)
bottomLayout.addWidget(self.lineEdit_x)
bottomLayout.addWidget(self.lineEdit_y)
self.plot()
def plot(self):
data = [random.random() for i in range(23)]
ax = self.figure.add_subplot(111)
ax.plot(data, 'r-', linewidth=0.5)
ax.set_title('PyQt Matplotlib Example')
self.canvas.draw()
def on_press(self, event):
self.lineEdit_x.setText(str(event.x))
self.lineEdit_y.setText(str(event.y))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
Note that if you still want to use Designer, then keep in mind that:
as said above, you should not edit the pyuic generated file (but, instead, follow the official guidelines about using Designer;
add the canvas by code, or use a promoted widget (do some research on the subject);
always use layout managers for all widgets;
Goal: Generate a 3D scatter plot from radar data obtained from vTrig_DataCollect.
Scatterplot_widget is not being shown in the MainWindow.
Step 1 is to make the plot show up from a single data collection.
Step 2 is to repeatedly call vTrig_DataCollect and have the plot refresh at 1 Hz.
from PyQt5 import QtCore, QtGui, QtWidgets
import time
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import vTrig_DataCollect
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
MainWindow.setCentralWidget(self.centralwidget)
self.scatterplot_widget = QtWidgets.QWidget(self.centralwidget)
self.scatterplot_widget.setGeometry(QtCore.QRect(0, 0, 800, 500))
self.scatterplot_widget.setObjectName("scatterplot_widget")
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.axes = self.fig.add_subplot(111, projection='3d')
payload_data = vTrig_DataCollect.data_collect()
x_points = payload_data['x']
y_points = payload_data['y']
z_points = payload_data['z']
intensity = payload_data['intensity']
self.scatterplot_widget = self.axes.scatter(x_points, y_points, z_points, s=4, c=intensity)
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", "Visualization_Window"))
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_())
The majority of the code was generated using Qt Designer, then I added the 9 lines of code related to my plot.
You must not modify the file generated by pyuic5 so you must restore the file by running: python -m pyuic5 your_ui.ui -o mainwindow.py -x, also I will assume that the file vTrig_DataCollect.py is:
import numpy as np
def data_collect():
N = 50
return {
"x": np.random.rand(N),
"y": np.random.rand(N),
"z": np.random.rand(N),
"intensity": np.random.rand(N),
}
The idea is to create the canvas (which is a QWidget too) and place it on top of another widget using a layout, then update the canvas with the new data.
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from mainwindow import Ui_MainWindow
import vTrig_DataCollect
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.canvas = FigureCanvas(Figure())
self.axes = self.canvas.figure.add_subplot(111, projection="3d")
lay = QVBoxLayout(self.ui.scatterplot_widget)
lay.addWidget(self.canvas)
timer = QTimer(self, interval=1000, timeout=self.handle_timeout)
timer.start()
def handle_timeout(self):
payload_data = vTrig_DataCollect.data_collect()
x_points = payload_data["x"]
y_points = payload_data["y"]
z_points = payload_data["z"]
intensity = payload_data["intensity"]
self.axes.clear()
self.axes.scatter(x_points, y_points, z_points, s=4, c=intensity)
self.canvas.draw()
def main():
app = QApplication([])
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()
I'm trying to connect two python folder together the two files contain PYQT5 UI code both of the two files run the codes good but I'm trying to connect the two file with a pushbutton:
file_1 called graph_window.py
contains a pushbutton which when I click it will show file_2 output UI this a proportion of the code :
from output_graph import Dialog # file_2
def view_graph_output(self):
self.w = Dialog()
self.w.show()
def setupUi(self, Dialog):
self.pushButton = QtWidgets.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(304, 162, 91, 31))
self.pushButton.setObjectName("pushButton")
# click function for the push button
self.pushButton.clicked.connect(self.view_graph_output)
file two called output_graph.py
class Ui_Dialog:
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(900, 500)
self.pushButton = QtWidgets.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(0, 380, 141, 61))
self.pushButton.setObjectName("pushButton")
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "output window"))
class Canvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = np.array([50, 30, 40])
labels = ["Apples", "Bananas", "Melons"]
self.axes.pie(x, labels=labels)
self.draw()
class Dialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.canvas = Canvas(self, width=8, height=4)
self.canvas.move(0, 0)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
when I run the code it gives me this error:
TypeError: 'QDialog' object is not callable
it seems I'm doing wrong when I call the output_graph from the Dialog class and I try to put the QtWidgets.QDialog with the calls and it didn't work
is there a way to call the dialog class from the pushbutton form file_1?
I think it should go like this:
import sys
from output_graph import Dialog # file_2
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class Ui_Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi()
# self.canvas = Canvas(self, width=8, height=4)
# self.canvas.move(0, 0)
def view_graph_output(self):
self.w = Dialog()
self.w.show()
def setupUi(self):
self.pushButton = QtWidgets.QPushButton('push', self)
self.pushButton.setGeometry(QtCore.QRect(304, 162, 91, 31))
self.pushButton.setObjectName("pushButton")
# click function for the push button
self.pushButton.clicked.connect(self.view_graph_output)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Ui_Dialog()
w.show()
sys.exit(app.exec_())
and output_graph.py should be:
import sys
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5 import QtCore
from PyQt5 import QtWidgets
import numpy as np
class Ui_Dialog:
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(900, 500)
self.pushButton = QtWidgets.QPushButton(Dialog)
self.pushButton.setGeometry(QtCore.QRect(0, 380, 141, 61))
self.pushButton.setObjectName("pushButton")
# self.pushButton.clicked.connect(lambda: funcion(12))
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "output window"))
class Canvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = np.array([50, 30, 40])
labels = ["Apples", "Bananas", "Melons"]
self.axes.pie(x, labels=labels)
self.draw()
class Dialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.canvas = Canvas(self, width=8, height=4)
self.canvas.move(0, 0)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.show()
sys.exit(app.exec_())
PS: it's a messy code so requires cleaning up, but works as you wanted, as to my understanding of the problem
Matplotlib figure/plot/canvas/layout hides buttons. The picture should explain the problem: I would like the container of the plot to leave space for the buttons on the right.
Here is a minimal working code example with some commented things that I tried:
import sys
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QApplication, QDialog
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
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(QDialog):
def __init__(self, parent=None):
super(Ui_Dialog, self).__init__(parent)
self.setupUi(self)
self.setupPlot()
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
Dialog.setToolTip("")
Dialog.setStatusTip("")
Dialog.setWhatsThis("")
Dialog.setAccessibleName("")
Dialog.setAccessibleDescription("")
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
self.buttonBox.setGeometry(QtCore.QRect(320, 50, 81, 241))
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
pass
def setupPlot(self):
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
#self.canvas.resize(self, 5, 5)
#self.resize(5,5)
self.toolbar = NavigationToolbar(self.canvas, self)
layout = QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self.plot()
def plot(self):
data = [random.random() for i in range(10)]
self.figure.clear()
#plt.figure(num=2, figsize=(3, 3), dpi=80, facecolor='w', edgecolor='k')
#self.figure.set_size_inches(5, 5, forward=True)
ax = self.figure.add_subplot(111)
#self.figure.subplots_adjust(left=1, bottom=1, right=1, top=1, wspace=1, hspace=1)
ax.plot(data, '*-')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Ui_Dialog()
main.show()
sys.exit(app.exec_())
Instead of placing the buttonbox in absolute coordinates, you may add it to a layout, just like you did with the canvas and the toolbar. To this end, you may use another widget, which contains the canvas and toolbar. This widget can be placed in a QHBoxLayout(); the QDialogButtonBox would be added second in the layout.
import sys
#from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QApplication, QDialog
from PyQt4.QtGui import QPushButton, QVBoxLayout, QApplication, QDialog
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import random
#from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt4 import QtCore, QtGui #, QtWidgets
class Ui_Dialog(QDialog):
def __init__(self, parent=None):
super(Ui_Dialog, self).__init__(parent)
self.setupUi(self)
self.setupPlot()
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.buttonBox = QtGui.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
self.setLayout(QtGui.QHBoxLayout())
self.layout().setContentsMargins(0,0,0,0)
def setupPlot(self):
self.figure = plt.figure()
self.figure.set_facecolor("none")
self.canvas = FigureCanvas(self.figure)
self.canvas.setContentsMargins(0,0,0,0)
self.widget = QtGui.QWidget()
self.widget.setContentsMargins(0,0,0,0)
self.toolbar = NavigationToolbar(self.canvas, self)
self.toolbar.setContentsMargins(0,0,0,0)
layout = QVBoxLayout()
layout.setSpacing(0)
layout.setContentsMargins(0,0,0,0)
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.widget.setLayout(layout)
self.layout().addWidget(self.widget)
self.layout().addWidget(self.buttonBox)
self.plot()
def plot(self):
data = [random.random() for i in range(10)]
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.plot(data, '*-')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Ui_Dialog()
main.show()
sys.exit(app.exec_())
Alternatively, you can first place the toolbar in a QVBoxLayout and beneath place a widget containing the canvas and the Button box in a QHBoxLayout.
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import random
from PyQt4 import QtCore, QtGui
class Ui_Dialog(QtGui.QDialog):
def __init__(self, parent=None):
super(Ui_Dialog, self).__init__(parent)
self.setupUi(self)
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.buttonBox = QtGui.QDialogButtonBox(Dialog)
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.buttonBox.accepted.connect(Dialog.accept)
self.buttonBox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
self.setLayout(QtGui.QVBoxLayout())
self.layout().setContentsMargins(0,0,0,0)
self.figure = plt.figure()
self.figure.set_facecolor("none")
self.canvas = FigureCanvas(self.figure)
self.widget = QtGui.QWidget()
self.toolbar = NavigationToolbar(self.canvas, self)
self.layout().addWidget(self.toolbar)
layout = QtGui.QHBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.buttonBox)
self.widget.setLayout(layout)
self.layout().addWidget(self.widget)
self.plot()
def plot(self):
data = [random.random() for i in range(10)]
self.figure.clear()
ax = self.figure.add_subplot(111)
ax.plot(data, '*-')
self.canvas.draw()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Ui_Dialog()
main.show()
sys.exit(app.exec_())
What is the best way to embed an interactive 3D plot in a PySide GUI? I have looked at some examples on here of 2D plots embedded in a PySide GUI:
Getting PySide to Work With Matplotlib
Matplotlib Interactive Graph Embedded In PyQt
Python/Matplotlib/Pyside Fast Timetrace Scrolling
However, the functionality that I'm looking for is not quite the same. The figure needs to rotate and zoom based on mouse input from the user in the same way as if it were drawn in a separate window.
I'm trying to avoid having to go in manually and write functions for transforming mouse click + move into a figure rotate and canvas repaint--even if that's the only way, I'm not even sure how to do that. But I figure (no pun intended) that there should be a way to reuse the functionality already present for creating 3D plots in their own windows.
Here's my code. It works as intended, but the plot is not interactive. Any advice is appreciated!
EDIT: I fixed the use of FigureCanvas according to tcaswell's corrections. I also added a bit from the matplotlib Event Handling and Picking documentation to show that the figure seems to be getting the events upon mouseclick.
Final Edit: The following code now produces the plot as desired.
# -*- coding: utf-8 -*-
from PySide import QtCore, QtGui
import numpy as np
import matplotlib
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
# import 3D plotting
from mpl_toolkits.mplot3d import Axes3D # #UnusedImport
from matplotlib.figure import Figure
# Auto-generated code from QT Designer ----------------------------------------
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(750, 497)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.frame_2 = QtGui.QFrame(self.centralwidget)
self.frame_2.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_2.setFrameShadow(QtGui.QFrame.Raised)
self.frame_2.setObjectName("frame_2")
self.verticalLayout = QtGui.QVBoxLayout(self.frame_2)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtGui.QLabel(self.frame_2)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.label_2 = QtGui.QLabel(self.frame_2)
self.label_2.setObjectName("label_2")
self.verticalLayout.addWidget(self.label_2)
self.lineEdit = QtGui.QLineEdit(self.frame_2)
sizePolicy = QtGui.QSizePolicy(
QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(
self.lineEdit.sizePolicy().hasHeightForWidth())
self.lineEdit.setSizePolicy(sizePolicy)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
spacerItem = QtGui.QSpacerItem(
20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
self.horizontalLayout_2.addWidget(self.frame_2)
self.frame_plot = QtGui.QFrame(self.centralwidget)
self.frame_plot.setMinimumSize(QtCore.QSize(500, 0))
self.frame_plot.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_plot.setFrameShadow(QtGui.QFrame.Raised)
self.frame_plot.setObjectName("frame_plot")
self.horizontalLayout_2.addWidget(self.frame_plot)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate(
"MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("MainWindow",
"This is a qlabel.", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow",
"And this is another one.", None, QtGui.QApplication.UnicodeUTF8))
self.lineEdit.setText(QtGui.QApplication.translate("MainWindow",
"Text goes here.", None, QtGui.QApplication.UnicodeUTF8))
# Auto-generated code from QT Designer ----------------------------------------
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# intialize the window
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# create the matplotlib widget and put it in the frame on the right
self.ui.plotWidget = Mpwidget(parent=self.ui.frame_plot)
class Mpwidget(FigureCanvas):
def __init__(self, parent=None):
self.figure = Figure(facecolor=(0, 0, 0))
super(Mpwidget, self).__init__(self.figure)
self.setParent(parent)
# plot random 3D data
self.axes = self.figure.add_subplot(111, projection='3d')
self.data = np.random.random((3, 100))
self.axes.plot(self.data[0, :], self.data[1, :], self.data[2, :])
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
# adjust the frame size so that it fits right after the window is shown
s = mw.ui.frame_plot.size()
mw.ui.plotWidget.setGeometry(1, 1, s.width() - 2, s.height() - 2)
sys.exit(app.exec_())
You are not using FigureCanvas right:
class Mpwidget(FigureCanvas):
def __init__(self, parent=None):
self.figure = Figure(facecolor=(0, 0, 0))
super(Mpwidget, self).__init__(self.figure) # this object _is_ your canvas
self.setParent(parent)
# plot random 3D data
self.axes = self.figure.add_subplot(111, projection='3d')
self.data = np.random.random((3, 100))
self.axes.plot(self.data[0, :], self.data[1, :], self.data[2, :])
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mw = MainWindow()
mw.show()
# adjust the frame size so that it fits right after the window is shown
s = mw.ui.frame_plot.size()
mw.ui.plotWidget.setGeometry(1, 1, s.width() - 2, s.height() - 2)
sys.exit(app.exec_())
Every time you called FigureCanvas(..) you were attaching the figure to a new canvas (which were not the FigureCanvas you were seeing) hence the call-backs were never firing (because they were listening on a FigureCanvas that you couldn't see).
1) Create the FigureCanvas before adding the axes. See https://stackoverflow.com/a/9007892/3962328
canvas = FigureCanvas(fig)
ax = figure.add_subplot(111, projection='3d')
or
class MyFigureCanvas(FigureCanvas):
def __init__(self):
self.figure = Figure()
super(FigureCanvas, self).__init__(self.figure)
self.axes = self.figure.add_subplot(111, projection='3d')
2) Try ax.mouse_init() to restore the connection:
...
ax = fig.gca(projection="3d")
...
canvas = FigureCanvas(fig)
ax.mouse_init()