How to delete old plot on Matplotlib figure embedded in PyQt5? - matplotlib

I am using matplotlib figure embedded in pyqt5 to draw a frame taking the height and the width from line edit entries.It works well but when I change the values inside the line edits and click on the push button it will draw another frame over the previous one. I have tried plt.gcf().clear() and ax.clear and they did not work.
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
Picture of the app

Instead of clearing the axes you may update the line that is drawn with the new coordinates.
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
self.line, = self.ax.plot([])
self.ax.axis([0,100,0,100])
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.line.set_data(x,y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()

As mentioned in the comment above, you need to call self.ax.clear() every time you click the button. Otherwise you will be redrawing on the same plot. Not sure why your graph doesnt reset on clear(), but here is the code I ran which worked fine. Let me know if it works for you:
from PyQt5.QtWidgets import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Draw Frame")
self.setGeometry(100,100,680, 450)
# Creation of figure and canvas
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111)
self.ax.axis("off")
#self.toolbar = NavigationToolbar(self.canvas, self)
self.plot_widget = QWidget(self)
self.plot_widget.setGeometry(250, 10, 400, 400)
plot_box = QVBoxLayout()
plot_box.addWidget(self.canvas)
self.plot_widget.setLayout(plot_box)
self.label1=QLabel("Frame height",self)
self.label1.move(10,30)
self.label2 = QLabel("Frame height", self)
self.label2.move(10, 70)
self.lineEdit1=QLineEdit(self)
self.lineEdit1.move(100,30)
self.lineEdit1.setText("10")
self.lineEdit2 = QLineEdit(self)
self.lineEdit2.move(100, 70)
self.lineEdit2.setText("20")
self.button = QPushButton('Draw Frame', self)
self.button.clicked.connect(self.plot)
self.button.move(70, 350)
self.show()
def plot(self):
self.ax.clear()
if len(self.lineEdit1.text())!=0:
self.h=int(self.lineEdit1.text())
else:
self.h=0
if len(self.lineEdit2.text()) != 0:
self.w=int(self.lineEdit2.text())
else:
self.w=0
x = [0, 0, self.w, self.w]
y = [0, self.h, self.h, 0]
self.ax.plot(x, y)
self.canvas.draw()
if __name__=='__main__':
app = QApplication(sys.argv)
w = Window()
app.exec_()

Related

why matplotlib Button not work with pyqt5?

I now want to use the Custom Button function of matplotlib, refer to this document: https://matplotlib.org/stable/gallery/widgets/buttons.html, it works quite well.
But when I want to combine it with PyQt5, I first create a main window with a button in it. When the button is clicked, a plot will pop up, but the button in the plot loses its response.
code show as below:
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs):
self.l = l
self.freqs = freqs
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I want to know why?
In this question:
matplotlib event doesn't work when I use button clicked connect in pyqt5, I see that it seems to define a window myself , and then embed matplotlib, but I don't understand why
Is there a document that says we must do this?
I tried Macos, linux, windows, it works under macos, but the button doesn't respond under linux and windows.
I suspect it has something to do with QCoreApplication::exec: The event loop is already running, but I don't understand why the qt problem affects matplotlib. Is the signal of matplotlib registered to pyqt5?
Yes, Yes, You need not to a PlotEx, ref to Why aren't the matplotlib checkboxes working in pyQt5?
I understood that is because the button is local var, I need a more big scope.
the right code is :
import sys
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class Index:
ind = 0
def __init__(self, l, freqs, t):
self.l = l
self.freqs = freqs
self.t = t
def next(self, event):
self.ind += 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind % len(self.freqs)
ydata = np.sin(2 * np.pi * self.freqs[i] * self.t)
self.l.set_ydata(ydata)
plt.draw()
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'PyQt5 button - pythonspot.com'
self.left = 10
self.top = 10
self.width = 320
self.height = 200
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
button = QPushButton('PyQt5 button', self)
button.setToolTip('This is an example button')
button.move(100, 70)
button.clicked.connect(self.on_click)
self.show()
#pyqtSlot()
def on_click(self):
freqs = np.arange(2, 20, 3)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
t = np.arange(0.0, 1.0, 0.001)
s = np.sin(2 * np.pi * freqs[0] * t)
l, = ax.plot(t, s, lw=2)
callback = Index(l, freqs, t)
axprev = fig.add_axes([0.7, 0.05, 0.1, 0.075])
axnext = fig.add_axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.bnext = bnext
plt.bprev = bprev
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
The difference is add (and fix some bugs):
plt.bnext = bnext
plt.bprev = bprev

histplot in figurecanvas doesn't fit to axis

I used figurecanvas to show histplot on the GUI made by PyQt5.
I made Optionwindow to make the graph with the region I want.
the code wrote:
class OptionWindow(QDialog):
def __init__(self, parent): # 부모 window 설정
super(OptionWindow, self).__init__(parent)
uic.loadUi("graph tool.ui", self)
self.fig = plt.Figure()
self.canvas = FigureCanvas(self.fig)
self.QVlayout_graph.addWidget(self.canvas)
self.graph_button.clicked.connect(self.graph_draw)
self.save_button.clicked.connect(self.graph_save)
self.show()
def graph_draw(self):
self.fig.clf()
global bin_width, x_begin, x_end, y_begin, y_end
bin_width = float(self.edit_bin.text())
x_begin = float(self.edit_x_begin.text())
x_end = float(self.edit_x_end.text())
y_begin = float(self.edit_y_begin.text())
y_end = float(self.edit_y_end.text())
ax = self.fig.add_subplot(111)
sns.histplot(data = df['contour_area (mm2)'], binwidth=bin_width, ax=ax)
ax.set_xlabel('Pin-hole Area (${mm^2}$)', size = 13)
ax.set_ylabel('Count', size = 13)
ax.set_xlim([x_begin, x_end])
ax.set_ylim([y_begin, y_end])
self.fig.tight_layout()
self.canvas.draw()
and the graph is a little diffrent with the axis.
It's not fit correctly to the axis.
How can I fix it?
enter image description here

VTKWidget in Qt is not updated as expected

I would like to display a 3D-Animation in my Qt5-Gui. Everything works as expected, but unfortunately the scene is not getting updated when I don't interact with the vtkWidget. In other words: When I want to see the animation, I need to click continously with the mouse on the widget. I'd be greatful for any help.
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
After reading the paintEvent()-Method of this file, I managed to find out, that one needs to call the Render()-Method of the Interactor-object. So instead of self.ren.Render() one needs to call self.iren.Render(). Then everything works.
Complete example code:
import sys
import vtk
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import Qt
import numpy as np
from vtk.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
class mainWindow(Qt.QMainWindow):
def __init__(self, parent = None):
Qt.QMainWindow.__init__(self, parent)
self.frame = Qt.QFrame()
self.vl = Qt.QVBoxLayout()
self.button = QtWidgets.QPushButton("TestButton")
self.label = QtWidgets.QLabel("This is a label")
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
#Create Source
self.source = vtk.vtkCylinderSource()
self.source.SetCenter(0, 0, 0)
self.source.SetRadius(5.0)
#Create Mapper
self.mapper = vtk.vtkPolyDataMapper()
self.mapper.SetInputConnection(self.source.GetOutputPort())
#Create Actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(self.mapper)
#Create poke matrix for cylinder
self.pMatrix = vtk.vtkMatrix4x4()
self.vl.addWidget(self.vtkWidget)
self.vl.addWidget(self.button)
self.vl.addWidget(self.label)
self.ren = vtk.vtkRenderer()
self.ren.AddActor(self.actor)
self.renWin = self.vtkWidget.GetRenderWindow()
self.renWin.AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
#Settings
self.ren.SetBackground(0.2, 0.2, 0.2)
self.timeStep = 20 #ms
self.total_t = 0
#Inititalize Window, Interactor, Renderer, Layout
self.frame.setLayout(self.vl)
self.setCentralWidget(self.frame)
self.ren.ResetCamera()
self.show()
self.iren.Initialize()
# Create Timer
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timerCallback)
self.timer.start(self.timeStep)
def timerCallback(self, *args):
self.total_t += self.timeStep / 1000
#Rotate Cylinder
angle = 2 * np.pi * self.total_t
rotMatrix = np.array([[np.cos(angle), -np.sin(angle), 0],
[np.sin(angle), np.cos(angle), 0],
[0, 0, 1]])
for i in range(3):
for j in range(3):
self.pMatrix.SetElement(i, j, rotMatrix[i, j])
self.actor.PokeMatrix(self.pMatrix)
self.iren.Render() #NOT: self.ren.Render()
if __name__ == "__main__":
app = Qt.QApplication(sys.argv)
window = mainWindow()
sys.exit(app.exec_())
Thanks #cakelover, I was facing same issue but in C++, your solution helped me resolve it:
//PCLVisualizer pointer
pcl::visualization::PCLVisualizer::Ptr viewer_3D;
//Renderer method to update the visualizer
viewer_3D->getRenderWindow()->GetInteractor()->Render();

Include matplotlib in pyqt5 with hover labels

I have a plot from matplotlib for which I would like to display labels on the marker points when hover over with the mouse.
I found this very helpful working example on SO and I was trying to integrate the exact same plot into a pyqt5 application.
Unfortunately when having the plot in the application the hovering doesn't work anymore.
Here is a full working example based on the mentioned SO post:
import matplotlib.pyplot as plt
import scipy.spatial as spatial
import numpy as np
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import sys
pi = np.pi
cos = np.cos
def fmt(x, y):
return 'x: {x:0.2f}\ny: {y:0.2f}'.format(x=x, y=y)
class FollowDotCursor(object):
"""Display the x,y location of the nearest data point.
https://stackoverflow.com/a/4674445/190597 (Joe Kington)
https://stackoverflow.com/a/13306887/190597 (unutbu)
https://stackoverflow.com/a/15454427/190597 (unutbu)
"""
def __init__(self, ax, x, y, tolerance=5, formatter=fmt, offsets=(-20, 20)):
try:
x = np.asarray(x, dtype='float')
except (TypeError, ValueError):
x = np.asarray(mdates.date2num(x), dtype='float')
y = np.asarray(y, dtype='float')
mask = ~(np.isnan(x) | np.isnan(y))
x = x[mask]
y = y[mask]
self._points = np.column_stack((x, y))
self.offsets = offsets
y = y[np.abs(y-y.mean()) <= 3*y.std()]
self.scale = x.ptp()
self.scale = y.ptp() / self.scale if self.scale else 1
self.tree = spatial.cKDTree(self.scaled(self._points))
self.formatter = formatter
self.tolerance = tolerance
self.ax = ax
self.fig = ax.figure
self.ax.xaxis.set_label_position('top')
self.dot = ax.scatter(
[x.min()], [y.min()], s=130, color='green', alpha=0.7)
self.annotation = self.setup_annotation()
plt.connect('motion_notify_event', self)
def scaled(self, points):
points = np.asarray(points)
return points * (self.scale, 1)
def __call__(self, event):
ax = self.ax
# event.inaxes is always the current axis. If you use twinx, ax could be
# a different axis.
if event.inaxes == ax:
x, y = event.xdata, event.ydata
elif event.inaxes is None:
return
else:
inv = ax.transData.inverted()
x, y = inv.transform([(event.x, event.y)]).ravel()
annotation = self.annotation
x, y = self.snap(x, y)
annotation.xy = x, y
annotation.set_text(self.formatter(x, y))
self.dot.set_offsets((x, y))
bbox = ax.viewLim
event.canvas.draw()
def setup_annotation(self):
"""Draw and hide the annotation box."""
annotation = self.ax.annotate(
'', xy=(0, 0), ha = 'right',
xytext = self.offsets, textcoords = 'offset points', va = 'bottom',
bbox = dict(
boxstyle='round,pad=0.5', fc='yellow', alpha=0.75),
arrowprops = dict(
arrowstyle='->', connectionstyle='arc3,rad=0'))
return annotation
def snap(self, x, y):
"""Return the value in self.tree closest to x, y."""
dist, idx = self.tree.query(self.scaled((x, y)), k=1, p=1)
try:
return self._points[idx]
except IndexError:
# IndexError: index out of bounds
return self._points[0]
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.width = 1000
self.height = 800
self.setGeometry(0, 0, self.width, self.height)
canvas = self.get_canvas()
w = QWidget()
w.layout = QHBoxLayout()
w.layout.addWidget(canvas)
w.setLayout(w.layout)
self.setCentralWidget(w)
self.show()
def get_canvas(self):
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*pi, 10)
y = cos(x)
markerline, stemlines, baseline = ax.stem(x, y, '-.')
plt.setp(markerline, 'markerfacecolor', 'b')
plt.setp(baseline, 'color','r', 'linewidth', 2)
cursor = FollowDotCursor(ax, x, y, tolerance=20)
canvas = FigureCanvas(fig)
return canvas
app = QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
What would I have to do to make the labels also show when hovering over in the pyqt application?
The first problem may be that you don't keep a reference to the FollowDotCursor.
So to make sure the FollowDotCursor stays alive, you can make it a class variable
self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
instead of cursor = ....
Next make sure you instatiate the Cursor class after giving the figure a canvas.
canvas = FigureCanvas(fig)
self.cursor = FollowDotCursor(ax, x, y, tolerance=20)
Finally, keep a reference to the callback inside the FollowDotCursor and don't use plt.connect but the canvas itself:
self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self)

how to make matplotlib imshow update quickly?

I want to make imshow() function update (256*873)array quickly. set_array()'s speed is about 15 times a second. I hope it can be about 30 times a second. there is a demo which using imshow() and set_array().Anybody help?Thank you so much!
import wx
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import numpy as np
TIMER_ID = wx.NewId()
class CanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'SinWave', size=(600,600))
self.figure = Figure()
self.canvas = FigureCanvas(self, -1, self.figure)
self.axes = self.figure.add_subplot(111)
mapdata = np.random.rand(256, 873)
self.img_artist = self.axes.imshow(mapdata, origin='lower', interpolation='nearest', aspect='auto',extent=[0,873,0,256])
self.img_artist.set_clim(vmin=0, vmax=1)
self.timer = wx.Timer(self) # 创建定时器
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
self.timer.Start(100) # 设定时间间隔
self.cnt=0
def OnTimer(self, evt):
mapdata = np.random.rand(256, 873)
self.img_artist.set_data(mapdata)
self.canvas.draw()
self.cnt +=1
print self.cnt
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = CanvasFrame()
frame.Show()
app.MainLoop()