I am creating a PyQt5 Gui and I am using PyQtGraph to plot some data. Here is a minimal, complete, verifiable example script that closely resembles the structure that I have.
import sys
from PyQt5.QtWidgets import (QWidget, QGridLayout, QApplication)
import pyqtgraph as pg
from pyqtgraph import QtCore, QtGui
class CustomPlot(pg.GraphicsObject):
def __init__(self, data):
pg.GraphicsObject.__init__(self)
self.data = data
print(self.data)
self.generatePicture()
def generatePicture(self):
self.picture = QtGui.QPicture()
p = QtGui.QPainter(self.picture)
p.setPen(pg.mkPen('w', width=1/2.))
for (t, v) in self.data:
p.drawLine(QtCore.QPointF(t, v-2), QtCore.QPointF(t, v+2))
p.end()
def paint(self, p, *args):
p.drawPicture(0, 0, self.picture)
def boundingRect(self):
return QtCore.QRectF(self.picture.boundingRect())
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.simpleplot()
def initUI(self):
self.guiplot = pg.PlotWidget()
layout = QGridLayout(self)
layout.addWidget(self.guiplot, 0,0)
def simpleplot(self):
data = [
(1., 10),
(2., 13),
(3., 17),
(4., 14),
(5., 13),
(6., 15),
(7., 11),
(8., 16)
]
pgcustom = CustomPlot(data)
self.guiplot.addItem(pgcustom)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
This generates a graph that looks like
The y-axis is on the left of the plot but I would like to move it to the right. I have tried a number of things but I cannot find the object (QtGui.QPainter, GraphicObject, etc.) that has the option or method to achieve this.
It can be set by methods of the PlotItem class.
def initUI(self):
self.guiplot = pg.PlotWidget()
plotItem = self.guiplot.getPlotItem()
plotItem.showAxis('right')
plotItem.hideAxis('left')
If you haven't read the section on Organization of Plotting Classes, take look at it. In particular at the relation between the PlotWidet and PlotItem classes.
Related
I am trying to add GraphicsLayoutWidget and LinearRegionItem, that two ratio is 4:1 or m:n, but I don't found any layout in pygtgraph.So I try to the code like below, but unfortunately it don't work and cast out traceback error info.
code
import pyqtgraph as pg
import numpy as np
from PyQt5.QtWidgets import *
class Chart(pg.GraphicsLayoutWidget):
def __init__(self):
super().__init__()
self.data = np.linspace(0, 2*np.pi, 100)
self.data = np.sin(self.data)
self.p = self.addPlot(y=self.data)
class LR(pg.GraphicsLayoutWidget):
def __init__(self, target):
super().__init__()
self.p = self.addPlot() # type:pg.PlotItem
self.target = target # type: pg.PlotItem
rg = (target.data[0], target.data[-1])
self.p.setXRange(rg[0], rg[1], padding=0)
self.lr = pg.LinearRegionItem([rg[0], rg[1]])
self.lr.sigRegionChanged.connect(self.onLRChanged)
self.p.addItem(self.lr)
def onLRChanged(self):
print(self.target, self.lr.getRegion())
self.target.setXRange(*self.lr.getRegion())
class Win(QWidget):
def __init__(self):
super().__init__()
lay = QVBoxLayout()
self.chart = Chart()
self.lr = LR(self.chart)
lay.addWidget(self.chart)
lay.addWidget(self.lr)
lay.setStretch(0, 4)
lay.setStretch(1, 1)
self.setLayout(lay)
app = QApplication([])
win = Win()
win.show()
app.exec()
image
error
The traceback info will show when user drag LinearRegionItem line in both side of the bottom graph(plot)
environment
Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)] on win32
Name: pyqtgraph Version: 0.12.2
Name: numpy Version: 1.20.3
Name: PyQt5 Version: 5.15.4
The problem has nothing to do with the stretch factor but setting the range to the GraphicsLayoutWidget that expects a QRect, instead of the PlotItem that expects a tuple causing that exception. On the other hand you can improve how to place the initial range based on the range of the data, also it is better to use signals.
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QVBoxLayout, QWidget
import pyqtgraph as pg
import numpy as np
class Chart(pg.GraphicsLayoutWidget):
def __init__(self, xdata, ydata):
super().__init__()
self.p = self.addPlot(x=xdata, y=ydata)
class LR(pg.GraphicsLayoutWidget):
range_changed = pyqtSignal(float, float)
def __init__(self, xmin, xmax):
super().__init__()
self.lr = pg.LinearRegionItem(values=(xmin, xmax))
self.p = self.addPlot()
self.p.addItem(self.lr)
self.lr.sigRegionChanged.connect(self.handle_region_changed)
def handle_region_changed(self, item):
self.range_changed.emit(*item.getRegion())
class Win(QWidget):
def __init__(self):
super().__init__()
xdata = np.linspace(0, 2 * np.pi, 100)
ydata = np.sin(xdata)
self.chart = Chart(xdata, ydata)
self.lr = LR(xdata.min(), xdata.max())
self.lr.range_changed.connect(self.chart.p.setXRange)
lay = QVBoxLayout(self)
lay.addWidget(self.chart, stretch=4)
lay.addWidget(self.lr, stretch=1)
def main():
app = QApplication([])
win = Win()
win.show()
app.exec()
if __name__ == "__main__":
main()
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_())
My drawingarea is cleared everytime the draw event is called.
How to avoid a drawingarea to be cleared ?
Thanks
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.coords = []
self.show_all()
def on_draw(self, wid, cr):
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(self.coords[0], self.coords[1], 40, 0, 2*math.pi)
cr.fill()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
self.coords = [e.x, e.y]
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
In this example, each time I click on the drawingarea, a circle is drawn. I would like to draw the new circle but without to redrawing the previous one.
Is it possible ?
I would recommend adjusting your mental model of the drawing area; don't think of it as being "cleared" every time the draw handler is called. Rather, think of it like this: the draw handler is called every time the drawing area needs to be redrawn from scratch (among other reasons: because some other window moved in front of it, or because your program asked for a draw update). The drawing area's contents, once drawn, are not persisted anywhere.
If you need persistent window contents, then you should use a backing store and draw that onto the screen in the draw handler, or you could use a canvas library if you want to treat existing drawn objects as independently existing.
I found he answer :
#!/usr/bin/env python3
import gi
gi.require_version('Gtk','3.0')
from gi.repository import Gtk, Gdk
import cairo
import math
class MouseButtons:
LEFT_BUTTON = 1
RIGHT_BUTTON = 3
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.darea = Gtk.DrawingArea()
self.darea.connect("draw", self.on_draw)
self.darea.set_events(Gdk.EventMask.BUTTON_PRESS_MASK)
self.add(self.darea)
self.set_title("Fill & stroke")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
self.darea.connect("button-press-event", self.on_button_press)
self.show_all()
a = self.darea.get_allocation()
print (a.x, a.y, a.width, a.height)
self.img = cairo.ImageSurface(cairo.Format.RGB24, a.width, a.height)
def on_draw(self, wid, cr):
cr.set_source_surface(self.img, 0, 0)
cr.paint()
def on_button_press(self, w, e):
if e.type == Gdk.EventType.BUTTON_PRESS \
and e.button == MouseButtons.LEFT_BUTTON:
cr = cairo.Context(self.img)
cr.set_source_rgb(0.6, 0.6, 0.6)
cr.arc(e.x, e.y, 40, 0, 2*math.pi)
cr.fill()
self.darea.queue_draw()
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
I'm trying to create simple frame that will run longrun procedure, but will show counting in different window (dialog), but it don't work (staticText1 is not updating)... Here's my code:
Main frame (simple frame with one button)
import wx
import time
from threading import Thread
import Dialog1
def create(parent):
return Frame1(parent)
[wxID_FRAME1, wxID_FRAME1BUTTON1, ] = [wx.NewId() for _init_ctrls in range(2)]
class Frame1(wx.Frame):
def test1(self):
for i in range(self.delays):
time.sleep(1)
def _init_ctrls(self, prnt):
# generated method, don't edit
wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
pos=wx.Point(589, 259), size=wx.Size(178, 128),
style=wx.DEFAULT_FRAME_STYLE, title='Frame1')
self.button1 = wx.Button(id=wxID_FRAME1BUTTON1, label='button1',
name='button1', parent=self, pos=wx.Point(0, 0), size=wx.Size(178,
128), style=0)
self.button1.Bind(wx.EVT_BUTTON, self.OnButton1Button,
id=wxID_FRAME1BUTTON1)
def __init__(self, parent):
self._init_ctrls(parent)
self.delays = 5
self.timer = wx.Timer(self)
def OnButton1Button(self, event):
self.testThread = Thread(target=self.test1)
self.testThread.start()
#self.Show(False)
self.dlg = Dialog1.Dialog1(self)
self.dlg.ShowModal()
self.button1.Disable()
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(20, oneShot=True)
event.Skip()
def PollThread(self, event):
if self.testThread.isAlive():
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(200, oneShot=True)
self.dlg.staticText1.SetLabel(self.dlg.staticText1.GetLabel()+".")
else:
self.button1.Enable()
self.dlg.Destroy()
Dialog (simple dialog with one statictext):
import wx
def create(parent):
return Dialog1(parent)
[wxID_DIALOG1, wxID_DIALOG1STATICTEXT1, ] = [wx.NewId() for _init_ctrls in range(2)]
class Dialog1(wx.Dialog):
def _init_ctrls(self, prnt):
# generated method, don't edit
wx.Dialog.__init__(self, id=wxID_DIALOG1, name='', parent=prnt,
pos=wx.Point(451, 295), size=wx.Size(544, 59),
style=wx.DEFAULT_DIALOG_STYLE, title='Dialog1')
self.staticText1 = wx.StaticText(id=wxID_DIALOG1STATICTEXT1,
label=u'Please wait', name='staticText1', parent=self,
pos=wx.Point(56, 24), size=wx.Size(95, 17), style=0)
def __init__(self, parent):
self._init_ctrls(parent)
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_()