Using matplotlib checkbuttons in PyQT5 - python-3.x

Is it possible to use matplotlib checkbuttons in a plot embedded in PyQT5? The code is below, the plot works and it is embedded in a PyQT window but the checkbuttons do not add or remove the series as they should. Code works fine when taken out of PyQT.
import numpy as np
import sys
from matplotlib.widgets import CheckButtons
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout
from PyQt5 import QtCore
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.figure = plt.figure()
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2*np.pi*t)
s1 = np.sin(4*np.pi*t)
s2 = np.sin(6*np.pi*t)
self.figure, ax = plt.subplots()
l0, = ax.plot(t, s0, visible=False, lw=2, color='k', label='2 Hz')
l1, = ax.plot(t, s1, lw=2, color='r', label='4 Hz')
l2, = ax.plot(t, s2, lw=2, color='g', label='6 Hz')
plt.subplots_adjust(left=0.2)
self.lines = [l0, l1, l2]
# Make checkbuttons with all plotted lines with correct visibility
rax = plt.axes([0.05, 0.4, 0.1, 0.15])
self.labels = [str(line.get_label()) for line in self.lines]
visibility = [line.get_visible() for line in self.lines]
check = CheckButtons(rax, self.labels, visibility)
check.on_clicked(self.b)
## print('showing')
## plt.show()
self.canvas = FigureCanvas(self.figure)
layout = QVBoxLayout()
## layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
# layout.addWidget(self.button)
self.setLayout(layout)
self.canvas.draw()
## print('done')
## plt.show()
def b(self,label):
index = self.labels.index(label)
self.lines[index].set_visible(not self.lines[index].get_visible())
plt.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())

If you are going to use matplotlib with Qt then you should not use pyplot but the Figure that is set on the canvas. Also "check" must be a member of the class.
Considering the above, the solution is:
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
import numpy as np
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import CheckButtons
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
self.canvas = FigureCanvas(Figure(figsize=(5, 3)))
t = np.arange(0.0, 2.0, 0.01)
s0 = np.sin(2 * np.pi * t)
s1 = np.sin(4 * np.pi * t)
s2 = np.sin(6 * np.pi * t)
ax = self.canvas.figure.subplots()
(l0,) = ax.plot(t, s0, visible=False, lw=2, color="k", label="2 Hz")
(l1,) = ax.plot(t, s1, lw=2, color="r", label="4 Hz")
(l2,) = ax.plot(t, s2, lw=2, color="g", label="6 Hz")
self.canvas.figure.subplots_adjust(left=0.2)
self.lines = [l0, l1, l2]
rax = self.canvas.figure.add_axes([0.05, 0.4, 0.1, 0.15])
self.labels = [str(line.get_label()) for line in self.lines]
visibility = [line.get_visible() for line in self.lines]
self.check = CheckButtons(rax, self.labels, visibility)
self.check.on_clicked(self.on_clicked)
lay = QVBoxLayout(self)
lay.addWidget(self.canvas)
self.resize(640, 480)
def on_clicked(self, label):
index = self.labels.index(label)
self.lines[index].set_visible(not self.lines[index].get_visible())
self.canvas.draw()
def main():
import sys
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

Related

PyQt: Is it possible to drag/drop QWidgets in a QGridLayout to rearrange them?

I am looking for a way to create a grid of graphs that can be dragged/dropped to rearrange the order. My first try was using QDockWidgets as they allow for drag/drop, however they were limited in a lot of other ways. Would it be possible to implement this function in a QGridLayout?
For now I have a QGridLayout with 3x3 matplotlib widgets.
Here is an example of the desired layout outcome.
Sample code:
import sys
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import random
from PyQt5.QtWidgets import QGridLayout, QVBoxLayout, QHBoxLayout, QScrollArea, QWidget, QDialog, QApplication, QFrame
class IndicSelectWindow(QDialog):
def __init__(self, parent=None):
super(IndicSelectWindow, self).__init__(parent=parent)
self.resize(1000, 800)
self.layout = QtWidgets.QHBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addWidget(self.scrollArea)
for i in range(3):
for j in range(3):
self.Frame = QFrame(self)
self.Frame.setStyleSheet("background-color: white;")
self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.Frame.setLineWidth(2)
self.layout = QHBoxLayout(self.Frame)
self.figure = Figure() # a figure to plot on
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111) # create an axis
data = [random.random() for i in range(10)]
self.ax.plot(data, '*-') # plot data
self.canvas.draw() # refresh canvas
self.layout.addWidget(self.canvas)
Box = QVBoxLayout()
Box.addWidget(self.Frame)
self.gridLayout.addLayout(Box, i, j)
self.gridLayout.setColumnStretch(i % 3, 1)
self.gridLayout.setRowStretch(j, 1)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IndicSelectWindow()
w.show()
sys.exit(app.exec_())
Here is an implementation that will swap the positions of the items involved in a drag/drop. The 3 main steps are:
(1) Reimplement mousePressEvent to get the index of the LayoutItem based on mouse coordinates.
(2) Reimplement mouseMoveEvent to set up a QDrag of the FigureCanvas.
(3) Reimplement dropEvent to swap the target items in the layout.
Since the matplotlib widgets absorb mouse events you also need to reimplement eventFilter to detect them.
import sys, random
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class IndicSelectWindow(QDialog):
def __init__(self, parent=None):
super(IndicSelectWindow, self).__init__(parent=parent)
self.resize(1000, 800)
self.target = None
self.setAcceptDrops(True)
self.layout = QHBoxLayout(self)
self.scrollArea = QScrollArea(self)
self.scrollArea.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
self.layout.addWidget(self.scrollArea)
for i in range(3):
for j in range(3):
self.Frame = QFrame(self)
self.Frame.setStyleSheet("background-color: white;")
self.Frame.setFrameStyle(QFrame.Panel | QFrame.Raised)
self.Frame.setLineWidth(2)
self.layout = QHBoxLayout(self.Frame)
self.figure = Figure() # a figure to plot on
self.canvas = FigureCanvas(self.figure)
self.ax = self.figure.add_subplot(111) # create an axis
data = [random.random() for i in range(10)]
self.ax.plot(data, '*-') # plot data
self.canvas.draw() # refresh canvas
self.canvas.installEventFilter(self)
self.layout.addWidget(self.canvas)
Box = QVBoxLayout()
Box.addWidget(self.Frame)
self.gridLayout.addLayout(Box, i, j)
self.gridLayout.setColumnStretch(i % 3, 1)
self.gridLayout.setRowStretch(j, 1)
def eventFilter(self, watched, event):
if event.type() == QEvent.MouseButtonPress:
self.mousePressEvent(event)
elif event.type() == QEvent.MouseMove:
self.mouseMoveEvent(event)
elif event.type() == QEvent.MouseButtonRelease:
self.mouseReleaseEvent(event)
return super().eventFilter(watched, event)
def get_index(self, pos):
for i in range(self.gridLayout.count()):
if self.gridLayout.itemAt(i).geometry().contains(pos) and i != self.target:
return i
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.target = self.get_index(event.windowPos().toPoint())
else:
self.target = None
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton and self.target is not None:
drag = QDrag(self.gridLayout.itemAt(self.target))
pix = self.gridLayout.itemAt(self.target).itemAt(0).widget().grab()
mimedata = QMimeData()
mimedata.setImageData(pix)
drag.setMimeData(mimedata)
drag.setPixmap(pix)
drag.setHotSpot(event.pos())
drag.exec_()
def mouseReleaseEvent(self, event):
self.target = None
def dragEnterEvent(self, event):
if event.mimeData().hasImage():
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if not event.source().geometry().contains(event.pos()):
source = self.get_index(event.pos())
if source is None:
return
i, j = max(self.target, source), min(self.target, source)
p1, p2 = self.gridLayout.getItemPosition(i), self.gridLayout.getItemPosition(j)
self.gridLayout.addItem(self.gridLayout.takeAt(i), *p2)
self.gridLayout.addItem(self.gridLayout.takeAt(j), *p1)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = IndicSelectWindow()
w.show()
sys.exit(app.exec_())

Can someone please tell me what is wrong with this code?

I'm working on Idle and when I ask to check the file, it says no problems. When I run it though, it crashes the app. I'm also working on a MAC, I don't know if that will influence anything. Can anyone please tell me what's wrong with this code? I'm still fairly new, so any help is appreciated.
Also what I am trying to do is mostly imbed a matplotlib animation into a GUI when the GUI has the entries for the animation data, and a button to call the animation is on the GUI as well. Ideally the button would be clicked and the Animation would show up above the two entries. I have been working on this program for a while, but imbedding the animation/ getting the button to callback the graph has been the most troublesome part.
import tkinter as tk
from tkinter import *
import time
import pyaudio
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
import numpy as np
import random
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def get_data():
while True:
return x, mylist
class App(tk.Frame):
def __init__(self, root):
self.root = root
self.root.title("Testing")
self.setupTk()
self.quit_button = Button(master=self.root, text='Quit', command=self._quit)
self.quit_button.pack(side='right')
def _quit(self):
self.root.destroy()
fig = plt.Figure()
x = np.arange(0, 2*np.pi, 0.01) # x-array
def animate(i):
line.set_ydata(np.sin((x+i/10.0))) # update the data
return line,
root = tk.Tk()
label = tk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(column=0,row=1)
entry1= Entry(root, text='Frequency')
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
entry2= Entry(root, text='Duration')
canvas.get_tk.widget().pack(side=TOP, fill=BOTH, expand=1)
entry2.pack()
f=int(entry1.get())
t=float(entry2.get())
def callback():
p=pyaudio.Pyaudio
fs=44100
volume=.7
samples=(np.sin(f*2*np.pi*np.arange(fs*t)/(fs).astype(np/float32)))
stream=p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
stream.write(volume*samples)
b=Button(root, text='Sound', command=callback)
ax = fig.add_subplot(111)
line, = ax.plot(x, np.sin(x))
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
def on_click(self):
if self.ani is None:
return self.start()
if self.running:
self.ani.event_source.stop()
self.btn.config(text='Un-Pause')
else:
self.ani.event_source.start()
self.btn.config(text='Pause')
self.running = not self.running
def start(self):
self.points = int(self.points_ent.get()) + 1
self.ani = animation.FuncAnimation(
self.fig,
self.update_graph,
frames=self.points,
interval=int(self.interval.get()),
repeat=False)
self.running = True
self.btn.config(text='Pause')
self.ani._start()
print('started animation')
def update_graph(self, i):
self.line.set_data(*get_data()) # update graph
if i >= self.points - 1:
self.btn.config(text='Start')
self.running = False
self.ani = None
return self.line,
def main():
root = tk.Tk()
app = App(root)
app.pack()
root.mainloop()
if __name__ == '__main__':
main
As I was interested in the pyaudio I have tried to make your code runnable with the functionality that I think you had intended. You can build from here to add a random signal to the sine wave or use different signals all together. Main changes is that all functions are now in the class App. Also to have the audio sound and plot to run at the same time I start the audio in a different thread.
Hope this is useful for you.
import threading
import random
import numpy as np
import tkinter as tk
from tkinter import Label, Button, Entry, TOP, BOTH
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import pyaudio
DEFAULT_FREQUENCY = 220
DEFAULT_DURATION = 5
VOLUME = 0.3
INTERVAL = 100
class App(tk.Frame):
def __init__(self, root):
self.root = root
self.root.title("SHM Simulation - Testing")
label = tk.Label(self.root, text="SHM Simulation")
label.pack()
self.fig, self.ax = plt.subplots(figsize=(5, 5))
self.canvas = FigureCanvasTkAgg(self.fig, master=root)
self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.ax.set_ylim(-1.1, 1.1)
self.ax.set_xlim(-0.1, 2 * np.pi + 0.1)
self.quit_button = Button(master=self.root, text='Quit', command=self.quit)
self.quit_button.pack(side='right')
self.sound_button = Button(self.root, text='Sound off', command=self.toggle_sound)
self.sound_button.pack(side='right')
self.plot_button = Button(self.root, text='Start')
self.plot_button.pack(side='right')
self.plot_button.bind('<Button-1>', self.on_click)
tk.Label(self.root, text='Frequency').pack(side='left')
self.frequency_entry = Entry(self.root)
self.frequency_entry.insert(0, DEFAULT_FREQUENCY)
self.frequency_entry.pack(side='left')
tk.Label(self.root, text='Duration').pack(side='left')
self.duration_entry = Entry(self.root)
self.duration_entry.insert(0, DEFAULT_DURATION)
self.duration_entry.pack(side='left')
self.ani = None
self.running = False
self.audio = pyaudio.PyAudio()
self.sound = False
self.samples = np.array([])
def quit(self):
self.audio.terminate()
self.root.quit()
def get_data(self, frame):
self.ydata = np.sin(self.frequency * self.xdata + frame)
def update_frame(self, frame):
self.get_data(frame)
self.line.set_data(self.xdata, self.ydata)
if frame > self.duration - 1.1 * INTERVAL / 1000 :
self.ani = None
self.running = False
self.plot_button.config(text='Start')
return self.line,
def start_animation(self):
self.xdata = []
self.ydata = []
self.line, = self.ax.plot(self.xdata, self.ydata, lw=3)
self.xdata = np.arange(0, 2 * np.pi, 0.01)
duration_range = np.arange(0, self.duration, INTERVAL / 1000)
self.ani = FuncAnimation(self.fig,
self.update_frame,
frames=duration_range,
interval=INTERVAL,
repeat=False)
if self.sound:
''' start audio in a seperate thread as otherwise audio and
plot will not be at the same time
'''
x = threading.Thread(target=self.play_sound)
x.start()
def callback(self, in_data, frame_count, time_info, status):
out = self.samples[:frame_count]
self.samples = self.samples[frame_count:]
return (out*VOLUME, pyaudio.paContinue)
def play_sound(self):
fs = 44100
self.samples = (np.sin(2 * np.pi * np.arange(fs * self.duration) *
self.frequency / fs)).astype(np.float32)
stream = self.audio.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True,
stream_callback=self.callback)
stream.start_stream()
# pause audio when self.running is False
while stream.is_active():
while not self.running:
if stream.is_active:
stream.stop_stream()
else:
pass
if self.running and not stream.is_active():
stream.start_stream()
else:
pass
stream.stop_stream()
stream.close()
def toggle_sound(self):
if self.sound:
self.sound_button.configure(text='Sound Off')
else:
self.sound_button.configure(text='Sound On ')
self.sound = not self.sound
def on_click(self, event):
if self.ani is None:
try:
self.ax.lines.pop(0)
except IndexError:
pass
try:
self.frequency = int(self.frequency_entry.get())
self.duration = int(self.duration_entry.get())
except ValueError:
self.frequency = DEFAULT_FREQUENCY
self.frequency_entry.insert(0, DEFAULT_FREQUENCY)
self.duration = DEFAULT_DURATION
self.duration_entry.insert(0, DEFAULT_DURATION)
self.start_animation()
self.running = True
self.plot_button.config(text='Pause')
print('animation started ...')
return
if self.running:
self.ani.event_source.stop()
self.plot_button.config(text='Run ')
else:
self.ani.event_source.start()
self.plot_button.config(text='Pause')
self.running = not self.running
def main():
root = tk.Tk()
app = App(root)
root.mainloop()
if __name__ == '__main__':
main()

Python Change between matplotlib graphic in pyqt

Is there a way to change the matplotlib display in a pyqt application ?
I made a destopapplication with 2 radiobuttons, dependinging which button is pressed, i want on radioButton_p graphic1 to be shown and on radioButton_b the graphic2.
I am able to change it from the first button which is pressed to the second, but if the first button is pressed again it will not change back to the graphic of the first button.
Here is my code so far:
from PyQt5 import QtWidgets, QtCore, QtPrintSupport, QtGui
import matplotlib
matplotlib.use("Qt5Agg")
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import *
found_words = ["a", "b", "c", "d", "e", "f", "g", "h"]
cound_words = ['1','2','3','4','5','6','7','8']
found_pos = [ 'aaa','ssss']
count_pos = ['2','3']
class MyMplCanvas_begr(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# self.compute_initial_figure()
self.axes.bar(found_words, cound_words, color=['r', 'b', 'k', 'y', 'g'])
self.axes.set_ylabel('Anzahl')
self.axes.set_xlabel('Begriffe')
self.axes.set_title('hi')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MyMplCanvas_pos(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
colors = ['yellowgreen', 'lightcoral']
explode = (0.1, 0) # explode 1st slice
ax = self.figure.add_subplot(111)
ax.pie(count_pos, explode=explode, labels=found_pos, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140)
class main_statistic(QtWidgets.QWidget):
def __init__(self, parent=None):
super(main_statistic, self).__init__(parent)
self.radioButton_p = QRadioButton('Pos', self)
self.radioButton_p.setGeometry(QtCore.QRect(100, 10, 95, 20))
self.radioButton_p.setCheckable(True)
self.radioButton_p.toggled.connect(self.clicked_pos)
self.radioButton_b = QRadioButton('Word', self)
self.radioButton_b.setGeometry(QtCore.QRect(200, 10, 95, 20))
self.radioButton_b.setCheckable(True)
self.radioButton_b.toggled.connect(self.clicked_begr)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.main_widget = QWidget(self)
self.main_widget.setGeometry(QtCore.QRect(100, 100, 700, 500))
self.main_widget1 = QWidget(self)
self.main_widget1.setGeometry(QtCore.QRect(100, 100, 700, 500))
def clicked_pos(self, down):
if down:
l = QVBoxLayout(self.main_widget)
dc = MyMplCanvas_pos(self.main_widget, width=5, height=4, dpi=100)
l.addWidget(dc)
else:
print('not prssed')
def clicked_begr(self, down):
if down:
l1 = QVBoxLayout(self.main_widget1)
dc1 = MyMplCanvas_begr(self.main_widget1, width=5, height=4, dpi=100)
l1.addWidget(dc1)
else:
print('not prssed2')
Instead of making 2 separate widgets you can add both of the graph widgets to the same layout and hide the one you don't want displayed (this is what i did bellow). Alternatively, you could make a layout and add or remove the widget you want displayed.
working code
from PyQt5 import QtWidgets, QtCore, QtPrintSupport, QtGui
import matplotlib
matplotlib.use("Qt5Agg")
from PyQt5 import QtCore
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, QRadioButton
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from PyQt5.QtWidgets import *
found_words = ["a", "b", "c", "d", "e", "f", "g", "h"]
cound_words = ['1','2','3','4','5','6','7','8']
found_pos = [ 'aaa','ssss']
count_pos = ['2','3']
class MyMplCanvas_begr(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
# self.compute_initial_figure()
self.axes.bar(found_words, cound_words, color=['r', 'b', 'k', 'y', 'g'])
self.axes.set_ylabel('Anzahl')
self.axes.set_xlabel('Begriffe')
self.axes.set_title('hi')
FigureCanvas.__init__(self, fig)
self.setParent(parent)
FigureCanvas.setSizePolicy(self,
QSizePolicy.Expanding,
QSizePolicy.Expanding)
FigureCanvas.updateGeometry(self)
class MyMplCanvas_pos(FigureCanvas):
"""Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
FigureCanvas.__init__(self, fig)
self.setParent(parent)
self.plot()
def plot(self):
colors = ['yellowgreen', 'lightcoral']
explode = (0.1, 0) # explode 1st slice
ax = self.figure.add_subplot(111)
ax.pie(count_pos, explode=explode, labels=found_pos, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140)
class main_statistic(QtWidgets.QWidget):
def __init__(self, parent=None):
super(main_statistic, self).__init__(parent)
self.radioButton_p = QRadioButton('Pos', self)
self.radioButton_p.setGeometry(QtCore.QRect(100, 10, 95, 20))
self.radioButton_p.setCheckable(True)
self.radioButton_p.toggled.connect(self.clicked_pos)
self.radioButton_b = QRadioButton('Word', self)
self.radioButton_b.setGeometry(QtCore.QRect(200, 10, 95, 20))
self.radioButton_b.setCheckable(True)
self.radioButton_b.toggled.connect(self.clicked_begr)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
self.main_widget = QWidget(self)
self.main_widget.setGeometry(QtCore.QRect(100, 100, 700, 500))
#self.editmenu.setHidden(True)
#self.sideMenu.setHidden(False)
l = QVBoxLayout()
self.dc = MyMplCanvas_pos( width=5, height=4, dpi=100)
self.dc.setHidden(True)
l.addWidget(self.dc)
self.dc1 = MyMplCanvas_begr( width=5, height=4, dpi=100)
self.dc1.setHidden(True)
l.addWidget(self.dc1)
self.main_widget.setLayout(l)
def clicked_pos(self):
self.dc.setHidden(False)
self.dc1.setHidden(True)
def clicked_begr(self):
self.dc.setHidden(True)
self.dc1.setHidden(False)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
mainWin = main_statistic()
mainWin.show()
sys.exit(app.exec_())

How to display multicursor on a QTabWidget?

The multicursor example
The question is : If I want the plot to be displayed on a tab of the QTabWidget,how to make the MultiCursor works?
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
import numpy as np
import sys
from matplotlib.gridspec import GridSpec
from matplotlib.widgets import MultiCursor
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class MainWindow(QMainWindow):
def __init__(self):
super().__init__(flags=Qt.Window)
self.setFont(QFont("Microsoft YaHei", 10, QFont.Normal))
self.setMinimumSize(1550, 950)
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
centralwidget = QWidget(flags=Qt.Widget)
centralwidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.setCentralWidget(centralwidget)
self.tabview = QTabWidget()
self.tabview.currentChanged.connect(self.onchange)
self.chart_widget0 = QWidget()
self.chart_widget1 = QWidget()
self.dc0 = my_Canvas(self.chart_widget0, width=20, height=8, dpi=100)
self.dc1 = my_Canvas(self.chart_widget1, width=20, height=8, dpi=100)
self.tabview.addTab(self.dc0, "MultiCursor")
self.tabview.addTab(self.dc1, "Cursor")
toplayout = QHBoxLayout()
toplayout.addWidget(self.tabview)
centralwidget.setLayout(toplayout)
def onchange(self,i):
if i == 0:
self.dc0.update_figure()
elif i == 1:
self.dc1.update_figure()
class my_Canvas(FigureCanvas):
def __init__(self, parent=None, width=10, height=7, dpi=100):
self.fig = plt.figure(figsize=(width, height), dpi=dpi)
gs = GridSpec(2, 1, height_ratios=[3, 1])
self.axes1 = plt.subplot(gs[0])
self.axes2 = plt.subplot(gs[1])
self.compute_initial_figure()
FigureCanvas.__init__(self, self.fig)
self.setParent(parent)
def compute_initial_figure(self):
self.axes1.cla()
self.axes2.cla()
def update_figure(self):
t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)
s2 = np.sin(4*np.pi*t)
self.axes1.plot(t, s1)
self.axes2.plot(t, s2)
multi = MultiCursor(self.fig.canvas, (self.axes1, self.axes2), color='r', lw=1)
self.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
w1 = MainWindow()
w1.show()
sys.exit(app.exec_())
How to modify the code to make the MultiCursor works, and could I control the display of the cursor by key or mousebutton click?
Further more, how to display the coordinate with the cursor?
As the Multicursor documentation tells us,
For the cursor to remain responsive you must keep a reference to it.
The easiest way is to make it a class variable,
self.multi = MultiCursor(...)

QWidget raise above matplotlib canvas

I am working on a project on which I have a GUI (coded by hand) with two tabs, and on each tab I have a different canvas (to plot different things in each tabs).
But, I added also some widgets on these tabs and when I add them to the layout, if I add the canvas at the same position of a button in the layout for example, I can click on this button anymore.
I know on PyQt it is possible to raise the level of the widget, so is there a way to do the same thing with a canvas?
Thank you in advance for your help. On this example, the "Quit" is active only on the right half.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self.Form)
LayoutG1 = QGridLayout()
LayoutTab1 = QGridLayout(self.tab1)
WidgetTemp = QWidget()
LayoutWidgetTemp = QGridLayout()
LayoutG1.addWidget(self.Bouton_quitter, 21, 29, 1, 2, Qt.AlignRight | Qt.AlignBottom)
LayoutG1.addWidget(self.canvas, 2, 10, 20, 20)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_Label, 0, 0, 1, 4)
LayoutWidgetTemp.addWidget(self.Widget_choixPalette_ComboBox, 1, 0, 1, 4)
WidgetTemp.setLayout(LayoutWidgetTemp)
LayoutG1.addWidget(WidgetTemp, 1, 18, 2, 4)
LayoutTab1.addLayout(LayoutG1, 0, 0, 1, 1)
self.tabWidget.addTab(self.tab1, " Tab1 ")
LayoutForm.addWidget(self.tabWidget, 1, 0, 1, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Operating system: windows 7 Pro
Matplotlib version: 4.0.4
Matplotlib backend: Qt5Agg
Python version: 3.6
Other libraries: PyQt5
Edit 25/10/17 : new code for example
Below is a version of your example script that fixes all the issues. Most of the problems are caused by a very muddled use of layouts. I had to completely
re-write the creation_layout method in order to get a sane starting point so I could see where the problems were. I also temporarily restored the background colour of the canvas to make it easier to see how the widgets are layed out relative to each other. I realize that it won't be easy to incorporate some of my changes into your real code. But hopefully it will give you some ideas on how to simplify your layout structure.
The most important fix is the use of subplots_adjust in the creation_figure method. This removes all the empty space at the top of the canvas, so there is no longer any need to try to position other widgets on top of it.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
class FenetrePrincipale(QWidget):
def __init__(self, parent=None):
super(FenetrePrincipale, self).__init__(parent)
self.setupUi(self)
# Fonction de configuration de la classe
def setupUi(self, Form):
self.Form = Form
Form.setMinimumSize(1220, 850)
self.creation_GUI()
self.creation_figure()
self.creation_layout()
self.tabWidget.setCurrentIndex(0)
self.Bouton_quitter.clicked.connect(self.close)
def resizeEvent(self, QResizeEvent):
self.tabWidget.setMinimumSize(QSize(self.width() - 20, self.height() - 60))
def creation_GUI(self):
self.tabWidget = QTabWidget()
self.tab1 = QWidget()
self.tabWidget.addTab(self.tab1, " Tab1 ")
self.Widget_choixPalette_Label = QLabel(self.tab1)
self.Widget_choixPalette_Label.setText("Text1")
self.Widget_choixPalette_ComboBox = QComboBox(self.tab1)
self.Widget_choixPalette_ComboBox.addItem("Try1")
self.Widget_choixPalette_ComboBox.addItem("Try2")
self.Bouton_quitter = QPushButton(self.tab1)
self.Bouton_quitter.setText("Quit")
def creation_layout(self):
LayoutForm = QGridLayout(self)
LayoutForm.addWidget(self.tabWidget, 0, 0, 1, 1)
LayoutTab1 = QGridLayout(self.tab1)
LayoutTab1.addWidget(self.Widget_choixPalette_Label, 0, 1, 1, 1)
LayoutTab1.addWidget(self.Widget_choixPalette_ComboBox, 1, 1, 1, 1)
self.Widget_choixPalette_ComboBox.setMinimumWidth(200)
LayoutTab1.addWidget(self.canvas, 2, 0, 1, 3)
LayoutTab1.addWidget(self.Bouton_quitter, 2, 3, 1, 1, Qt.AlignRight | Qt.AlignBottom)
LayoutTab1.setRowStretch(2, 1)
LayoutTab1.setColumnStretch(0, 1)
LayoutTab1.setColumnStretch(2, 1)
def creation_figure(self):
# Create figure (transparent background)
self.figure = plt.figure()
# self.figure.patch.set_facecolor('None')
self.canvas = FigureCanvas(self.figure)
self.canvas.setStyleSheet("background-color:transparent;")
# Adding one subplot for image
self.axe0 = self.figure.add_subplot(111)
self.axe0.get_xaxis().set_visible(False)
self.axe0.get_yaxis().set_visible(False)
# plt.tight_layout()
# Data for init image
self.imageInit = [[255] * 320 for i in range(240)]
self.imageInit[0][0] = 0
# Init image and add colorbar
self.image = self.axe0.imshow(self.imageInit, interpolation='none')
divider = make_axes_locatable(self.axe0)
cax = divider.new_vertical(size="5%", pad=0.05, pack_start=True)
self.colorbar = self.figure.add_axes(cax)
self.figure.colorbar(self.image, cax=cax, orientation='horizontal')
plt.subplots_adjust(left=0, bottom=0.05, right=1, top=1, wspace=0, hspace=0)
self.canvas.draw()
if __name__ == '__main__':
app = QApplication(sys.argv)
# QApplication.setStyle(QStyleFactory.create("plastique"))
form = FenetrePrincipale()
form.show()
sys.exit(app.exec_())
Just reverse the order you add things to the layout. Add the canvas first, then the button on top
LayoutForm.addWidget(canvas,1,0,1,6)
LayoutForm.addWidget(button,1,0,1,2)

Resources