I have a matplotlib.pyplot object inside a QVBoxLayout in PyQt5 widget. There are small spaces along the edge which is really annoying. How do I remove it?
See the screensnap below.
I already tried setlayoutSpacing(0), which have no effect.
Here is the breviate code (Cutout many data processing codes):
class MpWidget_SCF(Qt.QWidget):
def __init__(self, parent=None, y=[]):
super(MpWidget_SCF, self).__init__()
self.setParent(parent)
self.dpi = 50
self.fig = MpPyplot.figure(figsize=(2, 2), dpi=self.dpi, )
self.SCF_subplot = MpPyplot.subplot(1, 1, 1)
self.canvas = MpFigureCanvas(self.fig)
self.canvas.setParent(self)
self.SCF_subplot.clear()
self.SCF_subplot.plot(range(len(y)), y, 'r')
self.canvas.draw()
self.vLayout = Qt.QVBoxLayout()
self.vLayout.addWidget(self.canvas)
self.vLayout.setSpacing(0)
self.setLayout(self.vLayout)
class myWidget(Qt.QWidget):
def __init__(self):
super(myWidget, self).__init__()
self.main = Ui_OutputWindow_Form()
self.main.setupUi(self)
self.main.SCF_Graphic_Widget = MpWidget_SCF(self)
self.main.verticalLayout_2.addWidget(self.main.SCF_Graphic_Widget)
self.show()
You can remove the default margins:
self.vLayout.setContentsMargins(0, 0, 0, 0)
Related
I have a matplotlib image embedded in PyQt:
But am now having trouble updating it.
The UI and the initial embedding is set up as follows:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
# Ui_MainWindow is a python class converted from .ui file
def __init__(self, *args, obj=None, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
self.imageDisp = ImageDisplay() # Class definition below
layoutImage = self.Image_Area.layout() # Image_Area is a Qt Group Box
if layoutImage is None:
layoutImage = QVBoxLayout(self.Image_Area)
ax_img = self.imageDisp.displayImage()
canvas_image = FigureCanvas(ax_img.figure)
layoutImage.addWidget(canvas_image)
self.NextImageButton.clicked.connect(self.inc)
def inc(self):
self.imageDisp.nextImage()
layoutImage = self.Image_Area.layout()
if layoutImage is None:
layoutImage = QVBoxLayout(self.Image_Area)
ax_img = self.imageDisp.UpdateImage()
canvas_image = FigureCanvas(ax_img.figure)
self.imageDisp.draw()
layoutImage.addWidget(canvas_image)
And the ImageDisplay class is defined as follows:
class ImageDisplay(FigureCanvas): # FigureCanvas is matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg
def __init__(self):
# Other attributes
fig = Figure()
self.fig = fig
self.axes = fig.add_subplot(111)
def nextImage(self):
# Do something here
self.UpdateImage()
def UpdateImage(self):
self.axes.imshow(np.asarray(IMAGE))
return self.axes
def displayImage(self):
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
self.axes.imshow(np.asarray(IMAGE))
return self.axes
This does not update the image, but addWidget would create a new widget on top of it, resulting in a stacked plot:
I tried to find ways to remove the widget but to no avail so far. Is there a way I can properly remove this widget and make a new one for the updated image? Or is there another way to display and update an image embedded in PyQt?
try this instead
from PyQt5 import QtWidgets
from PyQt5.QtCore import *
import sys
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
import numpy as np
from matplotlib import image
import matplotlib.pyplot as plt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, obj=None, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.imageDisp = ImageDisplay(self, width=5, height=4, dpi=100)
self.imageDisp.displayImage()
self.NextImageButton=QtWidgets.QPushButton('next image',self)
self.NextImageButton.clicked.connect(self.inc)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.imageDisp)
layout.addWidget(self.NextImageButton)
widget = QtWidgets.QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
self.show()
def inc(self):
self.imageDisp.nextImage()
class ImageDisplay(FigureCanvas): # FigureCanvas is matplotlib.backends.backend_qt5agg.FigureCanvasQTAgg
def __init__(self, parent=None, width=5, height=4, dpi=100):
self.start=0
self.files=['ball.png','image.jpg']
self.fig = Figure(figsize=(width, height), dpi=dpi)
self.fig.clear()
self.axes = self.fig.add_subplot(111)
self.axes.clear()
self.axes.axis('off')
plt.draw()
super(ImageDisplay, self).__init__(self.fig)
def nextImage(self):
# Do something here
self.IMAGE = image.imread(self.files[self.start%2])
self.UpdateImage()
def UpdateImage(self):
self.start+=1
self.axes.clear()
self.axes.axis('off')
self.axes.imshow(np.asarray(self.IMAGE))
self.draw()
def displayImage(self):
self.IMAGE = image.imread(self.files[self.start%2])
self.start+=1
self.axes.clear()
self.axes.axis('off')
self.axes.imshow(np.asarray(self.IMAGE))
self.draw()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
sys.exit(app.exec_())
I'm trying to display a large amont of signals in a matplotlib figure. I have a value in my interface to set the spacing that exists between signals. My issue is, if that value is too large, I get this error:
ValueError: Image size of 1266x121000 pixels is too large. It must be less than 2^16 in each direction.
and the soft crashs. What I would like to do is avoiding the soft to crash. Does it exist a way to detect if that error will occur and then tell to the user that the value is too large? I could also adjust the dpi for large figure but the error could still comes anyway.
here a minimal example:
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
import numpy as np
import matplotlib
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
class Mon_Graph(QMainWindow):
def __init__(self, parent=None):
super(Mon_Graph, self).__init__()
self.parent = parent
self.centralWidget = QWidget()
self.setCentralWidget(self.centralWidget)
self.mainHBOX = QHBoxLayout()
self.spacing_e = QLineEdit('1')
self.mainHBOX.addWidget(self.spacing_e)
self.graph= graphic(self)
self.mainHBOX.addWidget(self.graph)
self.centralWidget.setLayout(self.mainHBOX)
self.Sigs = np.random.rand(100,100)*10
self.spacing_e.editingFinished.connect(self.update_sigs)
def update_sigs(self):
self.graph.update()
class graphic(QGraphicsView):
def __init__(self, parent=None):
super(graphic, self).__init__(parent)
self.parent = parent
self.scene = QGraphicsScene(self)
self.setScene(self.scene)
self.figure = plt.figure(facecolor='white')
self.canvas = FigureCanvas(self.figure)
self.widget = QWidget()
self.widget.setLayout(QVBoxLayout())
self.scroll = QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.axes = self.figure.add_subplot(111)
self.axes.set_xlabel("Time (s)")
layout = QVBoxLayout()
layout.addWidget(self.scroll)
self.setLayout(layout)
def update(self):
Sigs = self.parent.Sigs
self.figure.clear()
plt.figure(self.figure.number)
plt.subplots_adjust(left=0.1, bottom=0.01, right=1, top=1, wspace=0.0 , hspace=0.0 )
self.axes = plt.subplot(1, 1, 1)
spacing = float(self.parent.spacing_e.text())
for i in range(Sigs.shape[0]):
plt.plot(Sigs[i,:]+i*spacing)
self.axes.autoscale(enable=True, axis='both', tight=True)
self.canvas.setGeometry(0, 0, self.parent.width()-100, (self.parent.height()-100)*spacing)
self.canvas.draw_idle()
def main():
app = QApplication(sys.argv)
app.setStyle('Windows')
ex = Mon_Graph(app)
ex.showMaximized()
sys.exit(app.exec())
if __name__ == '__main__':
main()
If I put 20 in the lineedit then it is ok but if I put 200, it crashes with the previous error.
Just add a condition in your update method to check if any of both dimensions of Sigs surpass 2^16 (before the call to plot)
I want to plot a data-frame in one of the tabs of my application. I'm using python 3.6.9 and PyQt5.
I implemented a TabWidget from another answer. In one of those tabs I used MplCanvas from Plotting with Matplotlib to plot a data from my data-frame. Needed classes:
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
self.setTabBar(TabBar(self))
self.setTabPosition(QtWidgets.QTabWidget.West)
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=6.4, height=4.8, dpi=394):
fig = Figure(figsize= (width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
class TabBar(QtWidgets.QTabBar):
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
return s
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
painter.save()
s = opt.rect.size()
s.transpose()
r = QtCore.QRect(QtCore.QPoint(), s)
r.moveCenter(opt.rect.center())
opt.rect = r
c = self.tabRect(i).center()
painter.translate(c)
painter.rotate(90)
painter.translate(-c)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt)
painter.restore()
The QMainWindow of application:
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'PLOT APP'
self.left = 0
self.top = 0
self.width = 1024
self.height = 600
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.tab_widget = MyTabWidget(self, self.width, self.height)
self.setCentralWidget(self.tab_widget)
self.show()
And to run the application:
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
And the MyTabWidget class in which I implemented my code:
class MyTabWidget(QWidget):
def __init__(self, parent, width, height):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# creating a tab bar
self.tab = TabWidget()
self.new_df = self.prepare_data()
self.layout_gauges = QHBoxLayout(self)
self.layout_plot = QVBoxLayout(self)
self.canvas = MplCanvas(self, width=10, height=8, dpi=100)
self.toolbar = NavigationToolbar(self.canvas, self)
self.comboBox = QtWidgets.QComboBox(self)
# widgets associated with tab
self.graphs_tab = QWidget()
# assigning tab to tab-bar
self.tab.addTab(self.graphs_tab, "Graphs")
# graphs tab
self.graphs_tab.layout = QVBoxLayout(self)
self.layout_combobox_time_filter = QHBoxLayout(self)
self.layout_combobox = QHBoxLayout(self)
self.draw_graphs()
self.graphs_tab.setLayout(self.graphs_tab.layout)
self.layout.addWidget(self.tab)
self.setLayout(self.layout)
self.tab.resize(300, self.ertefa)
self.tab.show()
def draw_graphs(self):
for item in self.new_df.columns[1:]:
self.comboBox.addItem(str(item))
self.comboBox.setMaximumWidth(200)
self.comboBox.activated[str].connect(self.draw_plot)
self.layout_combobox.addWidget(self.comboBox)
self.layout_combobox.setAlignment(QtCore.Qt.AlignRight)
self.layout_combobox_time_filter.addLayout(self.layout_combobox.layout())
self.canvas.axes.plot(self.new_df.index, self.new_df.loc[:, self.comboBox.currentText()])
self.label = self.canvas.axes.set_xlabel('Time', fontsize=9)
self.label = self.canvas.axes.set_ylabel(self.comboBox.currentText(), fontsize=9)
self.canvas.axes.legend(str(self.comboBox.currentText()))
self.canvas.draw()
self.graphs_tab.layout.addWidget(self.toolbar)
self.graphs_tab.layout.addWidget(self.canvas)
self.graphs_tab.layout.addLayout(self.layout_combobox_time_filter.layout())
def draw_plot(self):
self.canvas.axes.plot(self.new_df.index, self.new_df.loc[:, self.comboBox.currentText()])
self.label = self.canvas.axes.set_xlabel('Time', fontsize=9)
self.label = self.canvas.axes.set_ylabel(self.comboBox.currentText(), fontsize=9)
self.canvas.axes.legend(self.comboBox.currentText())
self.canvas.draw()
def prepare_data(self):
# extract data from file
sensor = pd.read_excel("file.xlsx")
return df_new
The default values for width and height are (width=6.4, height=4.8) based on documentation.
My screen size is 1024 * 600 pixel. Then I create an instance of this class as follows and assign a NavigationToolbar to it:
self.canvas = MplCanvas(self, width=10, height=7, dpi=100)
self.toolbar = NavigationToolbar(self.canvas, self)
I plot my data-frame as following:
self.canvas.axes.plot(self.new_df.index, self.new_df.loc[:, self.comboBox.currentText()])
self.label = self.canvas.axes.set_xlabel('Time', fontsize=9)
self.label = self.canvas.axes.set_ylabel(self.comboBox.currentText(), fontsize=9)
self.canvas.axes.legend(str(self.comboBox.currentText()))
self.canvas.draw()
but every time I change the width and height of the canvas its dimensions don't change.
What is wrong with my code or my understanding of this?
And the legend of the plot isn't completely displayed, as the picture indicates, only the first character of the legend is shown. How to fix it?
Ok, I found the problem. in MplCanvas where the fig object is initialized, tight_layout property should be set to True.
class MplCanvas(FigureCanvasQTAgg):
def __init__(self, parent=None, width=6.4, height=4.8, dpi=394):
fig = Figure(figsize= (width, height), dpi=dpi, tight_layout=True)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
But the legend is shown with one character only.
I have been trying to learn to using PyQt5.
I want to implement a canvas below the "Menu Bar"
class gui(QDialog):
def __init__(self, parent=None):
super(gui, self).__init__(parent)
self.createTopLayout()
self.painter = canvas(self)
mainLayout = QGridLayout()
mainLayout.addLayout(self.topLayout, 0, 0, 1, 2)
mainLayout.addWidget(self.painter, 1, 0, 6, 2)
self.setLayout(mainLayout)
def createTopLayout(self):
self.topLayout = QHBoxLayout()
button1 = QPushButton("b1")
button2 = QPushButton("b2")
button3 = QPushButton("b3")
styleComboBox = QComboBox()
styleComboBox.addItems(QStyleFactory.keys())
styleLabel = QLabel("&Style:")
styleLabel.setBuddy(styleComboBox)
self.topLayout.addWidget(styleLabel)
self.topLayout.addWidget(styleComboBox)
self.topLayout.addStretch(1)
self.topLayout.addWidget(button1)
self.topLayout.addWidget(button2)
self.topLayout.addWidget(button3)
Where my canvas is defined as
class canvas(QMainWindow):
def __init__(self, parent=None):
super(canvas, self).__init__(parent)
self.setGeometry(100, 100, 1000, 700)
def paintEvent(self, e):
cir = circle() #circle class creates a circle with random center and radius both between 0 to 100
painter = QPainter(self)
painter.setPen(QPen(Qt.red, 1, Qt.SolidLine))
painter.drawEllipse(self, cir.center.x, cir.center.y, cir.radius, cir.radius)
but for me canvas doesnt render at all let alone the ellipse.
You should not use QMainWindow as a canvas, instead use a QWidget. On the other hand setGeometry will not work if you use layout since the latter handles the geometry, instead it establishes a fixed size and adequate margins. On the other hand it is recommended that the name of the classes begin with capital letters, considering the above the solution is:
class Canvas(QWidget):
def __init__(self, parent=None):
super(Canvas, self).__init__(parent)
self.setFixedSize(1000, 700)
self.setContentsMargins(100, 100, 100, 100)
def paintEvent(self, e):
# ...
I am having problems with a GUI I'm working on. The idea is to have a tree list of signals and be able to drag them onto the plot. Eventually having a long list of signals and multiple plots etc.. However the code segmentation faults after a seemingly random number of drag & drops (sometimes just one). I've stripped the code to the bare bones so it plots the same curve each time and there is only one 'signal'to choose from; in this case just x^2.
Below I've posted the code with the packages it requires.
import wx
import random
import scipy
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure
class MainFrame(wx.Frame):
''' Create the mainframe on which all of the other panels are placed.
'''
def __init__(self):
wx.Frame.__init__(self, parent=None, title="GUI", size=(998,800))
self.SetBackgroundColour('#CCCCCC')
self.GUIBox = wx.BoxSizer(wx.HORIZONTAL)
self.P = PlotWindow(self)
self.DD = DragDrop(self)
self.GUIBox.Add(self.DD, 0, wx.LEFT | wx.ALIGN_TOP)
self.GUIBox.Add(self.P, 0, wx.LEFT | wx.ALIGN_TOP)
self.SetSizer(self.GUIBox)
return
class PlotWindow(wx.Panel):
def __init__(self, parent):
wx.Window.__init__(self, parent)
self.Figure = Figure()
self.Figure.set_size_inches(8.56, 9.115)
self.C = FigureCanvasWxAgg(self, -1, self.Figure)
self.SP = self.Figure.add_subplot(111)
self.a = [0,1,2,3,4,5]
self.b = [0,1,4,9,16,25]
self.signals = [self.b]
def rePlot(self):
self.SP.clear()
c = scipy.zeros(6)
for i in range(0, 6, 1):
c[i] = self.b[i]*random.uniform(0, 2)
self.SP.plot(self.a,c)
self.C.draw()
class MyTextDropTarget(wx.TextDropTarget):
def __init__(self, objt):
wx.TextDropTarget.__init__(self)
self.Objt = objt
def OnDropText(self, x, y, data):
self.Objt.rePlot()
class DragDrop(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, style=wx.BORDER_RAISED)
self.SetBackgroundColour('#CCCCCC')
self.tree = wx.TreeCtrl(self, -1, size=(270,700))
# Add root
root = self.tree.AddRoot("Signals")
self.tree.AppendItem(root, "Square")
dt = MyTextDropTarget(self.GetParent().P)
self.GetParent().P.SetDropTarget(dt)
self.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnDragInit)
self.VBox = wx.BoxSizer(wx.VERTICAL)
self.VBox.Add(self.tree, 0)
self.SetSizer(self.VBox)
def OnDragInit(self, event):
text = self.tree.GetItemText(event.GetItem())
tdo = wx.TextDataObject(text)
tds = wx.DropSource(self.tree)
tds.SetData(tdo)
tds.DoDragDrop(True)
class App(wx.App):
def OnInit(self):
self.dis = MainFrame()
self.dis.Show()
return True
app = App()
app.MainLoop()
I've tried to take out as much unnecessary code as possible; any help would be much appreciated!
Cheers!