How to detect pan and zoom action in matplotlib navibar? - python

I am using the matplotlib canvas and navbar by creating a custom MplWidget in python, as follows:
from PyQt5.QtWidgets import QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import (FigureCanvasQTAgg as
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)
self.canvas.toolbar = NavigationToolbar(self.canvas, self)
self.layout().addWidget(self.canvas.toolbar)
self.layout().addWidget(self.canvas)
self.canvas.axes.grid(b=True, which='both', axis='both')
self.canvas.figure.set_tight_layout(True)
I want to detect when the pan or the zoom tool is toggled. I found this: in matplotlib how do I catch that event "zoom tool" has been selected?
Following the solution there, I tried
self.canvas.toolbar.get_state()['_current_action']
or simply just self.canvas.toolbar.get_state(), but I get the error:
AttributeError: 'NavigationToolbar2QT' object has no attribute 'get_state'
It seems like a very basic function to see which action is in use currently, so I am sure there is a simple solution, but I can't seem to find it.
zoom toggled

I've found two solutions, but I don't know if they work with all kinds of backends (not an expert in those).
The first is axes-dependant:
ax.get_navigate_mode()
it returns 'PAN', 'ZOOM', or None.
(Link to documentation)
The second is figure-dependant:
fig.canvas.toolbar.mode
it returns "pan/zoom", "zoom rect" or "".
(Link to source code of the NavigationToolbar2 object)

Related

navigation tool bar inside widget

I would like to put a navigation toolbar inside a widget I created with Qt designer.
I have a GUI, made in QT designer, that has 10 tabs. Each tab has a widget that I promoted to canvas.
I would like to put a navigation toolbar inside each widget.
Up to now I have tried this.
But this code simply adds a navigation toolbar at the top of the GUI
import woop
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
from PyQt4 import Qt, QtCore,QtGui
from matplotlib import cm
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import os
class woop(QtGui.QMainWindow, woop.Ui_MainWindow):
"""
Main control function for Woop GUI.
"""
# ----------------------------
def __init__(self, parent=None):
"""
Setup the GUI, and connect the buttons to functions.
"""
import os
super(woop, self).__init__(parent)
self.setupUi(self)
toolBar1 = NavigationToolbar(self.widget_LID1, self)
toolBar2 = NavigationToolbar(self.widget_LID2, self)
toolBar3 = NavigationToolbar(self.widget_LID3, self)
toolBar4 = NavigationToolbar(self.widget_LID4, self)
toolBar5 = NavigationToolbar(self.widget_LID5, self)
toolBar6 = NavigationToolbar(self.widget_LID6, self)
toolBar7 = NavigationToolbar(self.widget_LID7, self)
toolBar8 = NavigationToolbar(self.widget_LID8, self)
toolBarALL = NavigationToolbar(self.widget_LID_ALL, self)
toolBarMIR = NavigationToolbar(self.widget_MIR, self)
self.addToolBar(toolBar1)
self.addToolBar(toolBar2)
self.addToolBar(toolBar3)
self.addToolBar(toolBar4)
self.addToolBar(toolBar5)
self.addToolBar(toolBar6)
self.addToolBar(toolBar7)
self.addToolBar(toolBar8)
self.addToolBar(toolBarALL)
self.addToolBar(toolBarMIR)
self.widget_LID1.figure.clear()
self.widget_LID1.draw()
self.widget_LID2.figure.clear()
self.widget_LID2.draw()
self.widget_LID3.figure.clear()
self.widget_LID3.draw()
self.widget_LID4.figure.clear()
self.widget_LID4.draw()
self.widget_LID5.figure.clear()
self.widget_LID5.draw()
self.widget_LID6.figure.clear()
self.widget_LID6.draw()
self.widget_LID7.figure.clear()
self.widget_LID7.draw()
self.widget_LID8.figure.clear()
self.widget_LID8.draw()
self.widget_LID_ALL.figure.clear()
self.widget_LID_ALL.draw()
self.widget_LID_14.figure.clear()
self.widget_LID_14.draw()
self.widget_LID_58.figure.clear()
self.widget_LID_58.draw()
self.widget_MIR.figure.clear()
self.widget_MIR.draw()
def main():
"""
Main function
the only input to the GUI is the debug
by default is set to INFO
"""
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = woop()
#MainWindow.show()
MainWindow.showMaximized()
app.exec_()
if __name__ == '__main__':
main()
Try to add each toolbar directly to the corresponding widget using a QVBoxLayout as described in How to embed matplotlib in pyqt - for Dummies.
Or, if you still prefer to use the toolbar area of the QMainWindow, you need to connect the currentChanged signal of the QTabWidget to a slot replacing the toolbar by the one corresponding to the widget of the selected widget.
I managed to solve in this way.
I created this function
def _initialize_widget(self,widget):
"""
function that:
- initialises every tab (widget)
- add layout
- add navigation toolbar and position it at the bottom of the tab
:param widget:
:return:
"""
widget.figure.clear()
widget.draw()
widget.setLayout(QtGui.QVBoxLayout())
widget.layout().setContentsMargins(0, 710, 50, -0)#(left, top, right, bottom)
widget.layout().setSpacing(0)
toolbar = NavigationToolbar(widget, self)
widget.layout().addWidget(toolbar)
widget.figure.clear()
widget.draw()
and then I initialize the widgets (canvas)
self._initialize_widget(self.widget_LID1)
In this way I add a navigation toolbar at the bottom of the canvas.

Embedding matplotlib figure in QtDesigner GUI

I am trying to muddle my way through embedding a matplotlib figure inside of a Qt GUI created using Qt Designer. I am already able to create the figure I want just through Python. I've created a basic GUI with widgets to allow me to select/load input files, and plot the data in those files in a matplotlib figure that is embedded in the GUI. I accomplish this by adding a blank widget to the GUI called plotwidget, and then calling the GraphInit class with this widget as input.
The problem I am currently facing is that while my plot shows up and updates as desired inside the GUI, it doesn't seem to pay attention to the size policy defined for it in either my Python code (where the FigureCanvas is created) or for the plotWidget widget in Qt Designer. I have been using this demo, among others, as a starting point for this project. However, all of these examples have generated the GUI entirely from within Python. I suspect I am doing something wrong in assigning the matplotlib figure to the widget, but I haven't been able to figure out what.
Code (import & plotting code removed, but the core where figure is added is there):
import sys
import os
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QApplication, QMessageBox, QFileDialog, QPushButton, QLabel, QRadioButton, QDoubleSpinBox, QSpinBox, QWidget, QSizePolicy, QMainWindow
import PyQt5.uic
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import json
from datetime import datetime
import numpy as np
class LogViewer(QApplication):
def __init__(self):
QApplication.__init__(self, sys.argv)
self.ui = UI()
#staticmethod
def main():
vwr = LogViewer()
vwr.run()
def run(self):
self.ui.win.show() # Show the UI
self.ui.run() # Execute the UI run script
self.exec_()
class Graph_init(FigureCanvas):
def __init__(self, parent=None):
fig = Figure()
self.axes = fig.add_subplot(111)
self.compute_initial_figure()
self.axes.grid()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class UI(object):
def __init__(self):
ui_path = os.path.dirname(os.path.realpath(__file__))
self.win = PyQt5.uic.loadUi(ui_path + '\\logview.ui')
self.filename = ui_path + '\\test.txt'
self.plotWin = Graph_init(self.win.plotWidget)
def run(self):
self.init_ui() # get initial values from the controllers
self.attach_ui_connections()
def init_ui(self):
w = self.win
w.txtLogFilename.setText(self.filename.split('/')[-1]) # just the file (no path)
def attach_ui_connections(self):
w = self.win
if __name__ == '__main__':
LogViewer.main()
GUI is available here. Any suggestions appreciated!
I checked your ui file and plotWidget has no layout at all. Try to give it one, I suggest a grid layout.
Then, after parenting the canvas
self.setParent(parent)
try adding it to the parent layout:
parent.layout().addWidget(self)

Multiple embedded matplotlib canvases in pyqt change size on mouse over

I'm trying to embed multiple matplotlib plots in a multi-column layout in a PyQt GUI. At first sight I succeed in setting up the layout as wanted but when moving the mouse over any of the canvasses they change size and 'flicker'. When pressing the zoom button on the toolbar this becomes more pronounced.
For each matplotlib canvas I have connected a toolbar. If I do not connect the toolbars the problem does not appear. I have tried arranging the toolbars and canvases several ways - with a QGridLayout or nested QVBoxLayouts and QHBoxLayouts. Either way the problem appears if there are plots along side each other. If I put all plots in a single column it does not.
I have tried this in Python 3.6 in Windows (Anaconda 5.0.1) with PyQt4 and Python 3.5.2 in Linux (KDE Neon 64 bit) and with both PyQt4 and PyQt5 (v. 5.7.1), matplotlib 1.5.1 but with the same result. I have also tried using add_axes instead of add_subplot. Can someone help me understand what is causing this or find some kind of workaround? I can not use matplotlib subplots.
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
figure1 = Figure()
figure2 = Figure()
figure3 = Figure()
figure4 = Figure()
canvas1 = FigureCanvas(figure1)
canvas2 = FigureCanvas(figure2)
canvas3 = FigureCanvas(figure3)
canvas4 = FigureCanvas(figure4)
ax1 = figure1.add_subplot(111)
ax2 = figure2.add_subplot(111)
ax3 = figure3.add_subplot(111)
ax4 = figure4.add_subplot(111)
toolbar1 = NavigationToolbar(canvas1, self)
toolbar2 = NavigationToolbar(canvas2, self)
toolbar3 = NavigationToolbar(canvas3, self)
toolbar4 = NavigationToolbar(canvas4, self)
mainLayout = QtWidgets.QGridLayout()
mainLayout.addWidget(toolbar1,0,0)
mainLayout.addWidget(toolbar2,0,1)
mainLayout.addWidget(toolbar3,2,0)
mainLayout.addWidget(toolbar4,2,1)
mainLayout.addWidget(canvas1,1,0)
mainLayout.addWidget(canvas2,1,1)
mainLayout.addWidget(canvas3,3,0)
mainLayout.addWidget(canvas4,3,1)
self.setLayout(mainLayout)
self.setWindowTitle("Flow Layout")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWin = Window()
mainWin.show()
sys.exit(app.exec_())
I had a similar issue and I solved it by:
toolbar1.setMinimumWidth(canvas1.width())
toolbar2.setMinimumWidth(canvas2.width())
toolbar3.setMinimumWidth(canvas3.width())
toolbar4.setMinimumWidth(canvas4.width())
The problem is when the toolbar becomes wider than the canvas.

Can I embed plotly graphs (offline) in my PyQt4 application?

I know that plotly renders into HTML and can be embedded in web-like environments. I wonder if one can do that inside an HTML window in a PyQt application? Specifically I'd like to know if that works offline, having no internet connection.
EDIT:
This is an excerpt how I finally embedded graphs using matplotlib:
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg \
import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg \
import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Contour(QtGui.QFrame):
def __init__(self, parent=None):
super(Contour, self).__init__(parent)
self.parent = parent
# a figure instance to plot on
self.figure = plt.figure(figsize=(20, 30))
r, g, b = 100./255., 100./255., 100./255.
self.figure.patch.set_facecolor(color=(r, g, b))
# 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)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
And then in another function:
# create an axis
ax1 = self.figure.add_subplot(211, frame_on=False)
ax2 = self.figure.add_subplot(212, frame_on=False)
# plot data
r, g, b = 39./255., 40./255., 34./255.
ax1.plot(x, y, ls='o', color=(r, g, b), linewidth=3)
ax1.plot(coo[0], coo[1], 'go', zorder=20) # leading edge
ax1.plot(xg, yg, 'mo', zorder=30) # leading edge
ax1.plot(xr, yr, 'yo', zorder=30) # curvature circle center
ax1.add_patch(circle)
ax1.set_title('Contour', fontsize=14)
ax1.set_xlim(-10.0, 110.0)
# ax1.set_ylim(-10.0, 14.0)
r, g, b = 249./255., 38./255., 114./255.
ax1.fill(x, y, color=(r, g, b))
ax1.set_aspect('equal')
ax2.plot(coo[0], gradient, 'go-', linewidth=3)
ax2.set_title('Gradient', fontsize=14)
ax2.set_xlim(-10.0, 110.0)
You can use QWebEngineView from the QWebEngineWidgets module (I used PyQt5 here but I guess it'll also work for PyQt4). You create html code using plotly.offline.plot and set this as the html text for your instance of QWebEngineView. If you specify output_type='div' it will give you directly a string. I do not know why, but in my case it only worked with include_plotlyjs='cdn', but in that case you need to be on the internet for it to work according to the plotly documentation. By doing it that way the plot stays interactive also in your PyQt application.
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QMainWindow
from plotly.graph_objects import Figure, Scatter
import plotly
import numpy as np
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
# some example data
x = np.arange(1000)
y = x**2
# create the plotly figure
fig = Figure(Scatter(x=x, y=y))
# we create html code of the figure
html = '<html><body>'
html += plotly.offline.plot(fig, output_type='div', include_plotlyjs='cdn')
html += '</body></html>'
# we create an instance of QWebEngineView and set the html code
plot_widget = QWebEngineView()
plot_widget.setHtml(html)
# set the QWebEngineView instance as main widget
self.setCentralWidget(plot_widget)
if __name__ == '__main__':
app = QApplication([])
window = MainWindow()
window.show()
app.exec_()
I once tried using the:
import plotly.offline as plt
.
.
.
plt.plot(fig, filename=testName + '__plot.html')
and then tried to generate a plot.. this gave me an HTML file which then I also tried putting on a QWebView as its URL property [just to see if it renders].
Here is the image for your reference:
Plotly is primarily developed to make graphing in a browser easy. I don't think it can be embedded into a UI framework like PyQT or Tkinter. Plotly has a enterprise version which works in our company network without a internet connection.
PyQtgraph or MatPlotLib may be an option for you if you really need to embedd graphs in PyQT.
Here's sample code to export graphs as png's and then embed png image into your PyQT application.
import matplotlib.pyplot as plt
plt.plot([1,2,3,4,5], [10,20,30])
plt.savefig('graphs.png')
import os,sys
from PyQt4 import QtGui
pic.setPixmap(QtGui.QPixmap("graphs.png"))
Not sure if this totally meets your need put given you have
import plotly.plotly as py
you can save the image and then
py.image.save_as({'data': data}, 'your_image_filename.png')
pic.setPixmap(QtGui.QPixmap("your_image_filename.png"))
Warning, I haven't tried this

error in adding matplotlib widget into pyqt4

I was trying to add a custom widget into qtdesginer using following code
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import PySide
from matplotlib.figure import Figure
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
FigureCanvas.__init__(self, self.fig)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MplWidget(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.canvas = MplCanvas()
self.vbl = QtGui.QVBoxLayout()
self.vbl.addWidget(self.canvas)
self.setLayout(self.vbl)
But i just give me an error of
TypeError: 'PySide.QtGui.QWidget.setSizePolicy' called with wrong argument types:
PySide.QtGui.QWidget.setSizePolicy(Policy, Policy)
Supported signatures:
PySide.QtGui.QWidget.setSizePolicy(PySide.QtGui.QSizePolicy)
PySide.QtGui.QWidget.setSizePolicy(PySide.QtGui.QSizePolicy.Policy, PySide.QtGui.QSizePolicy.Policy)
I am not exactly sure what caused the error, since i bascially followed this part http://packtlib.packtpub.com/library/9781847197900/ch06lvl1sec04
Any suggestions would be good,since i am new to this qt designer.
I've had good luck inheriting FigureCanvas with super(...).__init__() in my custom matplotlib Widgets rather than the BaseClass.__init__(self) method. Your widget worked for me with a few minor changes:
class MplCanvas(FigureCanvas):
def __init__(self):
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
super(MplCanvas, self).__init__(self.fig)
self.setSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)
self.updateGeometry()
Also... I agree with #tcaswell that you should probably pick either PyQt4 or PySide and avoid importing both :)

Categories