Matplotlib + PyQt5, add custom Tools to the Toolbar - python

I'm developing a PYQt5 application with an embedded matplotlib canvas. The idea is, that an image is presented and the user then can draw rectangles into the image. I would like to realize this by adding a new tool to the matplotlib toolbar. The tool should work similarily to the zoom tool, the user selects the tool, draws a rectangle and I get the bounding box in Python, such that I can save it and also draw it for the user. However, I am currently unable to add ANY new tool to the toolbar.
My code currently looks like this:
import sys
import matplotlib
import numpy as np
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
from matplotlib.backends.backend_qtagg import FigureCanvas
from matplotlib.backends.backend_qtagg import \
NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (QAction, QApplication, QMainWindow, QVBoxLayout,
QWidget)
plt.rcParams['toolbar'] = 'toolmanager'
class ListTools(ToolBase):
"""List all the tools controlled by the `ToolManager`."""
# keyboard shortcut
default_keymap = 'm'
description = 'List Tools'
def trigger(self, *args, **kwargs):
print('_' * 80)
print("{0:12} {1:45} {2}".format('Name (id)', 'Tool description',
'Keymap'))
print('-' * 80)
tools = self.toolmanager.tools
for name in sorted(tools):
if not tools[name].description:
continue
keys = ', '.join(sorted(self.toolmanager.get_tool_keymap(name)))
print("{0:12} {1:45} {2}".format(name, tools[name].description,
keys))
print('_' * 80)
print("Active Toggle tools")
print("{0:12} {1:45}".format("Group", "Active"))
print('-' * 80)
for group, active in self.toolmanager.active_toggle.items():
print("{0:12} {1:45}".format(str(group), str(active)))
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.sc = FigureCanvas(Figure(figsize=(5, 3), tight_layout=True))
self.sc.manager.toolmanager.add_tool('List', ListTools)
self.toolbar = NavigationToolbar(self.sc, self)
self.addToolBar(self.toolbar)
print(dir(self.toolbar))
uniform_data = np.random.rand(10, 12)
axes = self.sc.figure.subplots()
axes.imshow(uniform_data)
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
layout.addWidget(self.sc)
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()
However, it fails at line 51:
self.sc.manager.toolmanager.add_tool('List', ListTools)
AttributeError: 'NoneType' object has no attribute 'toolmanager'
I couldn't find any additional information in the Matplotlib tutorial. Nor could I find any solutions about this issue on Stackoverflow, the only question I found was this, however, there are no answers. Futhermore, I only found one GitHub issue about this, which also does not go into detail how to fix this problem. Could somebody explain to me, what I am doing wrong?

The manager.toolmanager.addtool() method is derived from matplotlib.pyplot.figure() and not from matplotlib.backends.backend_qtagg.FigureCanvas(). However for NavigationToolbar() you need FigureCanvas().
import sys
import matplotlib
import numpy as np
matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt
from matplotlib.backend_tools import ToolBase
from matplotlib.backends.backend_qtagg import FigureCanvas
from matplotlib.backends.backend_qtagg import \
NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (QAction, QApplication, QMainWindow, QVBoxLayout,
QWidget)
class ListTools(ToolBase):
"""List all the tools controlled by the `ToolManager`."""
# keyboard shortcut
default_keymap = 'm'
description = 'List Tools'
def trigger(self, *args, **kwargs):
print('_' * 80)
print("{0:12} {1:45} {2}".format('Name (id)', 'Tool description',
'Keymap'))
print('-' * 80)
tools = self.toolmanager.tools
for name in sorted(tools):
if not tools[name].description:
continue
keys = ', '.join(sorted(self.toolmanager.get_tool_keymap(name)))
print("{0:12} {1:45} {2}".format(name, tools[name].description,
keys))
print('_' * 80)
print("Active Toggle tools")
print("{0:12} {1:45}".format("Group", "Active"))
print('-' * 80)
for group, active in self.toolmanager.active_toggle.items():
print("{0:12} {1:45}".format(str(group), str(active)))
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# https://matplotlib.org/2.0.2/examples/user_interfaces/toolmanager.html
self.sc = FigureCanvas(Figure(figsize=(5, 3), tight_layout=True))
#self.sc.canvas.manager.toolmanager.add_tool('List', ListTools)
fig = plt.figure()
fig.canvas.manager.toolmanager.add_tool('List', ListTools)
self.toolbar = NavigationToolbar(self.sc, self)
self.addToolBar(self.toolbar)
print(dir(self.toolbar))
uniform_data = np.random.rand(10, 12)
axes = self.sc.figure.subplots()
axes.imshow(uniform_data)
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
layout.addWidget(self.sc)
def main():
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
if __name__ == "__main__":
main()

Related

Why can't I successfully draw the region of interest in a matplotlib color map embedded in a pyqt5 gui?

I'm trying to draw a region of interest on a color map that is embedded in a pyqt5 gui. This is an example of what I want.
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton,
QHBoxLayout, QVBoxLayout, QApplication)
from PyQt5 import QtCore
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import ROI_class as roi # ROI_class.py
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.drawButton.clicked.connect(self.draw_map_Callback)
self.roiButton.clicked.connect(self.choose_roi)
def initUI(self):
self.drawButton = QPushButton("draw map")
self.roiButton = QPushButton("roi")
self.hbox = QHBoxLayout()
self.hbox.addStretch(1)
self.hbox.addWidget(self.drawButton)
self.hbox.addWidget(self.roiButton)
self.vbox = QVBoxLayout()
self.vbox.addStretch(1)
self.vbox.addLayout(self.hbox)
self.setLayout(self.vbox)
self.setGeometry(500, 500, 500, 500)
self.setWindowTitle('ROI')
self.show()
def draw_map_Callback(self):
img = np.ones((100, 100)) * range(0, 100)
fig, ax1 = plt.subplots()
self.con_canvas = FigureCanvas(plt.figure(tight_layout=True))
self.con_canvas.setFocusPolicy(QtCore.Qt.ClickFocus)
self.con_canvas.setFocus()
self.con_toolbar = NavigationToolbar(self.con_canvas, self)
self.vbox.addWidget(self.con_toolbar)
self.vbox.addWidget(self.con_canvas)
self._con_ax = self.con_canvas.figure.subplots()
self.con_img = self._con_ax.imshow(img, cmap ='jet')
self._con_ax.set_xlabel('xlabel')
self._con_ax.set_ylabel('ylabel')
self.con_cbar = self.con_canvas.figure.colorbar(self.con_img)
self._con_ax.set_aspect('equal')
def choose_roi(self):
y = roi.new_ROI(self.con_img)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
It will draw a colormap when I click "draw map". Then I want it to allow me to draw a region of interest with my mouse and get a mask using the code on this link below.
https://github.com/martindurant/misc/blob/master/ROI.py
The "ROI_class" that is imported is just a copy and paste of the code in the link above.
I can successfully draw the plot on the GUI but when I click "roi", it doesn't allow me to draw the region of interest.
When I mad a new file and paste the code in the link above with something like
fig, ax1 = plt.subplots()
s = ax1.imshow(img, cmap ='jet')
ax1.set_xlabel('subcolor')
ax1.set_ylabel('ylabel')
y = new_ROI(s)
at the end of the code, it worked just fine and I was able to draw the region of interest and get the mask of it.
But when I try to do this in the GUI, it wouldn't let me draw the region of interest. I'm very confused why this isn't working.
The problem is that picker (the variable "y") is a local variable that gets destroyed instantly causing the desired behavior not to be executed. The solution is to make it an attribute of the class:
self.y = roi.new_ROI(self.con_img)

Switch widget in QStackWidget from a button in another file

I got two py files. One has the main window with a QStackedWidget inside, the setCurrentWidget is set according to a condition. The other file has a widget which is dynamically added into the stacked widget and set as current widget when a button in the main window is clicked.
The widget in the second file has a dialog with a button in it. What I'm trying to do is, on clicking the button inside the dialog, the dialog should be closed and the setCurrentWidget is set according to the condition and the widget is removed from the stacked widget.
Here is what I've tried:
mainwindow.py
import sys
import os
import pathlib
from PySide2.QtWidgets import *
from PySide2 import *
from PySide2.QtCore import *
from PySide2.QtGui import *
list1 = ["item1", "item2", "item3"]
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(400, 300)
self.toolbar = QWidget()
self.toolbar.setFixedHeight(30)
self.toolbar.setStyleSheet("background: grey;")
self.button = QPushButton("Click here!")
t_layout = QHBoxLayout()
t_layout.setMargin(0)
t_layout.addWidget(self.button)
self.toolbar.setLayout(t_layout)
self.p1_label = QLabel("Such empty!")
self.p1_label.setStyleSheet("font-size: 30px;")
self.p1_label.setAlignment(Qt.AlignCenter)
self.p2_widget = QWidget()
self.p2_widget.setStyleSheet("background: orange;")
self.sw = QStackedWidget()
self.sw.addWidget(self.p1_label)
self.sw.addWidget(self.p2_widget)
if not list1:
self.sw.setCurrentWidget(self.p1_label)
else:
self.sw.setCurrentWidget(self.p2_widget)
self.mw_layout = QVBoxLayout()
self.mw_layout.addWidget(self.toolbar)
self.mw_layout.addWidget(self.sw)
self.setLayout(self.mw_layout)
def switch_widget():
import widget_test
p3 = widget_test.widget()
self.sw.addWidget(p3)
self.sw.setCurrentWidget(p3)
self.button.clicked.connect(switch_widget)
def switch_back(self):
import widget_test
p3 = widget_test.widget()
mwin = MainWindow()
sw_ = mwin.sw
sw_.removeWidget(p3)
p1 = mwin.p1_label
p2 = mwin.p2_widget
if not list1:
sw_.setCurrentWidget(p1)
else:
sw_.setCurrentWidget(p2)
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
widget.py
import sys
import os
import pathlib
import datetime
from PySide2.QtWidgets import *
from PySide2 import *
from PySide2.QtCore import *
from PySide2.QtGui import *
class widget(QWidget):
def __init__(self):
super(widget, self).__init__()
self.setStyleSheet("background: teal;")
widget_label = QLabel("fluid dynamics is cool")
show_pop_up = QPushButton("show pop up")
pop_up = QDialog(self)
pop_up_label = QLabel("click below to, hopefully, get outta here")
get_outta_here = QPushButton("get outta here")
pop_up_layout = QVBoxLayout()
pop_up_layout.addWidget(pop_up_label)
pop_up_layout.addWidget(get_outta_here)
pop_up.setLayout(pop_up_layout)
def show_popup():
pop_up.show()
def get_out():
from main_test import MainWindow
MainWindow.switch_back(self)
pop_up.reject()
get_outta_here.clicked.connect(get_out)
show_pop_up.clicked.connect(show_popup)
widget_layout = QVBoxLayout()
widget_layout.addWidget(widget_label)
widget_layout.addWidget(show_pop_up)
self.setLayout(widget_layout)
I could merge the code together and make it work but I'm trying to keep the directory clean.
There is a lot going on here, but let's break it down.
The main problem seems to be juggling between modules. Eventhough it might seem appealing to import the modules back and forth, it doesn't really work. What you need to look for, is the built-in Signals module that you can utilize.
Another bigger problem is that you are re-assigning some attributes eventhough you really shouldn't. You also should revisit the condition you are using to assign the .setCurrentWidget. Currently the condition reads as if list1 doesn't exist, do this. Else, do the other. Also, switch_widget should be outside of the def __init__(self):.
I rewrote some parts of the code to make it work with signals as an example for you.
mainwindow.py
import sys
import os
import pathlib
from PySide2.QtWidgets import *
from PySide2 import *
from PySide2.QtCore import *
from PySide2.QtGui import *
from widget_test import widget
list1 = ["item1", "item2", "item3"]
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.resize(400, 300)
self.toolbar = QWidget()
self.toolbar.setFixedHeight(30)
self.toolbar.setStyleSheet("background: grey;")
self.button = QPushButton("Click here!")
t_layout = QHBoxLayout()
t_layout.setMargin(0)
t_layout.addWidget(self.button)
self.toolbar.setLayout(t_layout)
self.p1_label = QLabel("Such empty!")
self.p1_label.setStyleSheet("font-size: 30px;")
self.p1_label.setAlignment(Qt.AlignCenter)
self.p2_widget = QWidget()
self.p2_widget.setStyleSheet("background: orange;")
self.p3 = None
self.sw = QStackedWidget()
self.sw.addWidget(self.p1_label)
self.sw.addWidget(self.p2_widget)
if not list1:
self.sw.setCurrentWidget(self.p1_label)
else:
self.sw.setCurrentWidget(self.p2_widget)
self.mw_layout = QVBoxLayout()
self.mw_layout.addWidget(self.toolbar)
self.mw_layout.addWidget(self.sw)
self.setLayout(self.mw_layout)
self.button.clicked.connect(self.switch_widget)
def switch_widget(self):
self.p3 = widget()
self.p3.update_signal.connect(self.switch_back)
self.sw.addWidget(self.p3)
self.sw.setCurrentWidget(self.p3)
def switch_back(self):
self.sw.removeWidget(self.p3)
if list1:
self.sw.setCurrentWidget(self.p1_label)
else:
self.sw.setCurrentWidget(self.p2_widget)
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
widget.py
import sys
import os
import pathlib
import datetime
from PySide2.QtWidgets import *
from PySide2 import *
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtCore import Signal
class widget(QWidget):
update_signal = Signal()
def __init__(self):
super(widget, self).__init__()
self.setStyleSheet("background: teal;")
widget_label = QLabel("fluid dynamics is cool")
show_pop_up = QPushButton("show pop up")
pop_up = QDialog(self)
pop_up_label = QLabel("click below to, hopefully, get outta here")
get_outta_here = QPushButton("get outta here")
pop_up_layout = QVBoxLayout()
pop_up_layout.addWidget(pop_up_label)
pop_up_layout.addWidget(get_outta_here)
pop_up.setLayout(pop_up_layout)
def show_popup():
pop_up.show()
def get_out():
self.update_signal.emit()
pop_up.reject()
get_outta_here.clicked.connect(get_out)
show_pop_up.clicked.connect(show_popup)
widget_layout = QVBoxLayout()
widget_layout.addWidget(widget_label)
widget_layout.addWidget(show_pop_up)
self.setLayout(widget_layout)
Finally, check Python coding conventions for naming and other "minor" details.

Redraw matplotlib figure after updating Line2D properties through PyQt5 GUI

In the following minimal-example GUI, I have a button and a plot window containing 2 lines. When the user clicks on the button, I want the linewidth of both lines to be changed, after which the plot should be redrawn.
When I click the button, I get a TypeError for the draw() command:
Traceback (most recent call last):
File "minimal_plot.py", line 55, in updatePlot
self.sc.ax.draw()
TypeError: draw_wrapper() missing 1 required positional argument: 'renderer'
I never had to pass arguments to draw() before. What should I put there?
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.Qt import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
fig = Figure(figsize=(10, 10), dpi=100)
self.ax = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# --- Define layout ---
layout1 = QVBoxLayout()
btn = QPushButton('', self)
btn.clicked.connect(self.updatePlot)
layout1.addWidget(btn)
# Plot section
self.sc = MplCanvas(self)
layout1.addWidget(self.sc)
self.sc.setFixedHeight(600)
self.sc.setFixedWidth(600)
self.show()
widget = QWidget()
widget.setLayout(layout1)
self.setCentralWidget(widget)
# Create some lines
self.sc.ax.plot([0,1,2,3,4], [10,1,20,3,40])
self.sc.ax.plot([0,1,2,3,4], [9,0,19,2,39])
def updatePlot(self):
lines = self.sc.ax.get_lines()
for i in range(len(lines)):
lines[i].set_linewidth(4)
self.sc.ax.draw()
# Fix to make PyQt5 close correctly in Spyder
def closeEvent(self,event):
QApplication.quit()
# Run main code
app = QApplication.instance()
window = MainWindow()
window.show()
app.exec_()
Note: the code may differ slightly from a normal PyQt5 application because I run it in Spyder (that's why I invoke app = Qapplication.instance() instead of app = Qapplication([]). The closeEvent() definition can also be removed if one does not use Spyder.

Python 2.7 Qt Matplotlib : subplot ID reference from event

My goal is to identify which subplot has been clicked on by the user. More precisely in the matplotlib class, I can identify the subplot using event.inaxes. Great. But I cannot get that event in the Qt widget class.
I am definitely missing something ...
Here is the code with my latest "awkward" attempt. Any suggestion on how to procceed ?
I am no Python expert. Python 2.7 has to be used (no choice)
from __future__ import print_function
from __future__ import division
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backend_bases import key_press_handler
from matplotlib.backend_bases import Event
from matplotlib.backends.backend_qt4agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
#Connect InventoryChartsWidget to ChartFigure: QT
class TheConnector(QtCore.QObject):
selecteddataregion=pyqtSignal(name='selecteddataregion')
def emitsignal(self,xmin,xmax,ymin,ymax):
self.selecteddataregion.emit()
#Chart including events: MATPLOTLIB
class ChartFigure(Figure):
def onclick(self,event):
#MAIN ISSUE
#HOW TO RETURN THE subplot axes to class InventoryChartsWidget?
if event.button==1 :
self.ConnSbPlt.emitsignal(1.0,1.0,2.0,2.0)
print('OK: Axes is ... ', event.inaxes)
def __init__(self,Conn):
#Init the Matplotlib
Figure.__init__(self) #initialize the orginal class, see also super()
super(ChartFigure, self).__init__()
self.canvas=FigureCanvas(self)
self.ConnSbPlt=Conn
#Chart including events: QT
class InventoryChartsWidget(QtGui.QDialog):
def __init__(self, parent=None,xlimlow=0,xlimhigh=100,ylimlow=0,ylimhigh=100, G_array=[], N_array=[], ddom_array=[], hdom_array=[], speciesDict={}):
QMainWindow.__init__(self, parent)
#Fake stupid data
self.mG_array = [2] * 10
self.mHdom_array = [0.5] * 10
#jte to make sur I have it
self.xdata_start=-1.0
#fake plot to get a member of type subplot: UGLY!
#Attempt to create a member object "axes"
self.tabFake = QtGui.QWidget()
self.tabFake = self.create_tab(self.tabFake)
self.tabFake.plots = []
self.subPlotFake = self.tabFake.fig.add_subplot(111)
print("here is OK; it exists ...", self.subPlotFake)
self.create_main_frame()
self.setModal(False)
self.setVisible(True)
self.show()
def create_main_frame(self):
#Associate a Qwidget with the InventoryChartsWidget widget
print("OK here too; it exists ... ",self.subPlotFake)
self.main_frame = QtGui.QWidget()
LesTabs = QtGui.QTabWidget()
self.tabG = QtGui.QWidget()
#Fill the tab with Matplotlib object and draw the charts
self.tabG=self.create_tab(self.tabG)
self.on_draw_G(self.tabG)
self.tabG.fig.subplots_adjust(left=0.02,bottom=0.05,right=1,top=0.95,wspace=0.2,hspace=0.2)
LesTabs.addTab(self.tabG,"Chart")
grid = QGridLayout()
grid.addWidget(LesTabs, 0, 0)
self.main_frame.setLayout(grid)
self.setLayout(grid)
self.layout().addWidget(self.main_frame)
def UpdatePlot_DataSelection(self):
#SLOT
print("Get connected here process the data in the subplot XX...")
def on_draw_G(self,tab):
#Juts one subplot for test purpose
tab.fig.clear()
tab.plots = []
subPlot = tab.fig.add_subplot(111)
subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
subPlot = tab.fig.add_subplot(122)
subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
tab.canvas.draw()
def create_tab(self,tab):
#Create the tab widget, associated with Matplotlib plot
print("OK member exists ... ", self.xdata_start)
print("OK member exists ",self.tabFake)
#ISSUE HERE: don't understand
#print("NOT OK !!! member does not exist Why ? ",self.subPlotFake)
Conn=TheConnector()
#MATPLOTLIB
tab.fig = ChartFigure(Conn)
tab.canvas = FigureCanvas(tab.fig)
tab.canvas.setParent(tab)
tab.canvas.setFocusPolicy(Qt.StrongFocus)
tab.canvas.setFocus()
#connect signal to slot
Conn.selecteddataregion.connect(self.UpdatePlot_DataSelection)
tab.mpl_toolbar = NavigationToolbar(tab.canvas, tab)
vbox = QVBoxLayout()
vbox.addWidget(tab.canvas)
vbox.addWidget(tab.mpl_toolbar)
tab.setLayout(vbox)
tab.canvas.mpl_connect('button_press_event', tab.fig.onclick)
return tab
def on_key_press(self, event):
#Keyboard input: standard mpl key press
key_press_handler(event, self.canvas, self.mpl_toolbar)
def PlotData_G(self, plot, G_array, hdom_array):
# Plot G
plot.hold(False)
plot.scatter(x=hdom_array, y=G_array, marker='+',linewidths=1.5)
plot.set_autoscaley_on(True)
plot.tick_params(labelsize=8)
def main():
app = QApplication(sys.argv)
form = InventoryChartsWidget(xlimlow=0,xlimhigh=60,ylimlow=0,ylimhigh=80)
form.show()
app.exec_()
if __name__ == "__main__":
main()
There are 3 classes:
TheConnector is a Signal/Slot QT class
ChartFigure is matplotlib class (including the desired mouse Event)
InventoryChartsWidget is the main widget (Qt; here I need the ID of the subplot)
Any help would be greatly appreciated. Thank you.
Here is a working solution. The true limit was my thinking in python (to be more specific the lack of properly declared variables ... can't get use to it)
from __future__ import print_function
from __future__ import division
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backend_bases import key_press_handler
from matplotlib.backend_bases import Event
from matplotlib.backends.backend_qt4agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
#Connect InventoryChartsWidget to ChartFigure: QT
class TheConnector(QtCore.QObject):
selecteddataregionARG=pyqtSignal(object,name='selecteddataregionIN')
def emitsignalEvent(self,TheEvent):
self.selecteddataregionARG.emit(TheEvent)
#Chart including events: MATPLOTLIB
class ChartFigure(Figure):
def onclick(self,event):
#MAIN ISSUE
#HOW TO RETURN THE subplot axes to class InventoryChartsWidget class?
if event.button==1 :
print('Event: Axes is ... ', event.inaxes)
self.ConnSbPlt.emitsignalEvent(event.inaxes)
def __init__(self,Conn):
#Init the Matplotlib
Figure.__init__(self) #initialize the orginal class, see also super()
super(ChartFigure, self).__init__()
self.canvas=FigureCanvas(self)
self.ConnSbPlt=Conn
#Chart including events: QT
class InventoryChartsWidget(QtGui.QDialog):
def __init__(self, parent=None,xlimlow=0,xlimhigh=100,ylimlow=0,ylimhigh=100, G_array=[], N_array=[], ddom_array=[], hdom_array=[], speciesDict={}):
QMainWindow.__init__(self, parent)
#Fake stupid data
self.mG_array = [2] * 10
self.mHdom_array = [0.5] * 10
#jte to make sur I have it
self.xdata_start=-1.0
self.create_main_frame()
self.setModal(False)
self.setVisible(True)
self.show()
def create_main_frame(self):
#Associate a Qwidget with the InventoryChartsWidget widget
self.main_frame = QtGui.QWidget()
LesTabs = QtGui.QTabWidget()
self.tabG = QtGui.QWidget()
#Fill the tab with Matplotlib object and draw the charts
self.tabG=self.create_tab(self.tabG)
self.on_draw_G(self.tabG)
self.tabG.fig.subplots_adjust(left=0.02,bottom=0.05,right=1,top=0.95,wspace=0.2,hspace=0.2)
LesTabs.addTab(self.tabG,"Chart")
grid = QGridLayout()
grid.addWidget(LesTabs, 0, 0)
self.main_frame.setLayout(grid)
self.setLayout(grid)
self.layout().addWidget(self.main_frame)
def UpdatePlot_DataSelection_withArg(self,TheEvent):
#SLOT
print("WITH ARG : Get connected here process the data in the subplot XX...",TheEvent)
def on_draw_G(self,tab):
#Juts one subplot for test purpose
tab.fig.clear()
tab.plots = []
subPlot = tab.fig.add_subplot(111)
subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
subPlot = tab.fig.add_subplot(122)
subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
tab.canvas.draw()
def create_tab(self,tab):
#Create the tab widget, associated with Matplotlib plot
Conn=TheConnector()
#MATPLOTLIB
tab.fig = ChartFigure(Conn)
tab.canvas = FigureCanvas(tab.fig)
tab.canvas.setParent(tab)
tab.canvas.setFocusPolicy(Qt.StrongFocus)
tab.canvas.setFocus()
#connect signal to slot
Conn.selecteddataregionARG.connect(lambda who="Three": self.UpdatePlot_DataSelection_withArg(who))
tab.mpl_toolbar = NavigationToolbar(tab.canvas, tab)
vbox = QVBoxLayout()
vbox.addWidget(tab.canvas)
vbox.addWidget(tab.mpl_toolbar)
tab.setLayout(vbox)
tab.canvas.mpl_connect('button_press_event', tab.fig.onclick)
return tab
def on_key_press(self, event):
#Keyboard input: standard mpl key press
key_press_handler(event, self.canvas, self.mpl_toolbar)
def PlotData_G(self, plot, G_array, hdom_array):
# Plot G
plot.hold(False)
plot.scatter(x=hdom_array, y=G_array, marker='+',linewidths=1.5)
plot.set_autoscaley_on(True)
def main():
app = QApplication(sys.argv)
form = InventoryChartsWidget(xlimlow=0,xlimhigh=60,ylimlow=0,ylimhigh=80)
form.show()
app.exec_()
if __name__ == "__main__":
main()
Maybe it can help someone ...
We can of course argue if the 3 classes make sense but this is another topic.
Here is how you could transfer the event to the main class:
from __future__ import print_function
from __future__ import division
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from matplotlib.figure import Figure
from matplotlib.backend_bases import key_press_handler
from matplotlib.backends.backend_qt4agg import (
FigureCanvasQTAgg as FigureCanvas,
NavigationToolbar2QT as NavigationToolbar)
#Connect InventoryChartsWidget to ChartFigure: QT
class TheConnector(QtCore.QObject):
selecteddataregion=QtCore.pyqtSignal(object, name='selecteddataregion')
def emitsignal(self,xmin,xmax,ymin,ymax, event):
self.selecteddataregion.emit((xmin,xmax,ymin,ymax, event))
#Chart including events: MATPLOTLIB
class ChartFigure(Figure):
def onclick(self,event):
#MAIN ISSUE
#HOW TO RETURN THE subplot axes to class InventoryChartsWidget?
if event.button==1 :
print('OK: Axes is ... ', event.inaxes)
self.ConnSbPlt.emitsignal(1.0,1.0,2.0,2.0, event)
def __init__(self,Conn):
#Init the Matplotlib
Figure.__init__(self) #initialize the orginal class, see also super()
super(ChartFigure, self).__init__()
self.canvas=FigureCanvas(self)
self.ConnSbPlt=Conn
#Chart including events: QT
class InventoryChartsWidget(QtGui.QDialog):
def __init__(self, parent=None,xlimlow=0,xlimhigh=100,ylimlow=0,ylimhigh=100, G_array=[], N_array=[], ddom_array=[], hdom_array=[], speciesDict={}):
QtGui.QMainWindow.__init__(self, parent)
#Fake stupid data
self.mG_array = [2] * 10
self.mHdom_array = [0.5] * 10
#jte to make sur I have it
self.xdata_start=-1.0
#fake plot to get a member of type subplot: UGLY!
#Attempt to create a member object "axes"
self.tabFake = QtGui.QWidget()
self.tabFake = self.create_tab(self.tabFake)
self.tabFake.plots = []
self.subPlotFake = self.tabFake.fig.add_subplot(111)
print("here is OK; it exists ...", self.subPlotFake)
self.create_main_frame()
self.setModal(False)
self.setVisible(True)
self.show()
def create_main_frame(self):
#Associate a Qwidget with the InventoryChartsWidget widget
print("OK here too; it exists ... ",self.subPlotFake)
self.main_frame = QtGui.QWidget()
LesTabs = QtGui.QTabWidget()
self.tabG = QtGui.QWidget()
#Fill the tab with Matplotlib object and draw the charts
self.tabG=self.create_tab(self.tabG)
self.on_draw_G(self.tabG)
self.tabG.fig.subplots_adjust(left=0.02,bottom=0.05,right=1,top=0.95,wspace=0.2,hspace=0.2)
LesTabs.addTab(self.tabG,"Chart")
grid = QtGui.QGridLayout()
grid.addWidget(LesTabs, 0, 0)
self.main_frame.setLayout(grid)
self.setLayout(grid)
self.layout().addWidget(self.main_frame)
def UpdatePlot_DataSelection(self, transfer_object):
#SLOT
xmin,xmax,ymin,ymax, event = transfer_object
print ("Axes are now in the InventoryChartsWidget: ", event.inaxes)
def on_draw_G(self,tab):
#Juts one subplot for test purpose
tab.fig.clear()
tab.plots = []
subPlot = tab.fig.add_subplot(111)
#subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
subPlot = tab.fig.add_subplot(122)
#subPlot.hold(False)
tab.plots.append(subPlot)
self.PlotData_G(subPlot,self.mG_array,self.mHdom_array)
tab.canvas.draw()
def create_tab(self,tab):
#Create the tab widget, associated with Matplotlib plot
print("OK member exists ... ", self.xdata_start)
print("OK member exists ",self.tabFake)
#ISSUE HERE: don't understand
#print("NOT OK !!! member does not exist Why ? ",self.subPlotFake)
# reason: self.subPlotFake does not yet exist
Conn=TheConnector()
#MATPLOTLIB
tab.fig = ChartFigure(Conn)
tab.canvas = FigureCanvas(tab.fig)
tab.canvas.setParent(tab)
tab.canvas.setFocusPolicy(QtCore.Qt.StrongFocus)
tab.canvas.setFocus()
#connect signal to slot
Conn.selecteddataregion.connect(self.UpdatePlot_DataSelection)
tab.mpl_toolbar = NavigationToolbar(tab.canvas, tab)
vbox = QtGui.QVBoxLayout()
vbox.addWidget(tab.canvas)
vbox.addWidget(tab.mpl_toolbar)
tab.setLayout(vbox)
tab.canvas.mpl_connect('button_press_event', tab.fig.onclick)
return tab
def on_key_press(self, event):
#Keyboard input: standard mpl key press
key_press_handler(event, self.canvas, self.mpl_toolbar)
def PlotData_G(self, plot, G_array, hdom_array):
# Plot G
#plot.hold(False) #axes.hold is deprecated.
plot.scatter(x=hdom_array, y=G_array, marker='+',linewidths=1.5)
plot.set_autoscaley_on(True)
plot.tick_params(labelsize=8)
def main():
app = QtGui.QApplication(sys.argv)
form = InventoryChartsWidget(xlimlow=0,xlimhigh=60,ylimlow=0,ylimhigh=80)
form.show()
app.exec_()
if __name__ == "__main__":
main()

Adding mpl_toolkits.basemap canvas to Pyside

I've generated a map using mpl_toolkits.basemap and it works.
However, after trying to integrate it into Pyside, I'm having trouble displaying it as a QWidget. I'm not getting any errors, the program just hangs while I wait for it to launch. I've looked online, and there isn't much documentation on this subject
from PySide.QtGui import (QWidget, QVBoxLayout, QFormLayout, QLineEdit,
QPushButton, QFileDialog, QGroupBox, QApplication)
import sys
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 NavigationToolbar2QTAgg as NavigationToolbar
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
class Map(QWidget):
def __init__(self, parent=None):
super(Map, self).__init__(parent)
self.setupUI()
def setupUI(self):
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.layout = QVBoxLayout(self)
self.mpl_toolbar = NavigationToolbar(self.canvas, self, coordinates = False)
self.layout.addWidget(self.canvas)
self.layout.addWidget(self.mpl_toolbar)
self.axes = self.fig.add_subplot(111)
self.setLayout(self.layout)
# make sure the value of resolution is a lowercase L,
# for 'low', not a numeral 1
map = Basemap(projection='robin', lat_0=0, lon_0=-100,
resolution='l', area_thresh=1000.0, ax=self.axes)
map.drawcoastlines()
map.drawcountries()
map.fillcontinents(color='green')
map.drawmapboundary()
# lat/lon coordinates of five cities.
lats = [40.02, 32.73, 38.55, 48.25, 17.29]
lons = [-105.16, -117.16, -77.00, -114.21, -88.10]
cities=['Boulder, CO','San Diego, CA',
'Washington, DC','Whitefish, MT','Belize City, Belize']
# compute the native map projection coordinates for cities.
x,y = map(lons,lats)
# plot filled circles at the locations of the cities.
map.plot(x,y,'bo')
# plot the names of those five cities.
for name,xpt,ypt in zip(cities,x,y):
plt.text(xpt+50000,ypt+50000,name)
self.canvas.draw()
def main():
app = QApplication(sys.argv)
map = Map()
app.exec_()
main()
You forgot to show your widget. Add self.show() to the end of setupUI.

Categories