Problems with matplotlib blitted cursor embeded in PyQt6 gui - python-3.x

I´m currently developing a software to process spectroscopic data using python. I´m using PyQt6 to design the gui and matplotlib to plot the data. To simplify the data selection I added a cursor widget from matplotlib to the axes. For improved performace I used the blitting mode.
Here´s a minimal code example:
# Import packages
from PyQt6.QtWidgets import QMainWindow, QApplication, QWidget, QVBoxLayout
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.widgets import Cursor
from matplotlib.figure import Figure
import numpy as np
# Main window
class MainWindow(QMainWindow):
def __init__(self):
# Initialize QMainWindow
super().__init__()
# Get MplWidget
self.mpl_widget = MplWidget()
# Set MplWidget as central widget
self.setCentralWidget(self.mpl_widget)
# Create axes
self.ax = self.mpl_widget.canvas.figure.add_subplot(111)
# Reference Cursor
self.cursor = Cursor(self.ax, useblit=True)
# Plot some random stuff
x, y = np.random.rand(100), np.random.rand(100)
self.ax.scatter(x, y)
# Show app
self.show()
# MplCanvas
class MplWidget(QWidget):
def __init__(self, parent=None):
# Initialize QWidget
super().__init__(parent)
# Create canvas
self.canvas = FigureCanvas(Figure())
# Set constrained layout
self.canvas.figure.set_constrained_layout(True)
# Create layout
layout = QVBoxLayout()
# Add canvas
layout.addWidget(self.canvas)
# Set layout
self.setLayout(layout)
# Initialize app
app = QApplication([])
UI = MainWindow()
app.exec()
Generally everything works fine, the cursor appears when hovering the axis and dissapears when leaving it, but when the mouse enters the axis, the whole axis content moves a little bit. Sometimes the figure edges also change their colors, e.g. from black to gray.
Here are two gifs to explain the phenomenon:
Does anyone know how to solve this problem?

Related

Matplotlib Navigation Toolbar in wxPython Panel

I am working on a GUI (developed with wxPython) where you can plot graphs on different panels. At the moment I have this:
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib import pyplot as plt
import numpy as np
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None,-1,'Plot',size=(1000,800))
# Main Panel (It will contain other elements besides the plotting panel)
self.mainPanel = wx.Panel(self,-1,size=(1000,800))
self.mainPanel.SetBackgroundColour('gray')
# Plotting panel
self.plottingPanel = wx.Panel(self,-1,pos=(50,20),size=(500,400))
self.plottingPanel.SetBackgroundColour('white')
# Plot example
figure = plt.figure()
axes = figure.add_subplot(111)
t = np.arange(0.0, 3.0, 0.01); s = np.cos(2 * np.pi * t);axes.plot(t,s)
plt.title('Cosine')
plt.xlabel('x');plt.ylabel('y')
# Canvas
canvas = FigureCanvas(self.plottingPanel,-1,figure)
# Navegation toolbar
navToolbar = NavigationToolbar2Wx(canvas)
navToolbar.DeleteToolByPos(6);navToolbar.DeleteToolByPos(2);navToolbar.DeleteToolByPos(1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(canvas)
sizer.Add(navToolbar)
class App(wx.App):
def OnInit(self):
self.Frame = Frame()
self.Frame.Show()
self.SetTopWindow(self.Frame)
return True
def main():
app = App()
app.MainLoop()
if __name__ == '__main__':
main()
When I run the script get this:
I have colored the plotting panel white to highlight it. How can the plot size be adapted to the panel size?
I want to get something like this (this is a montage):
On the other hand, I managed to eliminate from the bar some buttons that are unnecessary for what I need but the bar does not work, that is, when pressing the buttons nothing happens :(
Thanks for your help
It is possible to set parameters when the container for the plot elements (matplotlib.figure.Figure) is created.
e.g. figsize sets the figure dimension in inches and tight_layout adjust the sub plots in tight layout.
figure = plt.figure(figsize = (4, 3), tight_layout=True)
Alternatively you can set the position of the matplotlib.axes.Axes object by .set_position:
figure = plt.figure()
axes = figure.add_subplot(111)
axes.set_position(pos = [0.15,0.3,0.55,0.55], which='both')

PyQtGraph: prevent QScrollArea scrolling when panning plot

In PyQtGraph you can zoom into the plots with the scroll wheel. However, when embedding PyQtGraph inside a QScrollArea, scrolling both zooms into the hovered plot AND scrolls the QScrollArea.
Minimal reproducable code:
from PyQt5.QtWidgets import QApplication, QScrollArea, QMainWindow
import pyqtgraph as pg
app = QApplication([])
window = QMainWindow()
scroll = QScrollArea(window)
window.setCentralWidget(scroll)
frame = pg.GraphicsLayoutWidget()
plot = frame.addPlot().plot([1,2,3], [2,5,10])
scroll.setWidget(frame)
scroll.setWidgetResizable(True)
frame.setFixedHeight(600)
window.setFixedHeight(500)
window.show()
app.exec_()
I tried googling the issue, and I found a way to stop panning and allow scrolling, however I want it the other way around: prevent scrolling and allow panning.
Is it possible inside PyQtGraph to stop QScrollArea scrolling when a plot is hovered?
The solution is to cancel the wheelEvent of QScrollArea:
from PyQt5.QtWidgets import QApplication, QScrollArea, QMainWindow
import pyqtgraph as pg
class ScrollArea(QScrollArea):
def wheelEvent(self, event):
pass
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
window = QMainWindow()
scroll = ScrollArea(window)
window.setCentralWidget(scroll)
frame = pg.GraphicsLayoutWidget()
plot = frame.addPlot().plot([1, 2, 3], [2, 5, 10])
scroll.setWidget(frame)
scroll.setWidgetResizable(True)
frame.setFixedHeight(600)
window.setFixedHeight(500)
window.show()
sys.exit(app.exec_())

real-time plotting to a TK window using python3

I am trying to plot in real-time to a tkinter window in python3.
I am attempting to wrap my window in a class.
my code shows the graph, but data is not being plotted. Here is the code:
#! /usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
import tkinter
import time
import numpy as np
matplotlib.use("TkAgg")
class Window(tkinter.Frame):
def __init__(self, master = None):
tkinter.Frame.__init__(self, master)
self.master = master
self.master.grid()
self.fig = Figure(figsize=(5,5), dpi=100)
self.ax = self.fig.add_subplot(111)
self.t, self.y = [], []
self.init_window()
self.ani = animation.FuncAnimation(self.fig, self.animate, interval=1000)
def init_window(self):
# set window title
self.master.title("PlotSomething")
# run show_graph
self.show_graph()
def animate(self):
self.t.append(time.time())
self.y.append(np.random.random())
self.ax.clear()
self.ax.plot(self.t,self.y)
def show_graph(self):
canvas = FigureCanvasTkAgg(self.fig, self.master)
canvas.get_tk_widget().grid(row=1, column=1)
canvas.draw()
def client_exit(self):
sys.exit()
def main(args):
root = tkinter.Tk()
app = Window(root)
root.mainloop()
return 0
if __name__=="__main__":
sys.exit(main(sys.argv[1:]))
Please let me know what the issue is.
Bonus if you can help me get the real-time plot stuff into a separate class that I can call with my tkinter window.
I obviously need some learning on how to create and call classes using tkinter.
As always thank you for your support!

Kivy custom widget behaving differently when used in box layout

I was trying to make an window with two block with vertical box layout, with upper widget larger than lower one. But rather than that, the widgets are stacking on top of other at the bottom left corner, both being of same size.
Here is my code
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.codeinput import CodeInput
from kivy.core.window import Window
from kivy.uix.button import Button
Window.maximize()
class Editor(Widget):
def __init__(self, *arg, **kwarg):
super(Editor, self).__init__(*arg, **kwarg)
self.size_hint= (1, 0.8)
self.add_widget(Button(text= "1"))
class Output(Widget):
def __init__(self, *arg, **kwarg):
super(Output, self).__init__(*arg, **kwarg)
self.size_hint= (1, 0.2)
self.add_widget(Button(text= "2"))
class IDE(BoxLayout):
def __init__(self, *arg, **kwarg):
super(IDE, self).__init__(*arg, **kwarg)
self.orientation= "vertical"
box1= Editor()
self.add_widget(box1)
box2= Output()
self.add_widget(box2)
class MainApp(App):
def build(self):
return IDE()
if __name__=="__main__":
MainApp().run()
(The buttons are used in Output and Editor class just to check their respective parent widget position in resultant window)
Someone help me to figure out what am I doing wrong.
Your Editor and Output behave as expected, but are completely invisible. What you are seeing are their own child widgets, the two Buttons, each of which has no position or size applied to it and so takes the default of pos (0, 0) and size (100, 100).
Make the Editor and Output classes some kind of layout, e.g. FloatLayout, or simply remove them and add the Buttons directly to the BoxLayout.

Why doesn't this custom QWidget display correctly

I'm retrying this question with a much better code example.
The code below, in its current form, will display a green shaded QWidget in a window, which is what I want. However, when commenting out the line:
self.widget = QWidget(self.centralwidget)
and uncommenting,
self.widget = Widget_1(self.centralwidget)
the green box doesn't display. The Widget_1 class is a simple subclass of QWidget, so I'm trying to wrap my head around where the breakdown is occurring. There are no error messages, and the print("Test") line within the Widget_1 class is outputting just fine, so I know everything is being called properly.
I'm not looking to use any type of automated layouts for reasons I don't need to go into here. Can you help me to understand why the green rectangle isn't displaying, and what correction I would need to make in order to utilize the Widget_1 class?
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import QRect
import sys
class Main_Window(object):
def setupUi(self, seating_main_window):
seating_main_window.setObjectName("seating_main_window")
seating_main_window.setEnabled(True)
seating_main_window.resize(400, 400)
self.centralwidget = QWidget(seating_main_window)
self.centralwidget.setObjectName("centralwidget")
########### The following two lines of code are causing the confusion #######
# The following line, when uncommented, creates a shaded green box in a window
self.widget = QWidget(self.centralwidget) # Working line
# The next line does NOT create the same shaded green box. Where is it breaking?
# self.widget = Widget_1(self.centralwidget) # Non-working line
self.widget.setGeometry(QRect(15, 150, 60, 75))
self.widget.setAutoFillBackground(False)
self.widget.setStyleSheet("background: rgb(170, 255, 0)")
self.widget.setObjectName("Widget1")
seating_main_window.setCentralWidget(self.centralwidget)
class Widget_1(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setMinimumSize(10, 30) # I put this in thinking maybe I just couldn't see it
print("Test") # I see this output when run when Widget_1 is used above
class DemoApp(QMainWindow, Main_Window):
def __init__(self):
super().__init__()
self.setupUi(self)
if __name__ == '__main__': # if we're running file directly and not importing it
app = QApplication(sys.argv) # A new instance of QApplication
form = DemoApp() # We set the form to be our ExampleApp (design)
form.show() # Show the form
app.exec_() # run the main function
Accoriding to this Qt Wiki article:
How to Change the Background Color of QWidget
you must implement paintEvent in a custom QWidget subclass in order to use stylesheets. Also, since the widget is not part of a layout, you must give it a parent, otherwise it will not be shown. So your Widget_1 class must look like this:
from PyQt5.QtWidgets import QStyleOption, QStyle
from PyQt5.QtGui import QPainter
class Widget_1(QWidget):
def __init__(self, parent=None):
super().__init__(parent) # set the parent
print("Test")
def paintEvent(self, event):
option = QStyleOption()
option.initFrom(self)
painter = QPainter(self)
self.style().drawPrimitive(QStyle.PE_Widget, option, painter, self)
super().paintEvent(event)

Resources