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
Related
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.
My ui file contains a widget container with a vertical layout named "VL" and a line edit named "Radiance". I created a single bar graph that I want to change as I input values into the line edit. At the moment, it does just that, except it creates a new plot every time. If I use my "remove" function it doesn't make a whole separate plot, but it ruins the layout of the one. I think the problem lies with my "remove" function and where to put it, please help.
I imported QtWidgets, uic, matplot.figure, and necessary backends:
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('PyQt_App1.ui', self)
self.setWindowTitle("Window Title")
self.Radiance.textChanged.connect(self.animate)
def animate(self):
self.remove()
r = self.Radiance.text()
if r:
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
else:
r = 0
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
def addmpl(self, fig):
self.canvas = FigureCanvas(fig)
self.VL.addWidget(self.canvas)
# self.canvas.setParent(self.Frame)
self.canvas.draw()
def remove(self):
self.VL.removeWidget(self.canvas)
self.canvas.close()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
Instead of creating a new figure every time I would just keep references to the current bar plot and the current axes, and use those to update the figure, e.g.
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
central = QtWidgets.QWidget(self)
self.VL = QtWidgets.QVBoxLayout(central)
self.Radiance = QtWidgets.QLineEdit(self)
self.VL.addWidget(self.Radiance)
self.canvas = FigureCanvas(Figure())
self.VL.addWidget(self.canvas)
self.ax1f1 = self.canvas.figure.subplots()
self.ax1f1.set_ylim([0, 100])
self.ax1f1.set_xlim([0, 0.5])
self.bar = None
self.setWindowTitle("Window Title")
self.setCentralWidget(central)
self.Radiance.textChanged.connect(self.animate)
def animate(self):
r = self.Radiance.text()
try:
rad = float(r)
except ValueError:
rad = 0
positions = [0.25]
if self.bar:
self.bar.remove()
self.bar = self.ax1f1.bar(positions, rad, width=0.2, color="g")
self.canvas.draw()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
is there any way to relate a click on the Canvas but outside of a plot axes to the closest axes of the click? I have a canvas with x number of subplots and I'm trying to find out the closest subplot near the mouse click. Ultimately, this would help me create a zoom-in-rectangle that allows the user to zoom in the area of selected subplots (the navigation toolbar only zoom in one subplot).
#import os
#os.environ['QT_API'] = 'pyside'
from PyQt4 import QtGui, QtCore
import sys
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import numpy as np
class Canvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
self.figure = Figure()
super(Canvas, self).__init__(self.figure)
self.ax1 = self.figure.add_subplot(1,1,1)
self.figure.subplots_adjust(left = 0.05, bottom = 0.02, right = 0.98, top = 0.99)
self.setMinimumWidth(1000)
self.ax1.plot([1,2,3])
self.draw()
def add_subplot(self, data=[]):
rows = len(self.figure.axes) + 1
for index, axes in enumerate(self.figure.axes, start=1):
axes.change_geometry(rows, 1, index)
ax = self.figure.add_subplot(rows, 1, index+1)
x = np.arange(1000,1)
y = np.arange(1000,1)
ax.step(x,y)
ax.patch.set_facecolor('None')
self.figure.set_figheight(self.figure.get_figheight()*self.figScalingfactor)
def figScaling(self, numSubplot):
self.figScalingfactor = round(1.1729*pow(numSubplot, -0.028),3)
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.showMaximized()
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(5)
self.canvas = Canvas(self)
self.scroll = QtGui.QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.scroll.setWidgetResizable(False)
self.nav = NavigationToolbar(self.canvas, self.widget)
self.numSubplots = 30
self.canvas.figScaling(self.numSubplots)
for x in range(self.numSubplots):
self.canvas.add_subplot()
self.canvas.adjustSize()
self.canvas.draw_idle()
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.scroll)
self.showVline = False
self.hoveringLine = None
self.canvas.mpl_connect("scroll_event", self.scrolling)
self.canvas.mpl_connect("button_press_event", self.onClick)
self.canvas.mpl_connect("motion_notify_event", self.onMove)
def scrolling(self, event):
val = self.scroll.verticalScrollBar().value()
if event.button =="down":
self.scroll.verticalScrollBar().setValue(val+100)
else:
self.scroll.verticalScrollBar().setValue(val-100)
def onClick(self, event):
if event.dblclick and self.showVline == False:
self.background = self.canvas.copy_from_bbox(self.canvas.figure.bbox)
self.showVline = True
self.hoveringLine = self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
lw=2, zorder=0, clip_on=False)
elif event.dblclick and self.showVline:
self.showVline = False
self.hoveringLine = None
self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
lw=2, zorder=0, clip_on=False)
self.canvas.draw()
else:
print(event.xdata)
print(event.ydata)
def onMove(self, event):
if (self.showVline):
self.canvas.restore_region(self.background)
self.hoveringLine.set_xdata(event.xdata)
self.canvas.ax1.draw_artist(self.hoveringLine)
self.canvas.blit(self.canvas.figure.bbox)
def main():
app = QtGui.QApplication(sys.argv)
app.aboutToQuit.connect(app.deleteLater)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I'm trying to make a program in which I have a main window and a second window. The second window should be opened by checking a Check-Box in the main window and closed by unchecking it.
The following minimal example works already fine (thanks to ImportanceOfBeingErnest !), but I want to spin the arrow (the one, which is already bent when you run the example) by changing the SpinBox in the main window.
Solution: See 5th comment in the first answer!
import sys
from PyQt4 import QtGui, QtCore
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation
import numpy as np
class Newsphere(QtGui.QMainWindow):
def __init__(self):
super(Newsphere, self).__init__()
self.mainbox = QtGui.QWidget()
self.mainbox.setLayout(QtGui.QHBoxLayout())
self.setCentralWidget(self.mainbox)
self.spin = QtGui.QSpinBox()
self.spin.setValue(20)
self.spin.setMaximum(100)
self.spin.setMinimum(-100)
self.checkPlot = QtGui.QCheckBox("Check")
self.mainbox.layout().addWidget(self.spin)
self.mainbox.layout().addWidget(self.checkPlot)
self.Plot = None
self.checkPlot.clicked.connect(self.showPlot)
def showPlot(self):
if self.Plot == None:
self.Plot = Plot(self.kinematic())
self.Plot.show()
# register signal for closure
self.Plot.signalClose.connect(self.uncheck)
# register signal for spin value changed
self.spin.valueChanged.connect(self.kinematic)
else:
self.Plot.close()
self.Plot = None
def kinematic(self):
x = self.spin.value() / 100
v = np.matrix([[1.,x,0.],[0.,1.,0.],[0.,0.,1.]])
zero = np.matrix([[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]])
pos = np.hstack([v, zero])
return pos
def uncheck(self):
self.checkPlot.setChecked(False)
self.Plot = None
class Plot(QtGui.QWidget):
signalClose = QtCore.pyqtSignal()
def __init__(self, pos=None):
super(Plot, self).__init__()
self.setLayout(QtGui.QHBoxLayout())
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.fig.tight_layout()
self.ax.view_init(40, 225)
''' dashed coordinate system '''
self.ax.plot([0,1], [0,0], [0,0], label='$X_0$', linestyle="dashed", color="red")
self.ax.plot([0,0], [0,-10], [0,0], label='$Y_0$', linestyle="dashed", color="green")
self.ax.plot([0,0], [0,0], [0,1], label='$Z_0$', linestyle="dashed", color="blue")
self.ax.set_xlim3d(-3,3)
self.ax.set_ylim3d(-3,3)
self.ax.set_zlim3d(-3,3)
self.canvas = FigureCanvas(self.fig)
self.layout().addWidget(self.canvas)
self.pos = pos
self.setup_plot()
self.ani = animation.FuncAnimation(self.fig, self.update_plot, init_func=self.setup_plot, blit=True)
def setup_plot(self):
self.ax.legend(loc='best')
self.position = self.ax.quiver(0, 0, 0, 0, 0, 0, pivot="tail", color="black")
return self.position,
def update_plot(self, i):
x_zero = self.pos[:,3]
y_zero = self.pos[:,4]
z_zero = self.pos[:,5]
v_x = self.pos[0,0:3]
v_y = self.pos[1,0:3]
v_z = self.pos[2,0:3]
self.position = self.ax.quiver(-x_zero, -y_zero, z_zero, -v_x[0,:], v_y[0,:], v_z[0,:], pivot="tail", color="black")
self.canvas.draw()
return self.position,
# We need to make sure the animation stops, when the window is closed
def closeEvent(self, event):
self.signalClose.emit()
self.close()
super(Plot, self).closeEvent(event)
def close(self):
self.ani.event_source.stop()
super(Plot, self).close()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Newsphere()
main.show()
sys.exit(app.exec_())
Here is an working example of what I think you are trying to achieve.
The Main Window has a spin box and a check box. Once the checkbox is clicked, a new window with a plot will show up and an animation will start. The current value and some array will be given to the plot window. If you change the spin box value while the animation is running, it will be updated. When the plot window is closed or when the checkbox is unchecked, the animation will stop (and be deleted).
import sys
from PyQt4 import QtGui, QtCore
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation
import numpy as np
class Newsphere(QtGui.QMainWindow):
def __init__(self):
super(Newsphere, self).__init__()
self.mainbox = QtGui.QWidget()
self.mainbox.setLayout(QtGui.QHBoxLayout())
self.setCentralWidget(self.mainbox)
self.spin = QtGui.QSpinBox()
self.spin.setValue(5)
self.spin.setMaximum(10)
self.spin.setMinimum(1)
self.checkPlot = QtGui.QCheckBox("Check")
self.mainbox.layout().addWidget(self.spin)
self.mainbox.layout().addWidget(self.checkPlot)
self.Plot = None
self.checkPlot.clicked.connect(self.showPlot)
def showPlot(self):
if self.Plot == None:
self.Plot = Plot(self.kinematic(), self.spin.value())
self.Plot.show()
# register signal for closure
self.Plot.signalClose.connect(self.uncheck)
# register signal for spin value changed
self.spin.valueChanged.connect(self.Plot.update_factor)
else:
self.Plot.close()
self.Plot = None
def kinematic(self):
v = np.array([[1.,2.,3.],[2.,1.,3.],[3.,2.,1.]])
return v
def uncheck(self):
self.checkPlot.setChecked(False)
self.Plot = None
class Plot(QtGui.QWidget):
signalClose = QtCore.pyqtSignal()
def __init__(self, v=None, factor=1):
super(Plot, self).__init__()
self.setLayout(QtGui.QHBoxLayout())
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ax.set_aspect('equal')
self.fig.tight_layout()
self.ax.view_init(40, 225)
self.ax.set_xlim3d(0,3)
self.ax.set_ylim3d(0,3)
self.ax.set_zlim3d(0,4)
self.canvas = FigureCanvas(self.fig)
self.layout().addWidget(self.canvas)
self.pos = v
self.setup_plot()
self.update_factor(factor)
self.ani = animation.FuncAnimation(self.fig, self.update_plot, blit=False)
def setup_plot(self):
xpos, ypos = np.meshgrid(np.arange(self.pos.shape[0]),np.arange(self.pos.shape[1]) )
self.xpos = xpos.flatten('F')
self.ypos = ypos.flatten('F')
self.zpos = np.zeros_like(self.xpos)
self.bar = None
def update_factor(self, factor):
self.factor = factor
self.dx = np.ones_like(self.xpos)*np.min(np.abs(self.factor/10.), 0.1)
self.dy = self.dx.copy()
def update_plot(self, i):
if self.bar != None:
self.bar.remove()
del self.bar
pos = self.pos+np.sin(i/8.)
dz = pos.flatten()
self.bar = self.ax.bar3d(self.xpos, self.ypos, self.zpos, self.dx, self.dy, dz,
color=(1.-self.factor/10.,0,self.factor/10.), zsort='average', linewidth=0)
self.canvas.draw()
# We need to make sure the animation stops, when the window is closed
def closeEvent(self, event):
self.signalClose.emit()
self.close()
super(Plot, self).closeEvent(event)
def close(self):
self.ani.event_source.stop()
super(Plot, self).close()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Newsphere()
main.show()
sys.exit(app.exec_())
Since I wasn't sure about what you want to animate, I changed the plot to a barplot, but you can change it back to whatever you need. Hope that helps.
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]))