I am pretty new to PyQt5. I would like to have a button that, when clicked, generates and shows a plot. I cannot seem to find a way to use the QPushButton to call the PlotCanvas function successfully.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtWidgets, Qt
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import random
import numpy as np
import math
class GUI_Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
self.plot_button.clicked.connect(self.updater)
def updater(self):
PlotCanvas(self, inputparam=5)
print('Function has been called.')
The code above does not work (PlotCanvas doesn't plot) even though I receive the 'function has been called' printout.
However if I simply call PlotCanvas in initUI, it plots it as expected.
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
self.plot_button.clicked.connect(self.updater)
PlotCanvas(self, inputparam=5)
The above code plots the function as expected.
Furthermore, if I simply call the updater function without the pushbutton (shown below), the plot is shown.
def initUI(self):
self.plot_button = QtWidgets.QPushButton(self)
self.plot_button.setText('Plot')
self.plot_button.setToolTip('Plots the function')
self.plot_button.move(50,240)
#self.plot_button.clicked.connect(self.updater)
self.updater()
def updater(self):
PlotCanvas(self, inputparam=5)
print('Function has been called.')
I sense I do not know how 'self' works and by passing a clicked signal I am somehow messing things up. Any help/insight would be appreciated.
class PlotCanvas(FigureCanvas):
def __init__(self, parent=None, width=1.5, height=2, dpi=100, inputparam=0):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.LLA_Plotter(inputparam)
# This function performs calculations and outputs x,y data points to plot
ax = self.figure.add_subplot(111)
for i in range(0,90):
ax.scatter(x[i], y[i], color='b', s=0.6)
ax.set_title('testing plot')
plt.show()
Related
I am plotting some data in cartopy. I would like to be able to zoom in on a region of the map and have the latitude/longitude axes update to reflect the zoomed in region. Instead, they just dissapear altogether when I zoom in. How do I fix this?
Here is my code for generating the axes
plt.figure()
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.add_feature(cartopy.feature.LAND, edgecolor='black')
gl = ax.gridlines(crs=cartopy.crs.PlateCarree(), draw_labels=True,
linewidth=2, color='gray', alpha=0.5, linestyle='--')
# plot some stuff here
It is possible to update the cartopy gridliners in interactive mode, but you need to subclass the Navigation toolbar.
In this example below I have used a PySide/QT5 example code that allows me to substitute a subclassed toolbar, then merged in the gridliner example code. The overloaded toolbar callbacks recreate the gridlines everytime zoom/pan/home is used.
I used python3.8, matplotlib-3.4.2, cartopy-0.20
import sys
from PySide2 import QtWidgets
from PySide2.QtWidgets import QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import cartopy.crs as ccrs
class CustomNavigationToolbar(NavigationToolbar):
toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
def __init__(self, canvas, parent, coordinates=True, func_recreate_gridlines=None):
print('CustomNavigationToolbar::__init__')
super(CustomNavigationToolbar, self).__init__(canvas, parent, coordinates)
self.func_recreate_gridlines = func_recreate_gridlines
def home(self, *args):
print('CustomNavigationToolbar::home')
super(CustomNavigationToolbar, self).home(*args)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_pan(self, event):
print('CustomNavigationToolbar::release_pan')
super(CustomNavigationToolbar, self).release_pan(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_zoom(self, event):
print('CustomNavigationToolbar::release_zoom')
super(CustomNavigationToolbar, self).release_zoom(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
print('ApplicationWindow::__init__')
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.layout = QVBoxLayout(self._main)
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.toolbar = CustomNavigationToolbar(self.canvas, self,
coordinates=True,
func_recreate_gridlines=self.recreate_gridlines)
self.layout.addWidget(self.canvas)
self.addToolBar(self.toolbar)
# figure setup taken from gridlines example at
# https://scitools.org.uk/cartopy/docs/latest/matplotlib/gridliner.html
projection = ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=70.0)
self.ax = self.canvas.figure.add_subplot(1, 1, 1, projection=projection)
self.ax.set_extent([-6, 3, 48, 58], crs=ccrs.PlateCarree())
self.ax.coastlines(resolution='10m')
self._gl = None
self.recreate_gridlines()
def recreate_gridlines(self):
print('ApplicationWindow::recreate_gridlines')
print(' remove old gridliner artists')
if self._gl is not None:
for artist_coll in [self._gl.xline_artists, self._gl.yline_artists, self._gl.xlabel_artists, self._gl.ylabel_artists]:
for a in artist_coll:
a.remove()
self.ax._gridliners = []
print(' self.ax.gridlines()')
self._gl = self.ax.gridlines(crs=ccrs.PlateCarree(),
draw_labels=True, dms=True, x_inline=False, y_inline=False)
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
Starting with the working Matplotlib animation code shown below, my goal is to embed this animation (which is just a circle moving across the screen) within a PyQT4 GUI.
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
from matplotlib import animation
fig,ax = plt.subplots()
ax.set_aspect('equal','box')
circle = Circle((0,0), 1.0)
ax.add_artist(circle)
ax.set_xlim([0,10])
ax.set_ylim([-2,2])
def animate(i):
circle.center=(i,0)
return circle,
anim = animation.FuncAnimation(fig,animate,frames=10,interval=100,repeat=False,blit=True)
plt.show()
I am able to accomplish this using the following code, but there is one hitch: I cannot get blitting to work.
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5,4),dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal','box')
self.circle = Circle((0,0), 1.0)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0,10])
self.ax.set_ylim([-2,2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def animate(self):
self.anim = animation.FuncAnimation(self.fig,self.animate_loop,frames=10,interval=100,repeat=False,blit=False)
self.canvas.draw()
def animate_loop(self,i):
self.circle.center=(i,0)
return self.circle,
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
When I set blit=True, after pressing the Animate button I get the following error:
a.figure.canvas.restore_region(bg_cache[a])
KeyError: matplotlib.axes._subplots.AxesSubplot object at 0x00000000095F1D30
In searching this error, I find many posts about how blitting does not work on Macs, but I am using Windows 7. I have tried replacing self.canvas.draw() with self.canvas.update(), but this does not work.
After looking at the source code of the animation module, I realized that there is an error in the Animation class (the dictionary bg_cache is empty, when it is accessed for the first time with blitting switched on).
This is fixed in the git version of matplotlib; however, in the most recent stable version 1.5.1, the bug is still present. You can either fix the bug in the matplotlib code itself or you can make a subclass to FuncAnimation. I chose that way, because it should still work after updating matplotlib.
from matplotlib import animation
class MyFuncAnimation(animation.FuncAnimation):
"""
Unfortunately, it seems that the _blit_clear method of the Animation
class contains an error in several matplotlib verions
That's why, I fork it here and insert the latest git version of
the function.
"""
def _blit_clear(self, artists, bg_cache):
# Get a list of the axes that need clearing from the artists that
# have been drawn. Grab the appropriate saved background from the
# cache and restore.
axes = set(a.axes for a in artists)
for a in axes:
if a in bg_cache: # this is the previously missing line
a.figure.canvas.restore_region(bg_cache[a])
Then, simpy use MyFuncAnimation instead of animation.FuncAnimation.
Took me a while to figure it out, but I hope it helps anybody.
After some time I managed to recreate the animation by using the underlying functions directly and not using the animation wrapper:
import sys
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Circle
from matplotlib import animation
from time import sleep
class Window(QtGui.QDialog): #or QtGui.QWidget ???
def __init__(self):
super(Window, self).__init__()
self.fig = Figure(figsize=(5, 4), dpi=100)
self.canvas = FigureCanvas(self.fig)
self.ax = self.fig.add_subplot(111) # create an axis
self.ax.hold(False) # discards the old graph
self.ax.set_aspect('equal', 'box')
self.circle = Circle((0,0), 1.0, animated=True)
self.ax.add_artist(self.circle)
self.ax.set_xlim([0, 10])
self.ax.set_ylim([-2, 2])
self.button = QtGui.QPushButton('Animate')
self.button.clicked.connect(self.animate)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
self.canvas.draw()
self.ax_background = self.canvas.copy_from_bbox(self.ax.bbox)
def animate(self):
self.animate_loop(0)
def animate_loop(self,begin):
for i in range(begin,10):
self.canvas.restore_region(self.ax_background)
self.circle.center=(i,0)
self.ax.draw_artist(self.circle)
self.canvas.blit(self.ax.bbox)
self.canvas.flush_events()
sleep(0.1)
def main():
app = QtGui.QApplication(sys.argv)
ex = Window()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Maybe this will be of use to you.
Hello im trying to add a custom graph to the pyqt interface I have. Its not showing any data, but the placeholder matplotlib graph is showing. Any help?! Also If i plot just the graph data without putting it into PYQT, it shows up. Thanks!
class MyMplCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
#self.axes = fig.add_subplot(111)
# We want the axes cleared every time plot() is called
self.axes.hold(False)
self.compute_initial_figure()
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
def compute_initial_figure(self):
pass
class MyStaticMplCanvas(MyMplCanvas):
def mystatic(self, parent=None):
super(MyStaticMplCanvas, self).__init__(parent)
rate,data = read('test.wav') # reading
subplot(411)
self.plot(range(len(data)),data)
subplot(412)
self.specgram(data, NFFT=128, noverlap=0) # small window
subplot(413)
self.specgram(data, NFFT=512, noverlap=0)
subplot(414)
self.specgram(data, NFFT=1024, noverlap=0) # big window
self.show()
class ApplicationWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
l = QtGui.QVBoxLayout(self.main_widget)
sc = MyStaticMplCanvas(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(sc)
In the code sample you provided, there is no call to the mystatic method that you use to plot your data. Therefore, nothing is plotted on your figure.
Moreover, It seems you are using the pyplot interface for plotting your data by calling directly subplot and plot for example in mystatic. When embedding a mpl figure in an application, it is recommended from the mpl documentation to stick to the object oriented API.
I've produced a minimal working example from the code you've provided that follows the guidelines provided in the aforementioned documentation. Hope it helps.
from PyQt4 import QtGui
import sys
import numpy as np
import matplotlib as mpl
mpl.use('Qt4Agg')
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class MyMplCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = mpl.figure.Figure(figsize=(width, height), dpi=dpi)
fig.set_tight_layout('tight')
super(MyMplCanvas, self).__init__(fig)
class MyStaticMplCanvas(MyMplCanvas):
def mystatic(self, parent=None):
x, y = np.random.rand(50), np.random.rand(50)
ax1 = self.figure.add_subplot(411)
ax1.plot(x,y, '.')
ax2 = self.figure.add_subplot(412)
ax2.plot(x,y, '.')
ax3 = self.figure.add_subplot(413)
ax3.plot(x,y, '.')
ax4 = self.figure.add_subplot(414)
ax4.plot(x,y, '.')
if __name__ == '__main__' :
app = QtGui.QApplication(sys.argv)
w = MyStaticMplCanvas(width=5, height=6, dpi=100)
w.mystatic()
w.show()
sys.exit(app.exec_())
which results in:
I try to embed matplotlib into PyQT GUI made in Qt designer. In .ui on a widget tab I defined:
GraphWidget, that is a QWidget
and
Graph1Layout, that is a QVBoxLayout.
The code is below. I took some parts of the code from other application written by other person (class ParentCanvas and class PlotCanvas) so I do not completely understand what is going on there, but as far as I googled, that is the almost standard way to include matplotlib widget in GUI. I shortened my existing code and existing GUI to do a little example.
What is intended: I want to plot some points whenever I click run and delete the graph when I click stop (I tried just to clear the points from graph but without success).
Is this correct implementation?
Maybe somebody can make it more simple or better?
Is there a better way to delete widget?
How to delete only points on the plot?
I am a beginner in OOP and python in general. Any other comments for improving the code are welcome.
from __future__ import division
import sys,os
from PyQt4 import QtGui, QtCore, uic # Used to create and modify PyQt4 Objects.
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas # Used in creating the plots and graphs.
from matplotlib.figure import Figure
class ParentCanvas(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
fig.set_edgecolor('w')
fig.set_facecolor('w')
self.axes = fig.add_subplot(111)
self.axes.hold(False)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class PlotCanvas(ParentCanvas):
""" Creates a matplotlib canvas QWidget inheriting the features set out in ParentCanvas."""
def compute_initial_figure(self):
self.axes.set_xbound(lower=0, upper=1)
self.axes.set_ybound(lower=0, upper=1)
self.axes.set_ylabel('Best Fitness Function')
self.axes.set_xlabel('Population')
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
uic.loadUi('Test.ui', self)
self.SignalsAndSlots()
self.firstrun = True
self.iter=0
def SignalsAndSlots(self): # Function setting up the signals and slots.
self.btnStopOpt.clicked.connect(self.clickStop)
self.btnRun.clicked.connect(self.clickRun)
def clickStop(self): # Function that runs when the "Stop" button is clicked.
self.firstrun = True
self.iter =0
self.gb.setParent(None)
def clickRun(self): # Function that runs when the "Run Optimization" button is clicked.
if self.firstrun == True :
#graph setup
self.gb = PlotCanvas(self.Graph1Widget, width=10, height=7, dpi=70)
self.gb.axes.set_ylabel('Objective Value')
self.gb.axes.set_xlabel('Generation')
self.Graph1Layout.addWidget(self.gb)
self.firstrun = False
self.UpdateGraph(self.iter)
self.iter+=1
def UpdateGraph(self, iter): # Function that creates a graph
best = [0,0,0,3,5,9,12,30]
average = [0,0,0,1,2,3,14,20]
t = range(iter+1) # Will be used to store the generation numbers.
s = best[:iter+1] # Will be used to store the global best values.
self.gb.axes.hold(True) # Holds the graph so we can plot the global best and average scores as two separate scatter plots on to one graph.
self.gb.axes.scatter(t, s, c='b', label='Best objective value')
if iter == 0: # In the first generation we need to make a legend for the graph, this only need to be made once.
self.gb.axes.legend(loc='upper center', bbox_to_anchor=(0.5, 1.09), fancybox=True, shadow=True, ncol=5) # Legend content is determined by the labels above. Location is determined by bbox_to_anchor.
self.gb.axes.autoscale_view(True,False,True)
self.gb.draw()
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Main(None) # instantiation
app.setActiveWindow(window)
window.show() # show window
sys.exit(app.exec_()) # Exit from Python
I am trying to draw graphs with the result of a thread using matplotlib. First I launch the thread using a pyqt button, all is ok. But the second time I press the button, subplots crash because I suppose tuples can't be modified. This is my simplfied code that you can try and see by yourself:
from PyQt4 import QtGui, QtCore
import matplotlib.pyplot as plt
import numpy as np
from PyQt4.Qt import *
import sys
class thread(QThread):
def __init__(self, parent=None):
QThread.__init__(self, parent)
def __del__(self):
self.wait()
def render(self):
self.start()
def run(self):
data = [(1, 10), (3, 100), (4, 1000), (5, 100000)]
data_in_array = np.array(data)
transposed = data_in_array.T
x, y = transposed
fig, ax = plt.subplots(1,1)
ax.plot(x, y, 'ro')
ax.plot(x, y, 'b-')
plt.show()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.button)
self.thread=thread()
def handleButton(self):
self.thread.render()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Please, how can I solve it to avoid crash and uses several times my button?
Thanks
This is not the right way to mix your Qt application and Matplotlib (which uses Qt in its backend).
See https://matplotlib.org/gallery/user_interfaces/embedding_in_qt_sgskip.html for a better explanation than I can provide.
I got the same problem, was searching solution.
By adding these 3 lines solved the issue. Please add these 3 lines before importing pyplot
import matplotlib as mpl
mpl.rcParams['backend'] = "qt4agg"
mpl.rcParams['backend.qt4'] = "PySide"