I am new to PyQt4 and real time plots. I am trying to plot some random real time data on PyQt4 application using matplotlib and Python2.7 . My code looks like this:
#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
import time
class mplCanvas(FigureCanvas):
def __init__(self, parent=None):
self.fig = plt.figure(1)
self.ax = self.fig.add_subplot(111)
self.ax.grid(True)
self.manager = plt.get_current_fig_manager()
super(mplCanvas, self).__init__(self.fig)
self.setParent(parent)
self.init_figure()
class CustomFigCanvas(mplCanvas):
def __init__(self, *args, **kwargs):
mplCanvas.__init__(self, *args, **kwargs)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updateFigure)
def init_figure(self):
xaxis = np.arange(0, 100, 1)
yaxis = np.array([0]*100)
self.ax.set_title("Realtime Waveform Plot")
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Amplitude")
self.ax.axis([0, 100, -1.5, 1.5])
self.line1 = self.ax.plot(xaxis, yaxis, '-')
self.values = []
def addData(self):
self.values.append(random.random() * 2 - 1)
def updateFigure(self):
self.addData()
CurrentXAxis=np.arange(len(self.values)-100, len(self.values), 1)
self.line1[0].set_data(CurrentXAxis, np.array(self.values[-100:]))
self.ax.axis([CurrentXAxis.min(), CurrentXAxis.max(), -1.5, 1.5])
self.manager.canvas.draw()
And in the main application I call graph = CustomFigCanvas()
But all it does is it prints the plot with a straight line at 0 and the graph does not update at all. I cannot figure out what am I doing wrong. Why my plot is not updating? I was trying several options and still have the same result. I tried to do QThread to emit samples of data but it still did not work. All I get is a straight line. Do you have any suggestions? Any tips would be much appreciated. Thank you.
The main problem is that you set up a QTimer but never start it. So the updating function will never be called. What you need to do is something like
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updateFigure)
self.timer.start(1000) # 1000 milliseconds
Another problem is then how you create the data to update. The way it is in the code produces arrays of different length for the x and y coordinates, leading to errors.
Here is a complete running example where I also got rid of the unnecessary double subclassing.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt4 import QtGui
from PyQt4 import QtCore
import sys
class CustomFigCanvas(FigureCanvas):
def __init__(self, parent=None, *args, **kwargs):
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111)
self.ax.grid(True)
super(CustomFigCanvas, self).__init__(self.fig)
self.setParent(parent)
self.init_figure()
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updateFigure)
self.timer.start(1000)
def init_figure(self):
xaxis = np.arange(0, 100, 1)
yaxis = np.array([0]*100)
self.ax.set_title("Realtime Waveform Plot")
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Amplitude")
self.ax.axis([0, 100, -1.5, 1.5])
self.line1 = self.ax.plot(xaxis, yaxis, '-')
self.values = []
self.fig.tight_layout()
def addData(self):
self.values.append(random.random() * 2 - 1)
def updateFigure(self):
self.addData()
CurrentXAxis=np.arange(len(self.values))
self.line1[0].set_data(CurrentXAxis, np.array(self.values))
self.ax.axis([CurrentXAxis.min()-0.001, CurrentXAxis.max()+0.001, -1.5, 1.5])
self.fig.canvas.draw()
class App(QtGui.QMainWindow):
def __init__(self, parent=None):
super(App, self).__init__(parent)
self.tab = QtGui.QWidget()
self.setCentralWidget(self.tab)
self.tablayout = QtGui.QVBoxLayout(self.tab)
self.canvas = CustomFigCanvas()
self.tablayout.addWidget(self.canvas)
app = QtGui.QApplication(sys.argv)
thisapp = App()
thisapp.show()
app.exec_()
In your code you had the following errors:
You never started the timer, you should use: {your timer}.start({period in ms})
Arrays have different sizes.
Change self.manager.canvas.draw() to self.draw()
init_figure was never declared in mplCanvas
import sys
from PyQt4 import QtGui
import numpy as np
import matplotlib
matplotlib.use("Qt4Agg")
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt4 import QtCore
class mplCanvas(FigureCanvas):
def __init__(self, parent=None):
self.fig = plt.figure(1)
self.ax = self.fig.add_subplot(111)
self.ax.grid(True)
super(mplCanvas, self).__init__(figure=self.fig)
self.setParent(parent)
self.init_figure()
def init_figure(self):
pass
class CustomFigCanvas(mplCanvas):
def __init__(self, *args, **kwargs):
mplCanvas.__init__(self, *args, **kwargs)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.updateFigure)
self.timer.start(100)
def init_figure(self):
xaxis = np.arange(0, 100, 1)
self.values = [0]*100
yaxis = np.array(self.values)
self.ax.set_title("Realtime Waveform Plot")
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Amplitude")
self.ax.axis([0, 100, -1.5, 1.5])
self.line1 = self.ax.plot(xaxis, yaxis, '-')
def addData(self):
self.values.append(random.random() * 2 - 1)
def updateFigure(self):
self.addData()
CurrentXAxis = np.arange(len(self.values)-100, len(self.values), 1)
self.line1[0].set_data(CurrentXAxis, np.array(self.values[-100:]))
self.ax.axis([CurrentXAxis.min(), CurrentXAxis.max(), -1.5, 1.5])
self.draw()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main_widget = QtGui.QWidget()
l = QtGui.QVBoxLayout(main_widget)
graph = CustomFigCanvas(main_widget)
l.addWidget(graph)
main_widget.show()
sys.exit(app.exec_())
Screenshot:
Related
I am working on GUI where I have tab system with graphs. I want that if a user clicks (or puts cursor) at any point in the graph, it shows the exact x and y values in that point like that:
I know that in usual matplotlib it is easy to implement; however I do not know how to do that in PyQt5.
My tabs system and canvas look like that:
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import QDialog
from PyQt5.QtWidgets import QApplication, QWidget,QVBoxLayout,QTabWidget
import sys
import matplotlib.pyplot as plt
def mouse_move(event):
x, y = event.xdata, event.ydata
print(x, y)
plt.connect('motion_notify_event', mouse_move)
class Canvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=5, dpi=80):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
x = ['22-02 11:16:15', '22-02 15:31:54', '22-02 15:32:30',
'22-02 15:32:45', '22-02 15:33:57', '22-02 15:34:13',
'22-02 15:34:46']
y = [1, 4, 3, 4, 8, 9, 2]
self.figure.tight_layout()
ax = self.figure.add_subplot(111)
ax.plot(x, y)
class MainWindow(QDialog):
def __init__(self):
super().__init__()
self.top = 255
self.left = 150
self.setGeometry(self.left, self.top, 900, 900)
self.Mainlayout = QVBoxLayout(self)
self.tabs = QTabWidget()
self.graphUP = QWidget()
self.graphUP.layout = QVBoxLayout( self.graphUP)
self.graphUP.layout.addWidget(Canvas())
self.tabs.setFixedHeight(800)
self.tabs.setFixedWidth(800)
self.tabs.addTab(self.graphUP, "Graph1")
self.Mainlayout.addWidget(self.tabs)
self.show()
if __name__ == '__main__':
App = QApplication(sys.argv)
window = MainWindow()
sys.exit(App.exec())
import this module:
import mplcursors as mpl
and add : mpl.cursor(hover=True)
in your def plot() function.
I want to generate a scatterplot (up to half a million points) and on top of that, add different statistics (e.g. Q1, median, Q3). The idea is to add/delete those statistics without replotting the scatterplot in order to speed up the process. So far I can add plots independently on the figure but I can't delete a specific plot.
When I uncheck the checkbox, I get the following error:
AttributeError: 'Graphics' object has no attribute 'vline1'
I understand that when I create the plot, I need to store/return the plot in order to call it later when I want to delete it but I don't know how to do that.
Here my current code:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class Mainwindow(QMainWindow):
def __init__(self, parent=None):
super(Mainwindow, self).__init__(parent)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.gridLayout = QGridLayout(centralWidget)
self.gridLayout.addWidget(self.canvas)
self.btn_plot = QCheckBox("Plot")
self.btn_line = QCheckBox("Line")
self.gridLayout.addWidget(self.btn_plot, 1,0,1,1)
self.gridLayout.addWidget(self.btn_line, 2,0,1,1)
self.btn_plot.clicked.connect(self.btnPlot)
self.btn_line.clicked.connect(self.btnLine)
def btnPlot(self):
self.checked = self.btn_plot.isChecked()
self.Graphics = Graphics('plot', self.checked, self.axes)
def btnLine(self):
self.checked = self.btn_line.isChecked()
self.Graphics = Graphics('line', self.checked, self.axes)
class Graphics:
def __init__(self, typeGraph, checked, axes):
self.typeGraph = typeGraph
self.checked = checked
self.axes = axes
if self.typeGraph == 'plot': self.drawPlot()
if self.typeGraph == 'line': self.drawLine()
def drawPlot(self):
if self.checked == True:
self.plot = self.axes.plot([10,20,30], [5,10,2], 'o')
else:
self.plot.remove()
self.axes.figure.canvas.draw()
def drawLine(self):
if self.checked == True:
self.vline1 = self.axes.axvline(x=15, linestyle="dashed", color="#595959")
self.vline2 = self.axes.axvline(x=25, linestyle="dashed", color="#595959")
else:
self.vline1.remove()
self.vline2.remove()
self.axes.figure.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
prog = Mainwindow()
prog.show()
sys.exit(app.exec_())
Problem is be because when you click it then it creates always new Graphics (in btnPlot/btnLine) which doesn't have previous values - plot, vline1, vline2. You have to create Graphics only once and later run only drawPlot(checked), drawLine(checked) to add or remove item.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class Mainwindow(QMainWindow):
def __init__(self, parent=None):
super(Mainwindow, self).__init__(parent)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.gridLayout = QGridLayout(centralWidget)
self.gridLayout.addWidget(self.canvas)
self.btn_plot = QCheckBox("Plot")
self.btn_line = QCheckBox("Line")
self.gridLayout.addWidget(self.btn_plot, 1,0,1,1)
self.gridLayout.addWidget(self.btn_line, 2,0,1,1)
self.btn_plot.clicked.connect(self.btnPlot)
self.btn_line.clicked.connect(self.btnLine)
# create only once
self.Graphics = Graphics(self.axes)
def btnPlot(self):
# add or remove
self.Graphics.drawPlot(self.btn_plot.isChecked())
def btnLine(self):
# add or remove
self.Graphics.drawLine(self.btn_line.isChecked())
class Graphics:
def __init__(self, axes):
self.axes = axes
# create at start with default values (but frankly, now I don't need it)
self.plot = None
self.vline1 = None
self.vline2 = None
def drawPlot(self, checked):
if checked:
self.plot = self.axes.plot([10,20,30], [5,10,2], 'o')
else:
for item in self.plot:
item.remove()
self.axes.figure.canvas.draw()
def drawLine(self, checked):
if checked:
self.vline1 = self.axes.axvline(x=15, linestyle="dashed", color="#595959")
self.vline2 = self.axes.axvline(x=25, linestyle="dashed", color="#595959")
else:
self.vline1.remove()
self.vline2.remove()
self.axes.figure.canvas.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
prog = Mainwindow()
prog.show()
sys.exit(app.exec())
I want to add a second animated graph to the GUI holding my first animated graph with both graphs animating at the same time, but I'm not sure how.
Here is my code :
import sys
import numpy as np
from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
from matplotlib import animation
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
self.fig = Figure(figsize=(5, 3))
self.canvas = FigureCanvas(self.fig)
layout.addWidget(self.canvas)
self.addToolBar(NavigationToolbar(self.canvas, self))
self.setup()
def setup(self):
self.ax = self.fig.subplots()
self.ax.set_aspect('equal')
self.ax.grid(True, linestyle = '-', color = '0.10')
self.ax.set_xlim([-15, 15])
self.ax.set_ylim([-15, 15])
self.scat = self.ax.scatter([], [], c=(0.9, 0.1, 0.5), zorder=3)
self.scat.set_alpha(0.8)
self.anim = animation.FuncAnimation(self.fig, self.update,
frames = 720, interval = 10)
def update(self, i):
self.scat.set_offsets(([np.cos(np.radians(i))*7.5, np.sin(np.radians(i))*7.5], [0,0]))
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
And here is some sample code which has two graphs inside the same window (like how I want to)
Here they use an _update_canvas function for the animated graph and the other graph (which is just a static graph) they plot it in the application window class.
I'm using an update plot function to animate my graph, do I need a second update plot function? How?
Sample code:
import sys
import time
import numpy as np
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
if is_pyqt5():
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
static_canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(static_canvas)
self.addToolBar(NavigationToolbar(static_canvas, self))
dynamic_canvas = FigureCanvas(Figure(figsize=(5, 3)))
layout.addWidget(dynamic_canvas)
self.addToolBar(QtCore.Qt.BottomToolBarArea,
NavigationToolbar(dynamic_canvas, self))
self._static_ax = static_canvas.figure.subplots()
t = np.linspace(0, 10, 501)
self._static_ax.plot(t, np.tan(t), ".")
self._dynamic_ax = dynamic_canvas.figure.subplots()
self._timer = dynamic_canvas.new_timer(
100, [(self._update_canvas, (), {})])
self._timer.start()
def _update_canvas(self):
self._dynamic_ax.clear()
t = np.linspace(0, 10, 101)
# Shift the sinusoid as a function of time.
self._dynamic_ax.plot(t, np.sin(t + time.time()))
self._dynamic_ax.figure.canvas.draw()
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
To embed multiple animated graphs, you need to create multiple plot objects(Figure and FigureCanvas) then add each object to the QVBoxLayout. If you wanted to display it horizontally, you can use a QHBoxLayout. Each plot object will have its own subplot, grid, and data. To update each plot's data, you will need each plot to have its individual plot update function where you can pass this to the animation.FuncAnimation handler. So in your case, to have two animated graphs, you will need two update plot functions.
from matplotlib.figure import Figure
from matplotlib import animation
import numpy as np
import sys, matplotlib
from PyQt5 import QtWidgets, QtCore
from matplotlib.backends.backend_qt5agg import (FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
# Configure figure 1
self.fig1 = Figure(figsize=(5, 3))
self.canvas1 = FigureCanvas(self.fig1)
# Configure figure 2
self.fig2 = Figure(figsize=(5, 3))
self.canvas2 = FigureCanvas(self.fig2)
layout.addWidget(self.canvas1)
layout.addWidget(self.canvas2)
self.addToolBar(NavigationToolbar(self.canvas1, self))
self.addToolBar(QtCore.Qt.BottomToolBarArea, NavigationToolbar(self.canvas2, self))
self.setup()
def setup(self):
# Plot 1 (top)
self.ax1 = self.fig1.subplots()
self.ax1.set_aspect('equal')
self.ax1.grid(True, linestyle = '-', color = '0.10')
self.ax1.set_xlim([-15, 15])
self.ax1.set_ylim([-15, 15])
# Plot 2 (bottom)
self.ax2 = self.fig2.subplots()
self.ax2.set_aspect('equal')
self.ax2.grid(True, linestyle = '-', color = '0.10')
self.ax2.set_xlim([-15, 15])
self.ax2.set_ylim([-15, 15])
self.scat1 = self.ax1.scatter([], [], c=(0.9, 0.1, 0.5), zorder=3)
self.scat1.set_alpha(0.8)
self.scat2 = self.ax2.scatter([], [], c=(0.9, 0.1, 0.5), zorder=3)
self.scat2.set_alpha(0.8)
self.anim1 = animation.FuncAnimation(self.fig1, self.update1,frames = 720, interval = 10)
self.anim2 = animation.FuncAnimation(self.fig2, self.update2,frames = 720, interval = 10)
# Update data for plot 1
def update1(self, i):
self.scat1.set_offsets(([np.cos(np.radians(i))*7.5, np.sin(np.radians(i))*7.5], [0,0]))
# Update data for plot 2
def update2(self, i):
self.scat2.set_offsets(([np.cos(np.radians(i))*7.5, np.sin(np.radians(i))*7.5], [0,0]))
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
Is there a specific reason you need two separate canvas and two separate figures? If not, then I agree with ImportanceOfBeingErnest's comment and that you should create only one figure/canvas with 2 subplots, and call a single update function that takes care of updating the content of both axes.
In essence, your question would be a duplicate of this one, except for the fact that you are embedding the animation in a Qt app.
import sys
import time
import numpy as np
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
if is_pyqt5():
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
from matplotlib.figure import Figure
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
layout = QtWidgets.QVBoxLayout(self._main)
self.fig = Figure(figsize=(5, 6))
self.canvas = FigureCanvas(self.fig)
layout.addWidget(self.canvas)
self.addToolBar(QtCore.Qt.BottomToolBarArea,
NavigationToolbar(self.canvas, self))
self._axs = self.fig.subplots(2, 1)
self._timer = self.canvas.new_timer(
100, [(self._update_canvas, (), {})])
self._timer.start()
def _update_canvas(self):
[ax.clear() for ax in self._axs]
t = np.linspace(0, 10, 501)
self._axs[0].plot(t, np.tan(t + time.time()), ".")
t = np.linspace(0, 10, 101)
self._axs[1].plot(t, np.sin(t + time.time()))
self.canvas.draw()
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()
I added rotated labels to my pie chart and expected that by default labels would be centered in each slice of the pie chart. But this is not the case
How can I center my labels?
Here my backend code:
from PyQt5 import QtCore, QtGui, QtWidgets
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QWidget
from frontend import Ui_MainWindow
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
class Ui_MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Ui_MainWindow, self).__init__(parent)
self.setupUi(self)
self.graph = MyCanvas()
self.gridLayout.addWidget(self.graph, 0, 0, 1, 1)
self.graph.figure.clf()
self.axes = self.graph.figure.add_subplot(111)
self.y = [1,2,3, 4,8,16,32]
self.label = ['1.52%', '3.03%', '4.55%', '6.06%', '12.12%', '24.24%', '48.48%']
self.axes.pie(self.y, labels=self.label, labeldistance=0.6, rotatelabels =True)
class MyCanvas(FigureCanvas):
def __init__(self, *args, **kwargs):
self.figure = plt.figure()
FigureCanvas.__init__(self, self.figure)
self.figure.patch.set_facecolor("None")
self.figure.subplots_adjust(left=0.08, bottom=0.10, right=0.99, top=0.97)
if __name__ == '__main__':
app = QApplication(sys.argv)
prog = Ui_MainWindow()
prog.show()
sys.exit(app.exec_())
Looking at the documentation, you can pass dedicated options to the text objects in your pie chart using the textprops keyword. textprops accepts a dict, which apparently accepts all options that are accepted by matplotlib.text.Text. Feeding it the options rotation_mode='anchor', va='center' and ha='left' gives pretty good results:
import matplotlib.pyplot as plt
figure = plt.figure()
figure.patch.set_facecolor("None")
figure.subplots_adjust(left=0.08, bottom=0.10, right=0.99, top=0.97)
figure.clf()
axes = figure.add_subplot(111)
axes.set_aspect(1)
y = [1,2,3, 4,8,16,32]
label = ['1.52%', '3.03%', '4.55%', '6.06%', '12.12%', '24.24%', '48.48%']
axes.pie(
y, labels=label, labeldistance=0.6, rotatelabels =True,
textprops = dict(rotation_mode = 'anchor', va='center', ha='left'),
)
plt.show()
The result of the code looks like this:
Note that I added ax.set_aspect(1) to make the pie chart circular. If you don't want that, just leave out that line.
As part of a larger project I am updating a graph on matplotlib embedded in Pyqt5. I have an onpick event that adds and removes annotation to a scatter point. After I updated my plot in a fashion similar to below the onPick feature registers twice. There is something underlying that is not getting removed correctly. I was wondering what I might clear, besides the figure, that would correct my issue.
Simple Example Highlighting the issue:
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
class plot(QWidget):
def __init__(self):
super().__init__()
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.Layout = QVBoxLayout()
self.xarray = [1,2,3,4,5,6]
self.yarray = [6,7,5,4,2,1]
update_btn = QPushButton("Update Plot", self)
self.Layout.addWidget(update_btn, 1)
update_btn.clicked.connect(self.updateplot)
self.createplot()
self.setLayout(self.Layout)
def updateplot(self):
self.xarray = [6,7,5,3,2,1]
self.figure.clear()
self.createplot()
def createplot(self):
ax = self.figure.add_subplot(1,1,1)
ax.grid()
val = self.displayval(ax)
self.plot = ax.plot(self.xarray, self.yarray,'o', marker = 'o', c= 'b', picker = 5)[0]
self.Layout.addWidget(self.canvas, 2)
self.canvas.draw()
def displayval(self, ax):
def onPick(event):
print("connecting")
plot = event.artist
xval = plot.get_xdata()
yval = plot.get_ydata()
ind = event.ind
if xval[ind].size > 1 or yval[ind].size > 1: return
xy = (xval[ind][0], yval[ind][0])
ann = ax.annotate('(%f , %f)' % xy, xy= xy)
self.figure.canvas.draw()
self.figure.canvas.mpl_connect('pick_event', onPick)
return onPick
if __name__ == '__main__':
appl = QApplication(sys.argv)
main = plot()
main.show()
sys.exit(appl.exec_())
Note: I have tried updating the axis values with something like
ax.set_xaxis()
However this does not render the data correctly in my program, therefore an alternative answer would be preferred.
Here is a Solution to the issue. Thanks to #ImportanceOfBeingErnest for explaining why the issue was occurring. From there I was able to fix the problem. Once updated the on click annotation only occurs once. The solution is only to call displayval once and change ax to 'self.ax' and update the object variable.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
class plot(QWidget):
def __init__(self):
super().__init__()
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.Layout = QVBoxLayout()
self.xarray = [1,2,3,4,5,6]
self.yarray = [6,7,5,4,2,1]
self.init_trigger = True
update_btn = QPushButton("Update Plot", self)
self.Layout.addWidget(update_btn, 1)
update_btn.clicked.connect(self.updateplot)
self.createplot()
self.setLayout(self.Layout)
def updateplot(self):
self.xarray = [6,7,5,3,2,1]
self.figure.clear()
self.createplot()
def createplot(self):
self.ax = self.figure.add_subplot(1,1,1)
self.ax.grid()
if self.init_trigger: val = self.displayval()
self.plot = self.ax.plot(self.xarray, self.yarray,'o', marker = 'o', c= 'b', picker = 5)[0]
self.Layout.addWidget(self.canvas, 2)
self.canvas.draw()
self.init_trigger = False
def displayval(self):
def onPick(event):
print("connecting")
plot = event.artist
xval = plot.get_xdata()
yval = plot.get_ydata()
ind = event.ind
if xval[ind].size > 1 or yval[ind].size > 1: return
xy = (xval[ind][0], yval[ind][0])
ann = self.ax.annotate('(%f , %f)' % xy, xy= xy)
self.figure.canvas.draw()
self.figure.canvas.mpl_connect('pick_event', onPick)
return onPick
if __name__ == '__main__':
appl = QApplication(sys.argv)
main = plot()
main.show()
sys.exit(appl.exec_())