I am using Matplotlib library to implement Figures in a PyQt5 windows. When I run the code, I get the following warning errors:
Warning (from warnings module):
File "C:\Users\yagom\OneDrive\Escritorio\SUSKIND\Código fuente\wsS1.py", line 6
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
MatplotlibDeprecationWarning:
The matplotlib.backends.backend_qt4agg backend was deprecated in Matplotlib 3.3 and will be removed two minor releases later.
Warning (from warnings module):
File "C:\Users\yagom\OneDrive\Escritorio\SUSKIND\Código fuente\wsS1.py", line 106
ax.set_yticklabels(ax.get_yticks(), {'family':'Cambria','size':'9','color':'black'}) #
MatplotlibDeprecationWarning: Passing the fontdict parameter of _set_ticklabels() positionally is deprecated since Matplotlib 3.3; the parameter will become keyword-only two minor releases later.
Warning (from warnings module):
File "C:\Users\yagom\OneDrive\Escritorio\SUSKIND\Código fuente\wsS1.py", line 75
ax.set_xticklabels(ax.get_xticks(), {'family':'Cambria','size':'9','color':'white'})
UserWarning: FixedFormatter should only be used together with FixedLocator
Here is a little reproducible exemple where the warnings also occur.
import sys, time, random
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib import ticker
class wS1(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle('Interfaz')
self.setFixedSize(1440,880)
self.initUI()
def initUI(self):
def set_GraphWidget_1 (ys=[0,1,0,2,26,22,14,12,4,2],remark=3):
self.figure = Figure(figsize=(3,1.8))
self.Graph_1 = FigureCanvas(self.figure)
ax = self.figure.add_subplot(111)
self.figure.tight_layout()
ax.clear()
ax.grid(True,axis='both',linestyle=':')
ax.set_yticklabels(ax.get_yticks(), {'family':'Cambria','size':'9','color':'black'})
ax.set_xticklabels(ax.get_xticks(), {'family':'Cambria','size':'9','color':'white'})
ax.tick_params(which='major', width=0.75, length=5, color='grey')
ax.tick_params(which='minor', width=0.5, length=2.5, color='grey')
for spine in ax.spines.values():
spine.set_edgecolor('grey')
ax.set_facecolor('#f4f2f1')
ax.yaxis.set_major_locator(ticker.MultipleLocator(10))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(5))
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_formatter('{x} %')
ax.plot(ys,linewidth=1,markersize=3,marker='o',color='#e66f00',zorder=0)
if remark != None:
ax.scatter(remark,ys[remark],s=35,linewidth=0.5,edgecolors='black',color='#e66f00',zorder=1)
def set_GraphWidget_2 (ys=[120,66,19,19,14,9,9,5,0,6]):
self.figure = Figure(figsize=(3,2.5))
self.Graph_2 = FigureCanvas(self.figure)
ax = self.figure.add_subplot(111)
self.figure.tight_layout()
ax.clear()
ax.grid(True,axis='both',linestyle=':')
ax.set_yticklabels(ax.get_yticks(), {'family':'Cambria','size':'9','color':'black'}) #
ax.tick_params(which='major', width=0.75, length=5, color='grey')
ax.tick_params(which='minor', width=0.5, length=2.5, color='grey')
for spine in ax.spines.values():
spine.set_edgecolor('grey')
ax.set_facecolor('#f4f2f1')
ax.yaxis.set_major_locator(ticker.MultipleLocator(20))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(5))
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_formatter('{x}')
ax.xaxis.tick_top()
width = 0.08
opacity = 0.85
xs = range(1,len(ys)+1)
ax.bar(xs,ys,width,color='#e66f00')
set_GraphWidget_1()
set_GraphWidget_2()
self.Lay = QVBoxLayout()
self.Lay.addWidget(self.Graph_1)
self.Lay.addWidget(self.Graph_2)
self.setLayout(self.Lay)
app = QApplication(sys.argv)
window = wS1()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I have tried several changes but none of them seems to work. Could someone please explain me what should I change from the code? I would be very grateful.
About the tick warning. you should split the set ticks functions to 2, one for values, and one for labels. For example:
ax.set_yticks([0,50,80])
ax.set_yticklabels(['start', 'mid', 'end])
Transform dictionary values to keyword arguments
# From
ax.set_yticklabels(ax.get_yticks(), {'family': 'Cambria', 'size': '9'})
# To
ax.set_yticklabels(ax.get_yticks(), family='Cambria', size=9)
Related
Following is the codes. It plots a line via pressing a button. However, when I pressed the button, it just printed
matplotlib.lines.Line2D object at 0x11371fcc0 ......
but could not show the line on the canvas. How do you fix it?
import matplotlib
matplotlib.use("Qt5Agg")
from PyQt5 import QtCore
from PyQt5.QtWidgets import *
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import mywidgets
# mywidgets.MplCanvas is a wrapper of FigureCanvas in order to make the drawing convenient.
class ApplicationWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.setWindowTitle("Hello")
self.main_widget = QWidget(self)
l = QVBoxLayout(self.main_widget)
fig1 = Figure(figsize=(5, 4))
self.sc = mywidgets.MplCanvas(self.main_widget, fig1)
l.addWidget(self.sc)
bdraw = QPushButton('Draw')
bdraw.pressed.connect(self.draw)
l.addWidget(bdraw)
self.main_widget.setFocus()
self.setCentralWidget(self.main_widget)
def draw(self):
# it does not report any error, but on lines are drawn.
line = self.sc.axes.plot([1,2,3], 'r')
print(line)
if __name__ == '__main__':
app = QApplication([])
aw = ApplicationWindow()
aw.show()
#sys.exit(qApp.exec_())
app.exec_()
You forgot to update the canvas after plotting to it.
def draw(self):
line = self.sc.axes.plot([1,2,3], 'r')
self.sc.draw_idle()
I'm learning how to use matplotlib, and now I have a problem. When I create a Figure in "tkinter project" and give it a subplot, I use NavigationToolbar2TkAgg to create a toolbar. In the current toolbar that appears , i want to remove the configure subplot option but couldn't find a way to do so.
Is there any way to do it?
The solution to this is in principle already given in this question: How to modify the navigation toolbar easily in a matplotlib figure window?
But it may not be obvious how to use it. So we may adapt the code from here with a CustomToolbar. The Toolbars toolitems attribute can be changed as to remove the unwanted "Subplots" button.
import numpy as np
import Tkinter as tk
import matplotlib as mpl
from matplotlib.patches import Rectangle
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
# custom toolbar with lorem ipsum text
class CustomToolbar(NavigationToolbar2TkAgg):
toolitems = filter(lambda x: x[0] != "Subplots", NavigationToolbar2TkAgg.toolitems)
class MyApp(object):
def __init__(self,root):
self.root = root
self._init_app()
# here we embed the a figure in the Tk GUI
def _init_app(self):
self.figure = mpl.figure.Figure()
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.figure,self.root)
self.toolbar = CustomToolbar(self.canvas,self.root)
self.toolbar.update()
self.plot_widget = self.canvas.get_tk_widget()
self.plot_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.toolbar.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.canvas.show()
# plot something random
def plot(self):
self.ax.plot([1,3,2])
self.figure.canvas.draw()
def main():
root = tk.Tk()
app = MyApp(root)
app.plot()
root.mainloop()
if __name__ == "__main__":
main()
Note: In newer versions of matplotlib you should use NavigationToolbar2Tk instead of NavigationToolbar2TkAgg
I am using a wrapper of PyQt (pyqtgraph) to build a GUI application.
I wish to embed a Seaborn plot within it using the MatplotlibWidget. However, my problem is that the Seaborn wrapper method such as FacetGrid do not accept an external figure handle. Moreover, when I try to update the MatplotlibWidget object underlying figure (.fig) with the figure produced by the FacetGrid it doesn't work (no plot after draw). Any suggestion for a workaround?
Seaborn's Facetgrid provides a convenience function to quickly connect pandas dataframes to the matplotlib pyplot interface.
However in GUI applications you rarely want to use pyplot, but rather the matplotlib API.
The problem you are facing here is that Facetgrid already creates its own matplotlib.figure.Figure object (Facetgrid.fig). Also, the MatplotlibWidget
creates its own figure, so you end up with two figures.
Now, let's step back a bit:
In principle it is possible to use a seaborn Facetgrid plot in PyQt, by first creating the plot and then providing the resulting figure to the figure canvas (matplotlib.backends.backend_qt4agg.FigureCanvasQTAgg). The following is an example of how to do that.
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import sys
import seaborn as sns
import matplotlib.pyplot as plt
tips = sns.load_dataset("tips")
def seabornplot():
g = sns.FacetGrid(tips, col="sex", hue="time", palette="Set1",
hue_order=["Dinner", "Lunch"])
g.map(plt.scatter, "total_bill", "tip", edgecolor="w")
return g.fig
class MainWindow(QtGui.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtGui.QWidget(self)
self.fig = seabornplot()
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.button = QtGui.QPushButton("Button")
self.label = QtGui.QLabel("A plot:")
self.layout = QtGui.QGridLayout(self.main_widget)
self.layout.addWidget(self.button)
self.layout.addWidget(self.label)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
While this works fine, it is a bit questionable, if it's useful at all. Creating a plot inside a GUI in most cases has the purpose of beeing updated depending on user interactions. In the example case from above, this is pretty inefficient, as it would require to create a new figure instance, create a new canvas with this figure and replace the old canvas instance with the new one, adding it to the layout.
Note that this problematics is specific to those plotting functions in seaborn, which work on a figure level, like lmplot, factorplot, jointplot, FacetGrid and possibly others.
Other functions like regplot, boxplot, kdeplot work on an axes level and accept a matplotlib axes object as argument (sns.regplot(x, y, ax=ax1)).
A possibile solution is to first create the subplot axes and later plot to those axes, for example using the pandas plotting functionality.
df.plot(kind="scatter", x=..., y=..., ax=...)
where ax should be set to the previously created axes.
This allows to update the plot within the GUI. See the example below. Of course normal matplotlib plotting (ax.plot(x,y)) or the use of the seaborn axes level function discussed above work equally well.
from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns
tips = sns.load_dataset("tips")
class MainWindow(QtGui.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtGui.QWidget(self)
self.fig = Figure()
self.ax1 = self.fig.add_subplot(121)
self.ax2 = self.fig.add_subplot(122, sharex=self.ax1, sharey=self.ax1)
self.axes=[self.ax1, self.ax2]
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.dropdown1 = QtGui.QComboBox()
self.dropdown1.addItems(["sex", "time", "smoker"])
self.dropdown2 = QtGui.QComboBox()
self.dropdown2.addItems(["sex", "time", "smoker", "day"])
self.dropdown2.setCurrentIndex(2)
self.dropdown1.currentIndexChanged.connect(self.update)
self.dropdown2.currentIndexChanged.connect(self.update)
self.label = QtGui.QLabel("A plot:")
self.layout = QtGui.QGridLayout(self.main_widget)
self.layout.addWidget(QtGui.QLabel("Select category for subplots"))
self.layout.addWidget(self.dropdown1)
self.layout.addWidget(QtGui.QLabel("Select category for markers"))
self.layout.addWidget(self.dropdown2)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
self.update()
def update(self):
colors=["b", "r", "g", "y", "k", "c"]
self.ax1.clear()
self.ax2.clear()
cat1 = self.dropdown1.currentText()
cat2 = self.dropdown2.currentText()
print cat1, cat2
for i, value in enumerate(tips[cat1].unique().get_values()):
print "value ", value
df = tips.loc[tips[cat1] == value]
self.axes[i].set_title(cat1 + ": " + value)
for j, value2 in enumerate(df[cat2].unique().get_values()):
print "value2 ", value2
df.loc[ tips[cat2] == value2 ].plot(kind="scatter", x="total_bill", y="tip",
ax=self.axes[i], c=colors[j], label=value2)
self.axes[i].legend()
self.fig.canvas.draw_idle()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
win = MainWindow()
sys.exit(app.exec_())
A final word about pyqtgraph: I wouldn't call pyqtgraph a wrapper for PyQt but more an extention. Although pyqtgraph ships with its own Qt (which makes it portable and work out of the box), it is also a package one can use from within PyQt. You can therefore add a GraphicsLayoutWidget to a PyQt layout simply by
self.pgcanvas = pg.GraphicsLayoutWidget()
self.layout().addWidget(self.pgcanvas)
The same holds for a MatplotlibWidget (mw = pg.MatplotlibWidget()). While you can use this kind of widget, it's merely a convenience wrapper, since all it's doing is finding the correct matplotlib imports and creating a Figure and a FigureCanvas instance. Unless you are using other pyqtgraph functionality, importing the complete pyqtgraph package just to save 5 lines of code seems a bit overkill to me.
Here is exact copy of the accepted answer but using PYQT5:
from PyQt5 import QtCore, QtGui, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
import seaborn as sns
tips = sns.load_dataset("tips")
class MainWindow(QtWidgets.QMainWindow):
send_fig = QtCore.pyqtSignal(str)
def __init__(self):
super(MainWindow, self).__init__()
self.main_widget = QtWidgets.QWidget(self)
self.fig = Figure()
self.ax1 = self.fig.add_subplot(121)
self.ax2 = self.fig.add_subplot(122, sharex=self.ax1, sharey=self.ax1)
self.axes=[self.ax1, self.ax2]
self.canvas = FigureCanvas(self.fig)
self.canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
self.canvas.updateGeometry()
self.dropdown1 = QtWidgets.QComboBox()
self.dropdown1.addItems(["sex", "time", "smoker"])
self.dropdown2 = QtWidgets.QComboBox()
self.dropdown2.addItems(["sex", "time", "smoker", "day"])
self.dropdown2.setCurrentIndex(2)
self.dropdown1.currentIndexChanged.connect(self.update)
self.dropdown2.currentIndexChanged.connect(self.update)
self.label = QtWidgets.QLabel("A plot:")
self.layout = QtWidgets.QGridLayout(self.main_widget)
self.layout.addWidget(QtWidgets.QLabel("Select category for subplots"))
self.layout.addWidget(self.dropdown1)
self.layout.addWidget(QtWidgets.QLabel("Select category for markers"))
self.layout.addWidget(self.dropdown2)
self.layout.addWidget(self.canvas)
self.setCentralWidget(self.main_widget)
self.show()
self.update()
def update(self):
colors=["b", "r", "g", "y", "k", "c"]
self.ax1.clear()
self.ax2.clear()
cat1 = self.dropdown1.currentText()
cat2 = self.dropdown2.currentText()
print (cat1, cat2)
for i, value in enumerate(tips[cat1].unique().get_values()):
print ("value ", value)
df = tips.loc[tips[cat1] == value]
self.axes[i].set_title(cat1 + ": " + value)
for j, value2 in enumerate(df[cat2].unique().get_values()):
print ("value2 ", value2)
df.loc[ tips[cat2] == value2 ].plot(kind="scatter", x="total_bill", y="tip",
ax=self.axes[i], c=colors[j], label=value2)
self.axes[i].legend()
self.fig.canvas.draw_idle()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
sys.exit(app.exec_())
While any matplotlib plots can be embedded in pyqt5 the same way, it's important to note that the UI could get slow as the sizeof the dataset grows. But I found such approaches handy to parse and plot log files by employing regex functionalities.
I'm writing a python application using pyside and matplotlib. Following a combination of this tutorial and this SO post, I have created a matplotlib widget that I can successfully add to a parent. However when I go to actually add data to it, nothing seems to get displayed.
If I add static data like the SO post had, it shows up, but when I change it to update on the fly (currently every second on a timer, but it will eventually be using a signal from another class), I never get anything but the empty axes to appear. I suspect that I'm missing a call to force a draw or invalidate or that there is something wrong with the way I'm calling update_datalim (though the values that get passed to it seem correct).
from PySide import QtCore, QtGui
import matplotlib
import random
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
from collections import namedtuple
DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])
class BaseWidget(FigureCanvas):
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.axes.set_xlabel('X Label')
self.axes.set_ylabel('Y Label')
self.axes.set_title('My Data')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class DynamicWidget(BaseWidget):
def set_data(self, the_data):
self.axes.clear()
xys = list()
cmap = plt.cm.hot
for datum in the_data:
bottom_left = (datum.start_x, datum.start_y)
top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
rect = Rectangle(
xy=bottom_left,
width=datum.width, height=datum.height, color=cmap(100)
)
xys.append(bottom_left)
xys.append(top_right)
self.axes.add_artist(rect)
self.axes.update_datalim(xys)
self.axes.figure.canvas.draw_idle()
class RandomDataWidget(DynamicWidget):
def __init__(self, *args, **kwargs):
DynamicWidget.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.generate_and_set_data)
timer.start(1000)
def generate_and_set_data(self):
fake_data = [DataModel(
start_x=random.randint(1, 100),
width=random.randint(20, 40),
start_y=random.randint(80, 160),
height=random.randint(20, 90)
) for i in range(100)]
self.set_data(fake_data)
Edit: I'm suspecting that there's an issue with updating the limits of the plot. When running the above code, the plot opens with limits of 0 and 1 on both the x and y axis. Since none of my generated data falls into that range, I created another subclass of DynamicWidget that plots only data between 0 and 1 (the same data from the linked SO post). When instantiating the class below, the data shows up successfully. Do I need to do something more than calling update_datalim to get the graph to re-bound itself?
class StaticWidget(DynamicWidget):
def __init__(self):
DynamicWidget.__init__(self)
static_data = [
DataModel(0.5, 0.05, 0.2, 0.05),
DataModel(0.1, 0.2, 0.7, 0.2),
DataModel(0.3, 0.1, 0.8, 0.1)
]
self.set_data(static_data)
Yes, update_datalim only updates the bounding box that is kept internally by the axes. You also need to enable auto scaling for it to be used. Add self.axes.autoscale(enable=True) after the self.axes.clear() statement and it will work. Or you can set the axes' range to a fixed value by using self.axes.set_xlim and self.axes.set_ylim.
edit: here is my code, which works for me
from PySide import QtCore, QtGui
import matplotlib
import random, sys
matplotlib.use('Qt4Agg')
matplotlib.rcParams['backend.qt4']='PySide'
from matplotlib import pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.patches import Rectangle
from collections import namedtuple
DataModel = namedtuple('DataModel', ['start_x', 'start_y', 'width', 'height'])
class BaseWidget(FigureCanvas):
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.axes.autoscale(enable=True)
self.axes.set_xlabel('X Label')
self.axes.set_ylabel('Y Label')
self.axes.set_title('My Data')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QtGui.QSizePolicy.Expanding,
QtGui.QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class DynamicWidget(BaseWidget):
def set_data(self, the_data):
self.axes.clear()
self.axes.autoscale(enable=True)
#self.axes.set_xlim(0, 300)
#self.axes.set_ylim(0, 300)
xys = list()
cmap = plt.cm.hot
for datum in the_data:
print datum
bottom_left = (datum.start_x, datum.start_y)
top_right = (bottom_left[0] + datum.width, bottom_left[1] + datum.height)
rect = Rectangle(
xy=bottom_left,
width=datum.width, height=datum.height, color=cmap(100)
)
xys.append(bottom_left)
xys.append(top_right)
self.axes.add_artist(rect)
self.axes.update_datalim(xys)
self.axes.figure.canvas.draw_idle()
class RandomDataWidget(DynamicWidget):
def __init__(self, *args, **kwargs):
DynamicWidget.__init__(self, *args, **kwargs)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.generate_and_set_data)
timer.start(1000)
def generate_and_set_data(self):
fake_data = [DataModel(
start_x=random.randint(1, 100),
width=random.randint(20, 40),
start_y=random.randint(80, 160),
height=random.randint(20, 90)) for i in range(100)]
self.set_data(fake_data)
print "done:...\n\n"
def main():
qApp = QtGui.QApplication(sys.argv)
aw = RandomDataWidget()
aw.show()
aw.raise_()
sys.exit(qApp.exec_())
if __name__ == "__main__":
main()
I'm trying to use the 'ginput' to measure distance in a matplotlib figure by allowing the user to mouse click the locations. I am able to do this independently in the matplotlib figure, but I'm having problems when I tried to set the figure onto a matplotlib canvas and then embed it into PyQt4 widget. Below is my code, most of which were taken from the matplotlib examples. My solution will be to click a set of locations, and pass the (x,y) coordinates to the 'dist_calc' function to get the distance.
import sys
from PyQt4 import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
import random
import numpy as np
class Window(QtGui.QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.fig = Figure((6.5, 5.0), tight_layout=True)
self.ax = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.toolbar = NavigationToolbar(self.canvas, self)
self.button = QtGui.QPushButton('Plot')
self.button.clicked.connect(self.plot)
self.ndist = QtGui.QPushButton('Measure')
self.ndist.clicked.connect(self.draw_line)
self.toolbar.addWidget(self.button)
self.toolbar.addWidget(self.ndist)
self.fig.tight_layout()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
def plot(self):
data = [random.random() for i in range(20)]
self.ax.hold(False)
self.ax.plot(data, '*-')
self.canvas.draw()
def draw_line(self):
self.xy = plt.ginput(0)
x = [p[0] for p in self.xy]
y = [p[1] for p in self.xy]
self.ax.plot(x,y)
self.ax.figure.canvas.draw()
self.get_dist(x, y)
def get_dist(self, xpts, ypts):
npts = len(xpts)
distArr = []
for i in range(npts-1):
apt = [xpts[i], ypts[i]]
bpt = [xpts[i+1], ypts[i+1]]
dist =self.calc_dist(apt,bpt)
distArr.append(dist)
tdist = np.sum(distArr)
print(tdist)
def calc_dist(self,apt, bpt):
apt = np.asarray(apt)
dist = np.sum((apt - bpt)**2)
dist = np.sqrt(dist)
return dist
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
According to this comment by one of the lead Matplotlib developers, you must not import pyplot when you're embedding Matplotlib in Qt. Pyplot sets up its own gui, mainloop and canvas, which interfere with the Qt event loop.
Changing the line self.xy = plt.ginput(0) into self.xy = self.fig.ginput(0) did not help but gave an insightful error:
AttributeError: 'FigureCanvasQTAgg' object has no attribute 'manager'
Figure.show works only for figures managed by pyplot, normally created by pyplot.figure().
In short, I don't think this is possible. ginput is a blocking function and seems only to be implemented for a Matplotlib event loop. I'm afraid that you will have to build the functionality you want using Matplotlib mouse events, which do work when embedding in PyQt. Just be sure not to use pyplot!
Edit: I just remembered, perhaps the LassoSelector is what you need.