Related
I am trying to write a GUI in Python3 using PyQt4.
For data visualization, I need to isolate a specific point on a curve plotted by the function whole_plot(). To do so, I am currently using a slider that let the GUI user choose a point of interest. When the slider_value is changed, the point is selected and plotted by calling the function point_plot().
Regarding some previous answers, I am now trying to update my graph through matplotlib.animation (cf. post python matplotlib update scatter plot from a function). But for some reasons, I still got the wrong updating, can someone help me figure out what is the problem in my code?
import sys
import numpy as np
from PyQt4 import QtGui
from PyQt4 import QtCore
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
grid = QtGui.QGridLayout()
self.setLayout(grid)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas_1 = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas_1, self)
grid.addWidget(self.canvas_1, 2,0,1,2)
grid.addWidget(self.toolbar, 0,0,1,2)
# Slider
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(nbr_points-1)
self.slider.setTickInterval(1)
self.slider.valueChanged.connect(self.point_plot)
# Slider info box
self.label = QtGui.QLabel(self)
grid.addWidget(self.label,4,0)
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1', self)
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1', self)
btn_minus_one.clicked.connect(self.value_minus_one)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3,0,1,3)
self.whole_plot()
self.point_plot()
self.show()
def whole_plot(self):
ax1 = self.figure_1.add_subplot(111)
ax1.clear()
ax1.cla()
#
ax1.plot(my_X_data,my_Y_data,'b-')
#
ax1.set_xlim([-10,10])
ax1.set_ylim([-1000,1000])
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
#
self.canvas_1.draw()
def point_plot(self):
ax1 = self.figure_1.add_subplot(111)
#
X_point, Y_point = [],[]
scat = ax1.scatter(X_point,Y_point, s=100,c='k')
def animate(i):
index_slider_value = self.slider.value()
X_point = my_X_data[index_slider_value,]
Y_point = my_Y_data[index_slider_value,]
scat.set_offsets(np.c_[X_point,Y_point])
anim = matplotlib.animation.FuncAnimation(self.figure_1,animate, frames=my_X_data, interval=200, repeat=True)
self.canvas_1.draw()
def value_plus_one(self):
# slider +1
if self.slider.value() < (my_X_data.shape[0]-1):
index_slider_value = self.slider.value() + 1
self.slider.setValue(index_slider_value)
def value_minus_one(self):
# slider -1
if self.slider.value() > 0:
index_slider_value = self.slider.value() - 1
self.slider.setValue(index_slider_value)
def main():
app = QtGui.QApplication(sys.argv)
MyWidget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have to learn what is better to reuse than create, in this case you just need to create a scatter and update the data with set_offsets() when the value of the slider changes, so FuncAnimation is not necessary.
import numpy as np
from PyQt4 import QtCore, QtGui
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas, self)
# Slider
self.slider = QtGui.QSlider(minimum=0,
maximum= nbr_points-1,
orientation=QtCore.Qt.Horizontal,
tickInterval=1)
self.slider.valueChanged.connect(self.on_valueChanged)
# Slider info box
self.label = QtGui.QLabel()
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1')
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1')
btn_minus_one.clicked.connect(self.value_minus_one)
grid = QtGui.QGridLayout(self)
grid.addWidget(self.canvas, 2, 0, 1, 2)
grid.addWidget(self.toolbar, 0, 0, 1, 2)
grid.addWidget(self.label, 4, 0)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3, 0, 1, 3)
self.whole_plot()
def whole_plot(self):
ax = self.figure_1.add_subplot(111)
ax.clear()
ax.plot(my_X_data,my_Y_data,'b-')
ax.set_xlim([-10,10])
ax.set_ylim([-1000,1000])
ax.set_xlabel('X')
ax.set_ylabel('Y')
self.canvas.draw()
X_point, Y_point = [],[]
self.scat = ax.scatter(X_point, Y_point, s=100,c='k')
# set initial
self.on_valueChanged(self.slider.value())
#QtCore.pyqtSlot(int)
def on_valueChanged(self, value):
X_point = my_X_data[value,]
Y_point = my_Y_data[value,]
self.scat.set_offsets(np.c_[X_point,Y_point])
self.canvas.draw()
#QtCore.pyqtSlot()
def value_plus_one(self):
self.slider.setValue(self.slider.value() + 1)
#QtCore.pyqtSlot()
def value_minus_one(self):
self.slider.setValue(self.slider.value() - 1)
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
On the other hand QSlider will not update the value if it is less than minimum or greater than maximum
form_class = uic.loadUiType("GUI.ui")[0] # Load the UI
class MainWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, target, Afb, np, conversion, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
if self.RadioButton.isChecked():
Ids, Ugs = unipolar_steuerkennlinie(self.target, self.Afb, self.np, self.conversion)
def plot_matplotlibwidget(self, *args):
for x in range(0, 40):
self.matplotlibwidget.axes.plot([args[0][x]], [[args[1][x]]])
self.matplotlibwidget.axes.figure.canvas.draw_idle()
Hi, I have this function and I don't really know how to plot correctly. Now it shows nothing but the does change the scale.
Since it is hard to completely replicate your code and error, I've created you a simple minimal GUI that has a MPL widget and draws a few random lines.
from PyQt4 import QtGui, QtCore
import sys
import functools
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy as np
from matplotlib.figure import Figure
class test(QtGui.QWidget):
def __init__(self,parent=None):
self.widget=QtGui.QWidget.__init__(self, parent)
# Button to plot
self.btnPlot = QtGui.QPushButton('Plot')
self.btnPlot.connect(self.btnPlot, QtCore.SIGNAL('clicked()'), self.btnPlotPressed)
# Line edit for number of lines
self.qleN = QtGui.QLineEdit(str(0))
# Create canvas
self.setupMPLWidget()
# Layout
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.btnPlot)
self.hbox.addWidget(self.qleN)
self.hbox.addWidget(self.canvas)
self.setLayout(self.hbox)
self.show()
def btnPlotPressed(self):
"""Plots a few lines."""
# Get number of buttons to add
n=int(self.qleN.text())
# Generate some data
xs,ys=self.randData(n)
# Plot
self.plot_matplotlibwidget(xs,ys)
def randData(self,n):
"""Creates n random data sets."""
ys=[]
xs=[]
for i in range(n):
xs.append(np.arange(n-i))
ys.append(np.random.random(size=xs[-1].shape))
return xs,ys
def plot_matplotlibwidget(self,*args):
"""Plots list of datasets."""
for x in range(0, len(args[0])):
self.ax.plot(args[0][x], args[1][x])
self.ax.figure.canvas.draw_idle()
def setupMPLWidget(self):
"""Sets up a MPL figure to draw on."""
# Create parenting widget
self.plotFrame = QtGui.QWidget()
self.plotFrame.setMaximumWidth(1)
# Create Figure
self.fig = Figure( dpi=100)
#self.fig.set_size_inches(100,100,forward=True)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.plotFrame)
self.ax = self.fig.add_subplot(111)
def main():
#Creating application
app = QtGui.QApplication(sys.argv)
main_win = test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
It has a little QLE where you can specify how many lines you want to draw. I hope this helps.
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()
I am using PyQt 4 for a basic GUI and matplotlib for a plot from which I want to read the coordinates of the plotted data points. Based on these examples (simple picking example), I have the simple problem that I cannot display the coordinates of a data point in a text field such as QtGui.QLabel(). I do not understand why I cannot call the instance Window.msg in the method onpick(). Probably it is because the instance it not given to the method. I only have a basic understanding of object oriented programming (but I am working on it), so the problem is my lack of knowledge.
My question: How to display the coordinates of chosen data (by clicking on it) from a matplotlib plot in my GUI based on PyQT (in that case in my label lbl)?
Also, it would be nice to highlight the chosen data point in the plot.
Here is my code (working):
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# This does not work:
#Window.lbl.setText(self.msg)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The self is being overlapped by the picker (not sure why). In any case this should work:
import numpy as np
import matplotlib.pyplot as plt
from PyQt4 import QtGui
import sys
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.initUI()
def initUI(self):
self.msg = '0'
# a figure instance to plot on
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.toolbar = NavigationToolbar(self.canvas, self)
# a label
self.lbl = QtGui.QLabel(self.msg)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.lbl)
self.setLayout(layout)
self.plot()
def changelabel(arg):
main.lbl.setText(str(arg[0])+' '+str(arg[1]))
def plot(self):
# random data
data = [np.random.random() for i in range(10)]
# create an axis
ax = self.figure.add_subplot(111)
# discards the old graph
ax.hold(False)
# plot data
line, = ax.plot(data, 'o', picker=5) # 5 points tolerance
self.canvas.draw()
self.canvas.mpl_connect('pick_event', Window.onpick)
def onpick(self):
thisline = self.artist
xdata = thisline.get_xdata()
ydata = thisline.get_ydata()
ind = self.ind
# show data
self.msg = (xdata[ind], ydata[ind])
print(self.msg)
# Window.changelabel(self.msg)
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
, the change is in the setText function, since I call it directly from the variable (no self or Window).
main.lbl.setText(str(self.msg[0])+' '+str(self.msg[1]))
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.