Search code examples
pythonpyqtpyqtgraphviewbox

adding QGraphicsLineItem to Scene/View in pyqt / pyqtgraph


I am trying to make a custom border for a graph in pyqtgraph. I am doing so by adding a QGraphicsLineItem to the scene / graphicsview (ViewBox in pyqtgraph) but am having trouble with either approach. Adding the QGraphicsLineItem to the scene gives me what I want to get (a border around the top and right axis), but it does not scale:

upperplot = graphics_layout_widget.addPlot(0, 0, 1, 1)
self.curve_upper = upperplot.plot(np.linspace(0,0,8192),
                                  np.linspace(0,0,8192), # loaded_file.data.vm_array[0]
                                  pen=plotpen)

tl = upperplot.getViewBox().boundingRect().topLeft()
tr = upperplot.getViewBox().boundingRect().topRight()

topline = QtGui.QGraphicsLineItem(tl.x(), tl.y(), tr.x(), tr.y())
topline.setParentItem(upperplot.getViewBox())
topline.setPen(pg.mkPen(color=(211, 211, 211), width=10))

upperplot.getViewBox().scene().addItem(topline)

enter image description here enter image description here

I saw GraphicsView handles all resizing, and tried adding the item directly to GraphicView:

upperplot.getViewBox().addItem(topline)

works except for the line is now centered around Y = 0, not the top left. Interestingly the X axis is okay.

enter image description here

I feel it is a simple solution but cannot for the life of me find the answer - I am not sure if it is a problem with mapping Scene to View or with alignment of the scene in the viewbox but I've had no sucess playing around with either. Any help would be much appreciated.

Minimal reproducable example:

from PyQt5 import QtWidgets, QtGui
import pyqtgraph as pg
import numpy as np

class UiMainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super(UiMainWindow, self).__init__()

        # set mainwindow + widgets
        self.mainwidget = QtWidgets.QWidget(self)
        self.mainwidget_gridlayout = QtWidgets.QGridLayout(self.mainwidget)
        self.setCentralWidget(QtGui.QWidget(self))
        self.centralWidget().setLayout(self.mainwidget_gridlayout)

        self.graphics_layout_widget = pg.GraphicsLayoutWidget()  # contains a graphicsview
        self.graphics_layout_widget.setBackground('w')
        pg.setConfigOption('foreground', 'k')
        self.mainwidget_gridlayout.addWidget(self.graphics_layout_widget)

        # make plot
        plotpen = pg.mkPen(color='k', width=1)
        self.upperplot = self.graphics_layout_widget.addPlot(0, 0, 1, 1)
        self.curve_upper = self.upperplot.plot(np.linspace(0, 100, 8192),
                                               np.linspace(0, 0, 8192),
                                               pen=plotpen)

        # draw top border line
        QtWidgets.QApplication.processEvents()      # I could not get the boundingRect of the ViewBox without drawing first
        tl = self.upperplot.getViewBox().boundingRect().topLeft()
        tr = self.upperplot.getViewBox().boundingRect().topRight()
        br = self.upperplot.getViewBox().boundingRect().bottomRight()

        topline = QtGui.QGraphicsLineItem(tl.x(), tl.y(), tr.x(), tr.y())
        topline.setParentItem(self.upperplot.getViewBox())
        topline.setPen(pg.mkPen(color=(211, 211, 211), width=10))

        rightline = QtGui.QGraphicsLineItem(tr.x(), tr.y(), br.x(), br.y())
        rightline.setParentItem(self.upperplot.getViewBox())
        rightline.setPen(pg.mkPen(color=(211, 211, 211), width=10))

        self.upperplot.getViewBox().addItem(topline)  # correct scaling, but Y axis is centered as zero
        self.upperplot.getViewBox().addItem(rightline)

        # vs
#        self.upperplot.getViewBox().scene().addItem(topline)  # correct position, but cannot scale
#        self.upperplot.getViewBox().scene().addItem(rightline)


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    mw = UiMainWindow()
    mw.show()
    sys.exit(app.exec_())

Update around border:

self.upperplot.getViewBox().setBorder(color=(211, 211, 211), width=10)

gives:

enter image description here

rather than:

enter image description here


Solution

  • One possible solution is to implement a custom ViewBox by doing the custom painting:

    class CustomViewBox(pg.ViewBox):
        def paint(self, p, opt, widget):
            super().paint(p, opt, widget)
            r = QtCore.QRectF(self.boundingRect())
            p.save()
            tl = r.topLeft()
            tr = r.topRight()
            br = r.bottomRight()
            pen = pg.mkPen(color=(211, 211, 211), width=10)
            p.setPen(pen)
            p.drawLine(tl, tr)
            p.drawLine(tr, br)
            p.restore()
    
    self.upperplot = self.graphics_layout_widget.addPlot(
        0, 0, 1, 1, viewBox=CustomViewBox()
    )