Embed Matplotlib Graph in a PyQt5 - python

I am trying to embed matplotlib graph into PqQt5, I have a ready function to call them, however, I have no success with it. The code for the graph is as follow:
import re
import os
import sys
import json
import numpy as np
import pylab
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.pyplot as plt
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DATA_DIR = BASE_DIR + os.path.sep + 'data'
if not os.path.exists(DATA_DIR ):
popup_critical('Грешен път до данните')
with open('./data/71161.json', 'r') as f:
data = json.load(f)
for distro in data:
downwelling_radiance = np.array(distro['spectra']['ld']['data'])
irradiance_reflectance = np.array(distro['spectra']['r0minuscorr']['data'])
downwelling_irradiance = np.array(distro['spectra']['ed']['data'])
upwelling_radiance = np.array(distro['spectra']['lu']['data'])
wavelength = np.array(distro['spectra']['wavelength']['data'])
reflectance = np.array(distro['spectra']['r0minus']['data'])
date = np.array(distro['meta']['date'])
name = np.array(distro['meta']['id'])
def radiance_plot():
fig = plt.figure(figsize=(14, 8))
plt.title("Plot")
plt.xlabel('Wavelength [nm]')
plt.ylabel('Radiance [W/(m^2xnmxsr)]')
ax=plt.gca()
ax.set_xlim(400,800)
plt.grid(True, color='skyblue')
plt.plot(
wavelength, downwelling_radiance, 'orange',
wavelength, upwelling_radiance, 'burlywood',
lw=3,
)
print(np.arange(0, max(downwelling_radiance + 0.02) if max(downwelling_radiance) >
max(upwelling_radiance) else max(upwelling_radiance + 0.02), step=0.02))
print(list(np.arange(0, max(downwelling_radiance + 0.02) if max(downwelling_radiance) >
max(upwelling_radiance) else max(upwelling_radiance + 0.02), step=0.02)))
plt.ylim(0.00)
plt.yticks(list(np.arange(0, max(downwelling_radiance + 0.02) if max(downwelling_radiance)
> max(upwelling_radiance) else max(upwelling_radiance + 0.02), step=0.02)))
plt.legend(['Upwelling radiance', 'Downwelling radiance'], loc='upper left')
plt.twinx(ax = None)
plt.plot(
wavelength, downwelling_irradiance, 'c',
lw=3,
)
plt.grid(True)
plt.ylabel('Irradiance [W/(m^2xnm)]')
plt.yticks(list(np.arange(0, max(downwelling_irradiance + 0.2), 0.2)))
plt.legend(['Downwelling irradiance'], loc='upper right')
fig.tight_layout()
return plt.show()
def reflectance_plot():
fig = plt.figure(figsize=(14, 8))
plt.title("Plot")
plt.xlabel('Wavelength [nm]')
plt.ylabel('Reflectance [-]')
ax=plt.gca()
ax.set_xlim(400,800)
plt.grid(True)
plt.plot(
wavelength, reflectance, 'burlywood',
lw=3,
)
plt.ylim(0.00)
plt.yticks(list(np.arange(0, max(reflectance + 0.02), step=0.01)))
plt.legend(['Radiance'], loc='upper right')
fig.tight_layout()
return plt.show()
def date_print():
return date
def name_print():
return name
#radiance_plot()
#reflectance_plot()
#name_print()
#date_print()
The code above prints the graphs, now I want to print the graphs in PyQt5 so that when the user presses button 1 it prints the first graph and the second button prints the second graph. There is the code for the PyQt5
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QVBoxLayout, QListWidget,
QMainWindow, QPushButton
import sys
from PyQt5.QtGui import QIcon, QFont
from read import date_print, name_print, radiance_plot, reflectance_plot
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import numpy as np
class Window(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 400, 300)
self.setWindowTitle("Python and Matplotlib")
self.create_list()
self.myUI()
#chart = Canvas(self)
def create_list(self):
vbox = QVBoxLayout()
self.list_widget = QListWidget()
self.list_widget.insertItem(0, "File1")
self.list_widget.insertItem(1, "File2")
self.list_widget.insertItem(2, "file3")
self.list_widget.setStyleSheet("background-color:red")
self.list_widget.setFont(QFont("Sanserif", 15))
self.list_widget.clicked.connect(self.item_clicked)
self.label = QLabel("")
self.setFont(QFont("Sanserif", 14))
self.label.setStyleSheet("color:green")
vbox.addWidget(self.list_widget)
vbox.addWidget(self.label)
self.setLayout(vbox)
def item_clicked(self):
item = self.list_widget.currentItem()
self.label.setText("You have selected file:" + str(name_print()) + "; Date: " +
str(date_print()))
def myUI(self):
#canvas = Canvas(self, width=8, height=4)
#canvas.move(0,0)
butt = QPushButton("Click Me", self)
butt.move(100, 100)
butt2 = QPushButton("Click Me 2", self)
butt2.move(250, 100)
"""
class Canvas(FigureCanvas):
def __init__(self, parent):
fig, self.ax = plt.subplots(figsize=(5,4), dpi = 50)
super().__init__(fig)
self.setParent(parent)
self.plot()
def plot(self):
ref = self.figure.add_subplot(111)
ref.pie(reflectance_plot(), labels=labels)
"""
App = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec_())
I tried to connect them however I was not able to print anything. I found a way to print a graph however it does not work in my case
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
from read import wavelength, downwelling_radiance, upwelling_radiance
import pandas as pd
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Create the maptlotlib FigureCanvas object,
# which defines a single set of axes as self.axes.
sc = MplCanvas(self, width=10, height=5, dpi=100)
# Create our pandas DataFrame with some simple
# data and headers.
df = pd.DataFrame([
[0, 10], [5, 15], [2, 20], [15, 25], [4, 10],
], columns=['A', 'B'])
# plot the pandas DataFrame, passing in the
# matplotlib Canvas axes.
df.plot(ax=sc.axes)
# Create toolbar, passing canvas as first parament, parent (self, the MainWindow) as second.
toolbar = NavigationToolbar(sc, self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(toolbar)
layout.addWidget(sc)
# Create a placeholder widget to hold our toolbar and canvas.
widget = QtWidgets.QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.show()
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
app.exec_()
Any idea how to connect them

I’ve created this minimal example for you, showing how to embed matplotlib plotting into your pyqt5 app (works for pyside2 just the same).
I saw in your code, that you kept calling to matplotlib.pyplot. You’re not supposed to talk to pyplot when embedding in your gui backend.
The example also illustrates, how you can set your grid, legend, etc. via calling methods on the axis/figure object and not via plt.
There are 3 buttons, play around with it, and you’ll quickly get how to embed matplotlib and how to handle your “custom plot widget”.
EDIT: With keyboard keys 1, 2, 3 you can call the plot methods as well.
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
# in case of pyside: just replace PyQt5->PySide2
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
from matplotlib.figure import Figure
class MplWidget(qtw.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
fig = Figure(figsize=(5, 5))
self.can = FigureCanvasQTAgg(fig)
self.toolbar = NavigationToolbar2QT(self.can, self)
layout = qtw.QVBoxLayout(self)
layout.addWidget(self.toolbar)
layout.addWidget(self.can)
# here you can set up your figure/axis
self.ax = self.can.figure.add_subplot(111)
def plot_basic_line(self, X, Y, label):
# plot a basic line plot from x and y values.
self.ax.cla() # clears the axis
self.ax.plot(X, Y, label=label)
self.ax.grid(True)
self.ax.legend()
self.can.figure.tight_layout()
self.can.draw()
class MyQtApp(qtw.QWidget):
def __init__(self):
super(MyQtApp, self).__init__()
# layout
self.mpl_can = MplWidget(self)
self.btn_plot1 = qtw.QPushButton('plot1', self)
self.btn_plot2 = qtw.QPushButton('plot2', self)
self.btn_plot3 = qtw.QPushButton('scatter', self)
self.layout = qtw.QVBoxLayout(self)
self.layout.addWidget(self.mpl_can)
self.layout.addWidget(self.btn_plot1)
self.layout.addWidget(self.btn_plot2)
self.layout.addWidget(self.btn_plot3)
self.setLayout(self.layout)
# connects
self.btn_plot1.clicked.connect(self.plot1)
self.btn_plot2.clicked.connect(self.plot2)
self.btn_plot3.clicked.connect(self.plot_scatter)
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key_1: self.plot1()
elif event.key() == qtc.Qt.Key_2: self.plot2()
elif event.key() == qtc.Qt.Key_3: self.plot_scatter()
def plot1(self):
X, Y = (1, 2), (1, 2)
self.mpl_can.plot_basic_line(X, Y, label='plot1')
def plot2(self):
X, Y = (10, 20), (10, 20)
self.mpl_can.plot_basic_line(X, Y, label='plot2')
def plot_scatter(self):
X, Y = (1, 7, 9, 3, 2), (3, 6, 8, 2, 11)
self.mpl_can.ax.scatter(X, Y, label='scatter')
self.mpl_can.ax.legend()
self.mpl_can.can.draw()
if __name__ == '__main__':
app = qtw.QApplication([])
qt_app = MyQtApp()
qt_app.show()
app.exec_()
https://www.mfitzp.com/tutorials/plotting-matplotlib/ is an awesome more in depth tutorial on how to embed matplotlib into pyqt5, but I think for your purpose, above very basic example will suffice.

Related

How to collect the axis object of the last mouse click in matplotlib canvas drawn with pyqt5?

I have an interactive window and I need to know which subplot was selected during the interaction. When I was using matplotlib alone, I could use plt.connect('button_press_event', myMethod). But with pyqt5, I am importing FigureCanvasQTAgg and there is a reference to the figure itself but not an equivalent of pyplot. So, I am unable to create that reference.
Minimal reproducible example:
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
import numpy as np
# list to store the axis last used with a mouseclick
currAx = []
# detect the currently modified axis
def onClick(event):
if event.inaxes:
currAx[:] = [event.inaxes]
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.canvas = FigureCanvas(plt.Figure())
self.axis = self.canvas.figure.subplots(3)
for i, ax in enumerate(self.axis):
t = np.linspace(-i, i + 1, 100)
ax.plot(t, np.sin(2 * np.pi * t))
self.listOfSpans = [SpanSelector(
ax,
self.onselect,
"horizontal"
)
for ax in self.axis]
plt.connect('button_press_event', onClick)
# need an equivalent of ^^ to find the axis interacted with
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
toolbar = NavigationToolbar(self.canvas, self)
layout.addWidget(toolbar)
layout.addWidget(self.canvas)
self.setLayout(layout)
self.show()
def onselect(self, xmin, xmax):
if xmin == xmax:
return
# identify the axis interacted and do something with that information
for ax, span in zip(self.axis, self.listOfSpans):
if ax == currAx[0]:
print(ax)
print(xmin, xmax)
self.canvas.draw()
def run():
app = QApplication([])
mw = MyWidget()
app.exec_()
if __name__ == '__main__':
run()
Apparently, there is a method for connecting canvas as well - canvas.mpl_connect('button_press_event', onclick).
Link to the explanation: https://matplotlib.org/stable/users/explain/event_handling.html
Found the link to the explanation in this link :Control the mouse click event with a subplot rather than a figure in matplotlib.

Update lat/lon axes when zooming on cartopy plot

I am plotting some data in cartopy. I would like to be able to zoom in on a region of the map and have the latitude/longitude axes update to reflect the zoomed in region. Instead, they just dissapear altogether when I zoom in. How do I fix this?
Here is my code for generating the axes
plt.figure()
ax = plt.axes(projection=cartopy.crs.PlateCarree())
ax.add_feature(cartopy.feature.LAND, edgecolor='black')
gl = ax.gridlines(crs=cartopy.crs.PlateCarree(), draw_labels=True,
linewidth=2, color='gray', alpha=0.5, linestyle='--')
# plot some stuff here
It is possible to update the cartopy gridliners in interactive mode, but you need to subclass the Navigation toolbar.
In this example below I have used a PySide/QT5 example code that allows me to substitute a subclassed toolbar, then merged in the gridliner example code. The overloaded toolbar callbacks recreate the gridlines everytime zoom/pan/home is used.
I used python3.8, matplotlib-3.4.2, cartopy-0.20
import sys
from PySide2 import QtWidgets
from PySide2.QtWidgets import QVBoxLayout
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import cartopy.crs as ccrs
class CustomNavigationToolbar(NavigationToolbar):
toolitems = [t for t in NavigationToolbar.toolitems if t[0] in ('Home', 'Pan', 'Zoom', 'Save')]
def __init__(self, canvas, parent, coordinates=True, func_recreate_gridlines=None):
print('CustomNavigationToolbar::__init__')
super(CustomNavigationToolbar, self).__init__(canvas, parent, coordinates)
self.func_recreate_gridlines = func_recreate_gridlines
def home(self, *args):
print('CustomNavigationToolbar::home')
super(CustomNavigationToolbar, self).home(*args)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_pan(self, event):
print('CustomNavigationToolbar::release_pan')
super(CustomNavigationToolbar, self).release_pan(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
def release_zoom(self, event):
print('CustomNavigationToolbar::release_zoom')
super(CustomNavigationToolbar, self).release_zoom(event)
if self.func_recreate_gridlines is not None:
self.func_recreate_gridlines()
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
print('ApplicationWindow::__init__')
super().__init__()
self._main = QtWidgets.QWidget()
self.setCentralWidget(self._main)
self.layout = QVBoxLayout(self._main)
self.fig = Figure()
self.canvas = FigureCanvas(self.fig)
self.toolbar = CustomNavigationToolbar(self.canvas, self,
coordinates=True,
func_recreate_gridlines=self.recreate_gridlines)
self.layout.addWidget(self.canvas)
self.addToolBar(self.toolbar)
# figure setup taken from gridlines example at
# https://scitools.org.uk/cartopy/docs/latest/matplotlib/gridliner.html
projection = ccrs.RotatedPole(pole_longitude=120.0, pole_latitude=70.0)
self.ax = self.canvas.figure.add_subplot(1, 1, 1, projection=projection)
self.ax.set_extent([-6, 3, 48, 58], crs=ccrs.PlateCarree())
self.ax.coastlines(resolution='10m')
self._gl = None
self.recreate_gridlines()
def recreate_gridlines(self):
print('ApplicationWindow::recreate_gridlines')
print(' remove old gridliner artists')
if self._gl is not None:
for artist_coll in [self._gl.xline_artists, self._gl.yline_artists, self._gl.xlabel_artists, self._gl.ylabel_artists]:
for a in artist_coll:
a.remove()
self.ax._gridliners = []
print(' self.ax.gridlines()')
self._gl = self.ax.gridlines(crs=ccrs.PlateCarree(),
draw_labels=True, dms=True, x_inline=False, y_inline=False)
if __name__ == "__main__":
qapp = QtWidgets.QApplication(sys.argv)
app = ApplicationWindow()
app.show()
qapp.exec_()

Python scatter updating using Qt4 - Qslider

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

How to plot squarify graph on button click in PyQt5 GUI python

I am a python beginner and am creating a GUI using PyQt5 and have run into a problem, please help.
This is a squarify graph's example
import pandas as pd
df = pd.DataFrame({'nb_people':[8,3,4,2], 'group':["group A", "group B",
"group C", "group D"] })
squarify.plot(sizes=df['nb_people'], label=df['group'], alpha=.8 )
plt.axis('off')
plt.show()
And this is a function that I'm calling on button click which is plotting a random graph.
def plot(self):
# random data
data = [random.random() for i in range(10)]
# instead of ax.hold(False)
self.figure.clear()
# create an axis
ax = self.figure.add_subplot(111)
# plot data
ax.plot(data, '*-')
# refresh canvas
self.canvas.draw()
How to plot that squarify graph on this GUI?
squarify.plot() has an argument called ax which is the axes where it will be drawn.
import sys
import random
import pandas as pd
import matplotlib
matplotlib.use('Qt5Agg')
from PyQt5 import QtCore, QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import squarify
class Widget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QWidget.__init__(self, *args, **kwargs)
self.figure = Figure(figsize=(5, 4), dpi=100)
self.canvas = FigureCanvas(self.figure)
self.canvas.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
button = QtWidgets.QPushButton("random plot")
button.clicked.connect(self.plot)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.canvas)
lay.addWidget(button)
self.plot()
def plot(self):
self.figure.clear()
df = pd.DataFrame({'nb_people':[random.randint(1, 10) for i in range(4)], 'group':["group A", "group B", "group C", "group D"] })
ax = self.figure.add_subplot(111)
squarify.plot(sizes=df['nb_people'], label=df['group'], alpha=.8 ,ax=ax)
ax.axis('off')
self.canvas.draw()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

PyQt4 Scatterplot with Clickable and Selectable Points

I'm trying to create a scatterplot with about 1000 data points in a way so that each data point can be selected by clicking on them using a mouse, which will result in bringing up a context menu which will allow the user to remove or change the color of the data point. I've been following the tutorials for matplotlib, and looking at the Draggable Rectangle Exercise but I'm having a difficult time. I'm using the matplotlib.patches.Circle class to represent each data point, but I cannot get the 'contains' method to work correctly with the 'button_press_event'. Mainly, there seems to be no 'canvas' object associate with each Circle object. I get the following error at line 16:
AttributeError: 'NoneType' object has no attribute 'canvas'
Here's the code:
#!/usr/bin/python -tt
import sys
import numpy
#import matplotlib.pyplot
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle
class SelectablePoint:
def __init__(self, xy, label):
self.point = Circle( (xy[0], xy[0]), .005 )
self.label = label
self.point.figure
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.onClick)
def onClick(self, e):
print e.xdata, e.ydata
#print(dir(self.point))
print self.point.center
print self.point.contains(e)[0]
#if self.point.contains(e)[0]:
# print self.label
class ScatterPlot(FigureCanvas):
'''
classdocs
'''
def __init__(self, parent=None):
'''
Constructor
'''
self.fig = Figure()
FigureCanvas.__init__(self, self.fig)
self.axes = self.fig.add_subplot(111)
#x = numpy.arange(0.0, 3.0, 0.1)
#y = numpy.cos(2*numpy.pi*x)
x = [.5]
y = [.5]
#scatterplot = self.axes.scatter(x,y)
for i in range(len(x)):
c = Circle( (x[i], y[i]), .05 )
self.axes.add_patch(c)
#SelectablePoint( (x[i],y[i]), 'label for: ' + str(i), self.figure.canvas )
SelectablePoint( (x[i],y[i]), 'label for: ' + str(i) )
#self.axes.add_artist(c)
class MainContainer(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(900,600)
self.setWindowTitle('Scatter Plot')
sp = ScatterPlot(self)
self.setCentralWidget(sp)
self.center()
def center(self):
# Get the resolution of the screen
screen = QtGui.QDesktopWidget().screenGeometry()
# Get the size of widget
size = self.geometry()
self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
b = MainContainer()
b.show()
sys.exit(app.exec_())
I'm not sure if I'm approaching this the right way, or if I should be looking at another graphing module, but I've looked at gnuplot and chaco, and I felt that matplotlib was more fit for my problem. Are there any other recommendations? Thank you very much.
** My Solution **
Here's what I have working so far. It simply outputs the label of each data point to standard output when the points in the scatter plot are clicked on.
#!/usr/bin/python -tt
import sys
import numpy
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle
class SelectablePoint:
def __init__(self, xy, label, fig):
self.point = Circle( (xy[0], xy[1]), .25, figure=fig)
self.label = label
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.onClick)
def onClick(self, e):
if self.point.contains(e)[0]:
print self.label
class ScatterPlot(FigureCanvas):
'''
classdocs
'''
def __init__(self, parent=None):
'''
Constructor
'''
self.fig = Figure()
FigureCanvas.__init__(self, self.fig)
self.axes = self.fig.add_subplot(111)
xlim = [0,7]
ylim = [0,7]
self.axes.set_xlim(xlim)
self.axes.set_ylim(ylim)
self.axes.set_aspect( 1 )
x = [1, 1.2, 3, 4, 5, 6]
y = [1, 1.2, 3, 4, 5, 6]
labels = ['1', '2', '3', '4', '5', '6']
for i in range(len(x)):
sp = SelectablePoint( (x[i],y[i]), labels[i], self.fig)
self.axes.add_artist(sp.point)
class MainContainer(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(900,600)
self.setWindowTitle('Scatter Plot')
sp = ScatterPlot(self)
self.setCentralWidget(sp)
self.center()
def center(self):
# Get the resolution of the screen
screen = QtGui.QDesktopWidget().screenGeometry()
# Get the size of widget
size = self.geometry()
self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
b = MainContainer()
b.show()
sys.exit(app.exec_())
Oops I was creating two instances of patches.Circle for each data point. After passing in the first instance to my SelectablePoint class, everything worked fine.
I was interested to your code but I did get the event with this change in the SelectablePoint.
Is this better ? Well, it now clicks entire screen area, not only in the circles. So I miss the point perhaps? Why you created Circle twice ? I riped it off from the SelectablePoint though, but it didn't catch any event before the change of the onClick I present you.
Any case why ?
import sys
import numpy
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle
class SelectablePoint:
def __init__(self, xy, label, fig):
self.point = Circle( (xy[0], xy[1]), .25, figure=fig)
self.label = label
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', onClick)
def onClick(e):
print e.__dict__
class ScatterPlot(FigureCanvas):
'''
classdocs
'''
def __init__(self, parent=None):
'''
Constructor
'''
self.fig = Figure()
FigureCanvas.__init__(self, self.fig)
self.axes = self.fig.add_subplot(111)
xlim = [0,7]
ylim = [0,7]
self.axes.set_xlim(xlim)
self.axes.set_ylim(ylim)
self.axes.set_aspect( 1 )
x = [1, 1.2, 3, 4, 5, 6]
y = [1, 1.2, 3, 4, 5, 6]
labels = ['1', '2', '3', '4', '5', '6']
for i in range(len(x)):
sp = SelectablePoint( (x[i],y[i]), labels[i], self.fig)
self.axes.add_artist(sp.point)
class MainContainer(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(900,600)
self.setWindowTitle('Scatter Plot')
sp = ScatterPlot(self)
self.setCentralWidget(sp)
self.center()
def center(self):
# Get the resolution of the screen
screen = QtGui.QDesktopWidget().screenGeometry()
# Get the size of widget
size = self.geometry()
self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
b = MainContainer()
b.show()
sys.exit(app.exec_())

Categories