PYQT plot not shown in grid - pyqt

I have a main window with a grid layout
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1850, 1700) # width, height
self.gl_main = QGridLayout(self.centralwidget)
w = ChartWidget(self)
self.gl_main.addWidget(w, 4, 0, 1, 1)
class ChartWidget(QWidget):
def __init__(self, parent=None):
super(ChartWidget, self).__init__(parent)
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure)
self.axes = self.figure.add_subplot(111)
self.axes.hold(False)
self.axes.plot(range(10), range(10), 'b')
self.canvas.draw()
self.show()
The chartwidget is not added to the gridlayout, though. I can briefly see it flash up on the screen, but then it is gone. how do I have to attach it?

adding the canvas to the gridlayout instead of the widget solved this.
w = ChartWidget(self)
self.gl_main.addWidget(w.canvas, 4, 0, 1, 1)

Related

PyQt5: Qstyle.CC_ScrollBar rendered in the wrong place

I am working with a listView and a custom delegate. Through the paint function I draw a set of control elements so that each row of the list acts as if it was a widget, without actually being one. This is crucial for performance since the list is composed of hundreds of thousands of elements, as pointed here and here.
The only problem is with QStyleOptionSlider complex control: if I ask for a CC.ScrollBar the control is rendered in the top left corner of the view and not where i want. If in QApplication.style().drawComplexControl(QStyle.CC_ScrollBar, self.scrollOptions, painter) i ask for a CC_Slider (instead of CC_ScrollBar) the control is rendered where expected.
I also tried to initialise the style from a real scroll widget but nothing changed.
I would like to know if I'm doing something wrong or if it's a problem with the library, since all the other controls i have painted work perfectly. The only difference I've noticed is that other elements (e.g. frame, label, pushbutton) have their own QStyleOption class while the scrollbar is merged with the slider class, but to quote the docs:
QStyleOptionSlider contains all the information that QStyle functions need to draw QSlider and QScrollBar.
Debug Info: Python 3.8.6 / PyQt 5.15.1 / Pyqt-tools 5.15.1.2 / Windows 10
Minimal example
from PyQt5.QtCore import QSize, Qt, QRect
from PyQt5.QtGui import QColor
from PyQt5.QtGui import QStandardItem
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QStyle
from PyQt5.QtWidgets import QStyledItemDelegate, QApplication, QStyleOptionFrame, \
QStyleOptionSlider
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.inferenceListView = QtWidgets.QListView(self.centralwidget)
self.inferenceListView.setGridSize(QtCore.QSize(0, 200))
self.inferenceListView.setObjectName("inferenceListView")
self.gridLayout.addWidget(self.inferenceListView, 0, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.setupProposals()
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
def setupProposals(self):
self.delegate = MyDelegate()
# self.delegate.initScroll(self.horizontalScrollBar)
model = QStandardItemModel(0, 0)
for index in range(0, 5000):
model.appendRow(QStandardItem(str(index)))
self.inferenceListView.setItemDelegateForRow(index, self.delegate)
self.inferenceListView.setModel(model)
class MyDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
self.frame = QStyleOptionFrame()
# ---------------
self.scrollOptions = QStyleOptionSlider()
self.scrollOptions.orientation = Qt.Vertical
self.scrollOptions.LayoutDirectionAuto = Qt.LayoutDirectionAuto
self.scrollOptions.orientation = Qt.Vertical
self.scrollOptions.state = QStyle.State_Enabled
self.scrollOptions.maximum = 10
self.scrollOptions.minimum = 0
self.scrollOptions.sliderValue = 0
def initScroll(self, scroll):
self.scrollOptions.initFrom(scroll)
def sizeHint(self, option, index):
return QSize(150, 200)
def paint(self, painter, option, index):
optX = option.rect.x()
optY = option.rect.y()
optH = option.rect.height()
optW = option.rect.width()
painter.fillRect(option.rect, QColor(100, 100, 100, 100))
painter.drawLine(optX, optY + optH, optX + optW, optY + optH)
QApplication.style().drawControl(QStyle.CE_ShapedFrame, self.frame, painter)
self.scrollOptions.rect = QRect(optX + 100, optY + 100, 50, 80)
# OK WITH CC_SLIDER
#QApplication.style().drawComplexControl(QStyle.CC_Slider, self.scrollOptions, painter)
# WRONG PLACE WITH CC_SCROLLBAR
QApplication.style().drawComplexControl(QStyle.CC_ScrollBar, self.scrollOptions, painter)
def editorEvent(self, event, model, option, index):
return False
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
EDIT
Seems to be a Windows-related problem at the moment. I had a colleague with a Mac run the code above and the scrollbar is drawn in the correct place. I attach an image of what happens on Windows in the two cases:
It seems like a possibly incomplete implementation of subControlRect in QCommonStyle, as the rectangle returned for CC_Slider is always positioned on the top left (see source).
A possible solution can be to use a proxy style and return a translated rectangle if the subrect is not contained in the option rect:
class ProxyStyle(QtWidgets.QProxyStyle):
def subControlRect(self, cc, opt, sc, widget=None):
r = super().subControlRect(cc, opt, sc, widget)
if cc == self.CC_ScrollBar and not opt.rect.contains(r):
r.translate(opt.rect.topLeft())
return r
# ...
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ProxyStyle())

Figure Canvas widget not showing up

I'm trying to embed a matplotlib animation into my PyQt5-GUI. Here's my class of the animation:
class AnimationCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.fig.set_facecolor('#1a1a1a')
self.ax = self.fig.add_subplot(111,polar=True)
self.ax.set_facecolor('#333333')
self.ax.set_ylim(0,1)
self.ax.tick_params(axis='x', labelsize=8,colors='#ffffff')
self.ax.set_yticks([]) # Shows no r values
self.ax.set_xticks(np.pi/180. * np.linspace(0, 360, 18, endpoint=False)) # A line every 20 degrees
self.ax.grid(color='#595959', linestyle='-.', linewidth=0.7)
self.ax.set_title("Position of the speakers", pad = 8,
fontdict= dict(
color = '#ffffff',
fontsize = 15,
fontweight = 0.9))
self.line, = self.ax.plot([], [], marker = 'X', color= '#ffffe6',markersize=15)
super(AnimationCanvas, self).__init__(self.fig)
When I use the following code in a MainWindow class (that handles the animation) to set it as a central widget it shows, no problem there. But I want to embed it into my existing GUI so that it's not the central widget. When I try to give it an absolute position by using setGeometry() or move(), it just doesn't show up anymore. I've also tried using QVBoxLayout and adding self.canvas to the layout, that works too, but I don't know how to stylize it properly so that it doesn't look like a huge mess with those unwanted whitespaces.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self,xdata, *args, **kwargs,):
super(MainWindow, self).__init__(*args, **kwargs)
widget = QWidget()
vbox = QVBoxLayout(widget)
self.canvas = AnimationCanvas(self, width=5, height=4, dpi=100)
self.setCentralWidget(self.canvas)
vbox.addWidget(self.canvas)
self.angles = xdata*(3.14/180)
self.ydata = np.ones(len(self.angles))*0.96
self.index = 0
self.line = self.canvas.line
self.plot_button = QtWidgets.QPushButton(self.canvas)
self.plot_button.setGeometry(QtCore.QRect(5, 5, 50, 25))
self.plot_button.setText("plot")
self.plot_button.setObjectName("plot")
self.plot_button.clicked.connect(self.plot_animation)
vbox.addWidget(self.plot_button)
self.setCentralWidget(widget)
self.setGeometry(300, 300, 800, 600)
Questions:
Why doesn't setGeometry() work in this instance and how to fix it.
If I have to use QVBoxLayout, what's the best way to achieve a nice look? addSpacing() and addStretch() everywhere seems like a bit of an overkill to me.
Thanks in advance!
If you want to place your AnimationCanvas directly on the central widget then call setParent in the constructor.
class AnimationCanvas(FigureCanvas):
def __init__(self, parent=None, width=5, height=4, dpi=100):
...
super(AnimationCanvas, self).__init__(self.fig)
self.setParent(parent)
And construct the AnimationCanvas with parent widget, the central widget of the QMainWindow. The call to setGeometry on the QPushButton will now work, it will be placed on the AnimationCanvas at position (5, 5) with the size (50, 25).
class MainWindow(QtWidgets.QMainWindow):
def __init__(self,xdata=None, *args, **kwargs,):
super(MainWindow, self).__init__(*args, **kwargs)
widget = QWidget()
self.setCentralWidget(widget)
self.canvas = AnimationCanvas(parent=widget, width=5, height=4, dpi=100)
self.plot_button = QtWidgets.QPushButton(self.canvas)
self.plot_button.setGeometry(QtCore.QRect(5, 5, 50, 25))
self.setGeometry(300, 300, 800, 600)
If you use a layout then setGeometry will usually be ignored because the layout manages the position and size of all its widgets. However, you could add only the AnimationCanvas to the layout and not the QPushButton, allowing it to stay on the AnimationCanvas with the exact geometry you specify.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self,xdata=None, *args, **kwargs,):
super(MainWindow, self).__init__(*args, **kwargs)
widget = QWidget()
self.setCentralWidget(widget)
vbox = QVBoxLayout(widget)
self.canvas = AnimationCanvas(width=5, height=4, dpi=100)
vbox.addWidget(self.canvas)
self.plot_button = QtWidgets.QPushButton(self.canvas)
self.plot_button.setGeometry(QtCore.QRect(5, 5, 50, 25))
self.setGeometry(300, 300, 800, 600)

scaledToHeight() does not scale to height - PyQt5

I'm trying to render my image, but scaled to the height of the current window and keeping with aspect ratio.
The following does just that, but does not actually scale the image:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Img(QWidget):
def __init__(self, parent=None):
super(Img, self).__init__(parent)
self.setAttribute(Qt.WA_StyledBackground, True)
self.setStyleSheet('background-color: orange')
window = QDesktopWidget().screenGeometry(0)
height = window.height()
layout = QVBoxLayout(self)
pixmap = QPixmap("test.png")
pixmap = pixmap.scaledToHeight(height)
label = QLabel()
label.setPixmap(pixmap)
label.setScaledContents(True)
layout.addWidget(label)
print(height)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
img = Img()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(img)
layout.setAlignment(Qt.AlignCenter)
content = QWidget()
content.setLayout(layout)
w,h = (1024,670)
self.resize(w,h)
self.setCentralWidget(content)
self.show()
if __name__ == '__main__':
app = QApplication([])
window = MainWindow()
app.exec_()
Although it prints out height is 1050, what I see is a huge image and the window expands down past my monitor. Can someone explain the issue and what needs to happen for scaledToHeight()?
Here is an example of what I see:
And here is the actual image:
The image gets cut off because it gets extended past the monitor screen, and is also stretched oddly.

how i can get cursor coordinate and using mouse position in other panel in wxpython

I am trying to put a cursor and rectangle patch that can moved and that can display the coordinates or rather pixel of an image select and display on another panel Paneltwo in the textctrl
each time I move the mouse ( automatically rectangel) the pixels of each point are displayed on the other panel
the first problem that the rectangle can't moved with mouse !
the second that what i need to do is when the rectangle moved with mouse i can visualize the pixel or position of mouse ( rectangle) in panel two textctrl !
how can I do that ?
that part of code :
import wx
from numpy import arange, sin, pi,cos
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.widgets import RectangleSelector
from matplotlib.figure import Figure
class MainFrame(wx.Frame):
def __init__(self, parent ):
wx.Panel.__init__(self, parent,name="Main", size = (600,400))
self.Panel = Panel(self)
class Panel(wx.Panel):
def __init__(self,parent):
super().__init__(parent)
panel = wx.Panel(self)
self.panel_two = PanelTwo(parent=self)
self.canvas_panel = CanvasPanel(self)
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.panel_two,1,wx.EXPAND)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel)
sizer.Add(canvas_sizer)
self.SetSizerAndFit(sizer)
self.Show()
class CanvasPanel(wx.Panel):
""" Panel du graphique matplotlib """
def __init__(self, parent , size=(200,250)):
super().__init__(parent)
self.figure = Figure(figsize =(4,3))
self.canvas = FigureCanvas(self, -1, self.figure)
self.axes = self.figure.add_subplot(111)
self.Size = self.canvas.Size
self.parent = parent
t = arange(0.5, 3.0, 0.01)
s = cos(2 * pi * t)
self.axes.plot(t, s)
#can'tmove rectangel with mouse
def on_press(self,event):
xpress, ypress = event.xdata, event.ydata
w = rect.get_width()
h = rect.get_height()
rect.set_xy((xpress-w/2, ypress-h/2))
ax.lines = []
ax.axvline(xpress, c='b')
ax.axhline(ypress, c='b')
self.fig.canvas.draw()
self.fig = plt.figure
self.axes = plt.subplot(111)
self.axes.imshow(t,s)
self.fig.canvas.mpl_connect('button_press_event',on_press)
self.rect = patches.Rectangle((x,y),0.01,0.01,linewidth=1,edgecolor='g',facecolor='none')
self.axes.add_patch(rect)
self.plt.show()
class PanelTwo(wx.Panel): #here when i need to visualize pixel and coordinator cursor
def __init__(self,parent):
wx.Panel.__init__(self,parent,size=(200,250))
self.text_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|
wx.TE_RICH2, size=(200,170), pos = (40,20))
lbl = wx.StaticText(self,label="Coordinato cursor & Pixel " , pos=(40,0))
app = wx.App()
frame = MainFrame(None).Show()
app.MainLoop()
THANK YOU
Try this and see if it moves you in the right direction.
Note: I have no idea how you are going to retrieve the pixel position from that plot.
Perhaps someone else, who actually knows what they are doing with matplotlib, because I certainly don't, can help with that.
import wx
from numpy import arange, sin, pi,cos
import numpy as np
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.patches as patches
class MainFrame(wx.Frame):
def __init__(self, parent ):
wx.Panel.__init__(self, parent,name="Main", size = (800,400))
self.Panel = Panel(self)
class Panel(wx.Panel):
def __init__(self,parent):
super().__init__(parent)
panel = wx.Panel(self)
self.panel_two = PanelTwo(parent=self)
self.canvas_panel = CanvasPanel(self)
canvas_sizer = wx.BoxSizer(wx.HORIZONTAL)
canvas_sizer.Add(self.canvas_panel,1,wx.EXPAND)
canvas_sizer.Add(self.panel_two,1,wx.EXPAND)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(panel)
sizer.Add(canvas_sizer)
self.SetSizerAndFit(sizer)
self.Show()
class CanvasPanel(wx.Panel):
""" Panel du graphique matplotlib """
def __init__(self, parent , size=(200,250)):
super().__init__(parent)
self.figure = Figure(figsize =(4,3))
self.canvas = FigureCanvas(self, -1, self.figure)
self.axes = self.figure.add_subplot(111)
self.Size = self.canvas.Size
self.parent = parent
t = arange(0.5, 3.0, 0.01)
s = cos(2 * pi * t)
self.axes.plot(t, s)
self.canvas.mpl_connect('button_press_event', self.on_press)
x = y = 0.2
self.rect = patches.Rectangle((x, y), 0.4, 0.4, alpha=1, fill=None, label='Label')
self.axes.add_patch(self.rect)
self.axes.plot()
def on_press(self, click):
x1, y1 = click.xdata, click.ydata
self.parent.panel_two.Update(x1,y1)
self.rect.set_x(x1 - 0.2) #Move the rectangle and centre it on the X click point
self.rect.set_y(y1 - 0.2) #Move the rectangle and centre it on the Y click point
self.axes.plot()
self.canvas.draw()
class PanelTwo(wx.Panel): #here when i need to visualize pixel and coordinator cursor
def __init__(self,parent):
wx.Panel.__init__(self,parent,size=(300,250))
self.text_ctrl = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.BORDER_SUNKEN|wx.TE_READONLY|wx.TE_RICH2, size=(200,170))
lbl = wx.StaticText(self,label="Coordinato cursor & Pixel ")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(lbl,0, wx.ALIGN_CENTRE,10)
sizer.Add(self.text_ctrl,0, wx.ALIGN_CENTRE,10)
self.SetSizer(sizer)
def Update(self,x1,y1):
self.text_ctrl.SetValue("Mouse click at;\nX "+str(x1)+"\nY "+str(y1))
app = wx.App()
frame = MainFrame(None).Show()
app.MainLoop()

How do I auto fit a Matplotlib figure inside a PySide QFrame?

I'm creating a simple PySide application that also uses MatPlotLib. However, when I add the figure into a QFrame, the figure doesn't automatically fit to the frame:
My graph is created using the following code:
class GraphView(gui.QWidget):
def __init__(self, name, title, graphTitle, parent = None):
super(GraphView, self).__init__(parent)
self.name = name
self.graphTitle = graphTitle
self.dpi = 100
self.fig = Figure((5.0, 3.0), dpi = self.dpi, facecolor = (1,1,1), edgecolor = (0,0,0))
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.Title = gui.QLabel(self)
self.Title.setText(title)
self.layout = gui.QVBoxLayout()
self.layout.addStretch(1)
self.layout.addWidget(self.Title)
self.layout.addWidget(self.canvas)
self.setLayout(self.layout)
def UpdateGraph(self, data, title = None):
self.axes.clear()
self.axes.plot(data)
if title != None:
self.axes.set_title(title)
self.canvas.draw()
And it's added to the main Widget like so:
# Create individual Widget/Frame (fftFrame)
fftFrame = gui.QFrame(self)
fftFrame.setFrameShape(gui.QFrame.StyledPanel)
self.FFTGraph = GraphView('fftFrame', 'FFT Transform:', 'FFT Transform of Signal', fftFrame)
Here's a working code sample that shows you how to get it working. I first thought it was because of the stretch you added to the layout, which will use up the additional space around the other widgets. But when I removed it, it still wouldn't resize. The 'easy' solution is to add a resizeEvent, which lets you define the size of your GraphView widget. In this case I just set its geometry to be that of the QFrame, though you might want to add some padding and make sure you set a sensible minimum size for the QFrame.
from PySide import QtGui
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.fft_frame = FftFrame(self)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.fft_frame)
self.setLayout(self.layout)
self.setCentralWidget(self.fft_frame)
class FftFrame(QtGui.QFrame):
def __init__(self, parent=None):
super(FftFrame, self).__init__(parent)
self.setFrameShape(QtGui.QFrame.StyledPanel)
self.parent = parent
self.graph_view = GraphView('fftFrame', 'FFT Transform:', 'FFT Transform of Signal', self)
def resizeEvent(self, event):
self.graph_view.setGeometry(self.rect())
class GraphView(QtGui.QWidget):
def __init__(self, name, title, graph_title, parent = None):
super(GraphView, self).__init__(parent)
self.name = name
self.graph_title = graph_title
self.dpi = 100
self.fig = Figure((5.0, 3.0), dpi = self.dpi, facecolor = (1,1,1), edgecolor = (0,0,0))
self.axes = self.fig.add_subplot(111)
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self)
self.Title = QtGui.QLabel(self)
self.Title.setText(title)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.Title)
self.layout.addWidget(self.canvas)
self.layout.setStretchFactor(self.canvas, 1)
self.setLayout(self.layout)
self.canvas.show()
def update_graph(self, data, title = None):
self.axes.clear()
self.axes.plot(data)
if title != None:
self.axes.set_title(title)
self.canvas.draw()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()

Resources