Margins in PyQtGraph's GraphicsLayout - python

Having a simple graphics layout with PyQtGraph:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = QtGui.QApplication([])
view = pg.GraphicsView()
l = pg.GraphicsLayout(border='g')
view.setCentralItem(l)
view.show()
view.resize(800,600)
l.addPlot(0, 0)
l.addPlot(1, 0)
l.layout.setSpacing(0.)
l.setContentsMargins(0., 0., 0., 0.)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Whichs outputs:
How could I get rid of the small margins which are between the green external line and the window borders?
I could do the trick and use l.setContentsMargins(-10., -10., -10., -10.), and that works:
But it seems to me like a dirty trick and there should be another parameter which is setting that margin. Could this be possible? Is there another margin parameter which I could set to 0 to get the same results?

I think this might be a Qt bug. There's an easy workaround:
l = pg.GraphicsLayout()
l.layout.setContentsMargins(0, 0, 0, 0)
To understand this, let's look at a modified example:
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
app = QtGui.QApplication([])
view = pg.GraphicsView()
view.show()
view.resize(800,600)
class R(QtGui.QGraphicsWidget):
# simple graphics widget that draws a rectangle around its geometry
def boundingRect(self):
return self.mapRectFromParent(self.geometry()).normalized()
def paint(self, p, *args):
p.setPen(pg.mkPen('y'))
p.drawRect(self.boundingRect())
l = QtGui.QGraphicsGridLayout()
r1 = R()
r2 = R()
r3 = R()
r1.setLayout(l)
l.addItem(r2, 0, 0)
l.addItem(r3, 1, 0)
view.scene().addItem(r1)
In this example, calling l.setContentsMargins(...) has the expected effect, but calling r1.setContentsMargins(...) does not. The Qt docs suggest that the effect should have been the same, though: http://qt-project.org/doc/qt-4.8/qgraphicswidget.html#setContentsMargins

For anyone going through this in 2022, use a pg.GraphicsLayoutWidget :
# GraphicsLayoutWidget is now recommended
w = pg.GraphicsLayoutWidget(border=(30,20,255))
win.centralWidget.layout.setContentsMargins(0,0,0,0)
win.centralWidget.layout.setSpacing(0)
Notice how there is no spacing between each blue border of each plot :

A little update.
self.graphicsView = pg.GraphicsLayoutWidget(self)
self.graphicsView.ci.layout.setContentsMargins(0, 0, 0, 0)
self.graphicsView.ci.layout.setSpacing(0)

For those who are using pyqtgraph.jupyter.GraphicsLayoutWidget, the layout object is a variable named gfxLayout
from pyqtgraph.jupyter import GraphicsLayoutWidget
win = GraphicsLayoutWidget()
win.gfxLayout.setContentsMargins(10,10,10,10)
win.gfxLayout.setSpacing(0)

Related

PyQtGraph: why the object is always at the left-bottom corner

Here is my code:
import sys
import pyqtgraph.opengl as gl
from PyQt5.QtWidgets import QApplication
class Demo(gl.GLViewWidget):
def __init__(self):
super(Demo, self).__init__()
xgrid = gl.GLGridItem()
ygrid = gl.GLGridItem()
zgrid = gl.GLGridItem()
xgrid.rotate(90, 0, 1, 0)
ygrid.rotate(90, 1, 0, 0)
xgrid.scale(0.2, 0.1, 0.1)
ygrid.scale(0.2, 0.1, 0.1)
zgrid.scale(0.1, 0.2, 0.1)
self.addItem(xgrid)
self.addItem(ygrid)
self.addItem(zgrid)
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
After running the code, you can see that the 3d object is always at the left-bottom corner of the whole widget.
I've check the source code of PyQtGraph but don't know which is the right function to change the position.
Any helps would be appreciated!
Also had this problem, found a question related to it on github
Installing the development branch of PyQtGraph with pip install git+https://github.com/pyqtgraph/pyqtgraph#develop resolved it for me

Dynamically rotate TextItem in pyqtgraph

I want to dynamically rotate TextItem but cannot get it to work. Changing position or anchor with setPos and setAnchor updates the item, but wanting to change angel with setAngle doesn't update the text. The strangest thing is that it does update once I drag the canvas.
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
app = QtGui.QApplication([])
w = pg.GraphicsView()
w.show()
w.resize(800,800)
view = pg.ViewBox()
w.setCentralItem(view)
view.setAspectLocked(True)
view.setRange(QtCore.QRectF(0, 0, 200, 200))
anchor = pg.TextItem()
anchor.setText("hey")
anchor.setColor(QtGui.QColor(255, 255, 255))
view.addItem(anchor)
def rotate():
x, y = anchor.pos()
anchor.setPos(x + 1, y + 1)
anchor.setAngle(anchor.angle + 10)
timer = QtCore.QTimer()
timer.timeout.connect(rotate)
timer.start(1000)
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I am wondering what signal or function to call so that the item updates immediately.
It ended up being a bug. I solved it by just removing the old TextItem and creating a new one with the updatet angle.

How to show matplotlib.pyplot in qt widget?

I want to show the pyplot image in widget (QWidget) that I put in a gui designed in QtDesigner:
When I push the Çiz button I want to show the image that I can create in python with that code:
points = np.array([(1, 1), (2, 4), (3, 1), (9, 3)])
x = points[:,0]
y = points[:,1]
# calculate polynomial
z = np.polyfit(x, y, 2)
f = np.poly1d(z)
x_fit = np.linspace(min(x), max(x), 10000)
y_fit = [f(_x) for _x in x_fit]
plt.plot(x, y)
plt.plot(x_fit, y_fit)
plt.show()
EDIT
I made some changes according to the answer but I have new problems.
After I promote it:
I rearrange my code below:
# calculate polynomial and r
self.x_fit = np.linspace(min(self.itemX), max(self.itemY), 10000)
self.y_fit = [f(_x) for _x in self.x_fit]
self._dlg.plotwidget.plot(self.itemX, self.itemY)
self._dlg.plotwidget.plot(self.x_fit, self.y_fit)
self.itemX is x values in a list.
self.itemY is y values in a list.
self._dlg is the MainWindow that you see.
When I try to open that window I get this error message:
I just tested the code below works for me:
import sys
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, uic
uifilename = 'test.ui'
form_class = uic.loadUiType(uifilename)[0] #dirty reading of the ui file. better to convert it to a '.py'
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.onInit()
def onInit(self):
#usually a lot of connections here
self.x_fit = np.linspace(1,10000, 10000)
self.y_fit = [f(_x) for _x in self.x_fit]
self.plotwidget.plot(self.x_fit,self.y_fit,symbol='o',pen=None)
self.plotwidget.setLabel('left',text='toto',units='')
self.plotwidget.setLabel('top',text='tata',units='')
def f(x):
return x**2+1
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
app = QtGui.QApplication([])
pg.setConfigOption('background', 'w')
win = MyWindowClass()
win.show()
app.exec_()
with this test.ui : https://github.com/steph2016/profiles/blob/master/test.ui.
Note the plotwidget is encapsulated inside a layout. I thought it works with just the Main Window, but I'm not sure anymore...
Sorry I don't exactly answer the question but I don't use matplotlib.pyplot with pyqt. I recommend using pyqtgraph (http://pyqtgraph.org), which is quite convenient and powerful. With Qt Designer:
you just insert a 'graphics view' in your GUI
you right-click on it and promote it to "plotwidget" using pyqtgraph. I guess the english tab is something like this: base class 'QGraphicsView'; promoted class 'PlotWidget'; header file 'pyqtgraph', then 'add', then 'promote'
then you can make plotwidget.plot(x,y,...), plotwidget.clear(), and co. It will plot everything inside the plotwidget with a bunch of interacting possibilities

pyqtgraph - move origin of ArrowItem to local center

I'm using pyqtgraph to plot tracks of a robot (the path that the bot drove). Now I want to add a marker to the plot to indicate the bots current position and heading. I thought ArrowItem would be the right choice, because it is scale invariant and can be rotated easily. However the local origin of the arrow is at its tip like this
but I want it to be in the center like this
How can I do that? I would also appreciate different solutions to this problem.
Update
After applying eyllansec's code I get some rendering problems. A minimal example (one has to zoom or move the view to disable the auto scaling):
import pyqtgraph as pg
import numpy as np
import time
class CenteredArrowItem(pg.ArrowItem):
def paint(self, p, *args):
p.translate(-self.boundingRect().center())
pg.ArrowItem.paint(self, p, *args)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
while True:
for i in range(300):
arrow = CenteredArrowItem(angle=i, headLen=40, tipAngle=45, baseAngle=30)
arrow.setPos(i / 300, i / 300)
tracker.addItem(arrow)
app.processEvents()
time.sleep(0.02)
tracker.removeItem(arrow)
As you may noticed I'm adding and removing the arrow each iteration. This is because arrow.setStyle(angle=i) is not working as it does not update the rotation of the arrow (probably a bug).
A possible solution is to overwrite the paint method of ArrowItem and move the QPainter:
import numpy as np
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
class MyArrowItem(pg.ArrowItem):
def paint(self, p, *args):
p.translate(-self.boundingRect().center())
pg.ArrowItem.paint(self, p, *args)
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
p = pg.PlotWidget()
p.showGrid(x = True, y = True, alpha = 0.3)
w.show()
w.resize(640, 480)
w.setCentralWidget(p)
w.setWindowTitle('pyqtgraph example: Arrow')
a = pg.ArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3}, brush='r')
b = MyArrowItem(angle=-160, tipAngle=60, headLen=40, tailLen=40, tailWidth=20, pen={'color': 'w', 'width': 3})
a.setPos(10,0)
b.setPos(10,0)
p.addItem(a)
p.addItem(b)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
As shown in the following figure, the red arrow is the default ArrowItem, and the blue is the offset, both are located in the same position with respect to the plot.
Update:
The problem is caused by the method that rotates the item used as the center of coordinates using the center of transformations by default, that is to say the (0, 0), we must move it:
import pyqtgraph as pg
import numpy as np
import time
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import functions as fn
class CenteredArrowItem(pg.ArrowItem):
def setStyle(self, **opts):
# http://www.pyqtgraph.org/documentation/_modules/pyqtgraph/graphicsItems/ArrowItem.html#ArrowItem.setStyle
self.opts.update(opts)
opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
tr = QtGui.QTransform()
path = fn.makeArrowPath(**opt)
tr.rotate(self.opts['angle'])
p = -path.boundingRect().center()
tr.translate(p.x(), p.y())
self.path = tr.map(path)
self.setPath(self.path)
self.setPen(fn.mkPen(self.opts['pen']))
self.setBrush(fn.mkBrush(self.opts['brush']))
if self.opts['pxMode']:
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
else:
self.setFlags(self.flags() & ~self.ItemIgnoresTransformations)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
while True:
for i in range(300):
arrow = CenteredArrowItem(angle=i, headLen=40, tipAngle=45, baseAngle=30)
arrow.setPos(i / 300, i / 300)
tracker.addItem(arrow)
app.processEvents()
time.sleep(0.02)
tracker.removeItem(arrow)
After digging through the source code of pyqtgraph I ended up with a special function that suits my needs. I apply the translation when creating the arrow path, instead when rendering it. Fortunately this also solves the roation bug (for whatever reason).
Example:
import pyqtgraph as pg
import numpy as np
import time
import pyqtgraph.functions
class CenteredArrowItem(pg.ArrowItem):
def setData(self, x, y, angle):
self.opts['angle'] = angle
opt = dict([(k, self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
path = pg.functions.makeArrowPath(**opt)
b = path.boundingRect()
tr = pg.QtGui.QTransform()
tr.rotate(angle)
tr.translate(-b.x() - b.width() / 2, -b.y() - b.height() / 2)
self.path = tr.map(path)
self.setPath(self.path)
self.setPos(x, y)
if __name__ == '__main__':
app = pg.QtGui.QApplication([])
window = pg.GraphicsWindow(size=(1280, 720))
window.setAntialiasing(True)
tracker = window.addPlot(title='Tracker')
arrow = CenteredArrowItem(headLen=40, tipAngle=45, baseAngle=30)
tracker.addItem(arrow)
tracker.addItem(pg.InfiniteLine(pos=(0,0), angle=45))
center = pg.ScatterPlotItem([], [], brush='r')
tracker.addItem(center)
while True:
for i in range(300):
arrow.setData(i, i, i)
center.setData([i], [i])
app.processEvents()
time.sleep(0.02)

turning grid on with AxisItem in pyqtgraph causes axis scaling to break

I am having trouble with AxisItem. As soon as I turn on both the x and y grid, the x-axis is no longer able to scale in and out with the zoom/pan function. Any ideas?
from PyQt4 import QtCore, QtGui
from pyqtgraph import Point
import pyqtgraph as pg
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
class plotClass(QtGui.QMainWindow):
def setupUi(self, MainWindow):
self.centralwidget = QtGui.QWidget(MainWindow)
MainWindow.resize(1900, 1000)
self.viewbox = pg.GraphicsView(MainWindow, useOpenGL=None, background='default')
self.viewbox.setGeometry(QtCore.QRect(0, 0, 1600, 1000))
self.layout = pg.GraphicsLayout()
self.viewbox.setCentralWidget(self.layout)
self.viewbox.show()
self.view = self.layout.addViewBox()
self.axis1 = pg.AxisItem('bottom', linkView=self.view, parent=self.layout)
self.axis2 = pg.AxisItem('right', linkView=self.view, parent=self.layout)
self.axis1.setGrid(255)
self.axis2.setGrid(255)
self.layout.addItem(self.axis1, row=1, col=0)
self.layout.addItem(self.axis2, row=0, col=1)
if __name__== "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = plotClass()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Looking at your last comment, consider this option:
The pyqtgraph examples folder contains a "GraphItem.py" example which adds and displays a GraphItem object to a window via a ViewBox only. They don't use a grid however, so if you want to use a grid with a GraphItem, just add a PlotItem first (which has an associated ViewBox already... and you guessed it,...AxisItems for a grid!),... then get the ViewBox to add your GraphItems. The modified GraphItem.py would look like this (with the accompanying showGrid):
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
# Enable antialiasing for prettier plots
pg.setConfigOptions(antialias=True)
w = pg.GraphicsWindow()
w.setWindowTitle('pyqtgraph example: GraphItem')
### comment out their add of the viewbox
### since the PlotItem we're adding will have it's
### own ViewBox
#v = w.addViewBox()
pItem1 = w.addPlot() # this is our new PlotItem
v = pItem1.getViewBox() # get the PlotItem's ViewBox
v.setAspectLocked() # same as before
g = pg.GraphItem() # same as before
v.addItem(g) # same as before
pItem1.showGrid(x=True,y=True) # now we can turn on the grid
### remaining code is the same as their example
## Define positions of nodes
pos = np.array([
[0,0],
[10,0],
[0,10],
[10,10],
[5,5],
[15,5]
])
## Define the set of connections in the graph
adj = np.array([
[0,1],
[1,3],
[3,2],
[2,0],
[1,5],
[3,5],
])
## Define the symbol to use for each node (this is optional)
symbols = ['o','o','o','o','t','+']
## Define the line style for each connection (this is optional)
lines = np.array([
(255,0,0,255,1),
(255,0,255,255,2),
(255,0,255,255,3),
(255,255,0,255,2),
(255,0,0,255,1),
(255,255,255,255,4),
], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)])
## Update the graph
g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False)
## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
I tested this and the scroll/zooming still worked after enabling the grid, so still not sure why doing it the other way DOESN'T work, but sometimes finding another way is the best answer :)

Categories