pyqtpyqt6pyqtgraph

gradient background in pyqtgraph w/ pyqt6


Is it possible to give a gradient background to a plot in pyqtgraph & pyqt6?

Minimal code to start with:

import pyqtgraph as pg
from PyQt6.QtWidgets import QApplication

# Create application
app = QApplication([])

# Create main window with 2 plots
win = pg.GraphicsLayoutWidget(title="Dual Plots Example", size=(800, 400))

# Left plot (plot 1)
plot1 = win.addPlot(row=0, col=0, title="Signal 1")
plot1.plot([1, 2, 3, 4], [1, 3, 2, 4], pen='b')

# Right plot (plot 2)
plot2 = win.addPlot(row=0, col=1, title="Signal 2")
plot2.plot([1, 2, 3, 4], [4, 2, 3, 1], pen='r')

# Display the window
win.show()
app.exec()

I would like to add a vertical gradient background from blue to red. I don't think pyqtgraph handle gradient background, but maybe there is a workaround.


Solution

  • Almost all pyqtgraph objects inherit from Qt objects.

    By checking the documentation, we can easily find the original classes: GraphicsLayoutWidget is based on GraphicsView, which is a reimplementation of Qt's QGraphicsView.

    QGraphicsView provides a backgroundBrush property, and QBrush does allow gradients.

    # further imports
    from PyQt6.QtGui import *
    
    ...
    
    grad = QLinearGradient(0, 0, 0, 1)
    grad.setCoordinateMode(QGradient.CoordinateMode.ObjectMode)
    grad.setStops((
        (0, QColorConstants.Blue),
        (1, QColorConstants.Red),
    ))
    win.setBackgroundBrush(QBrush(grad))
    
    win.show()
    app.exec()
    

    The arguments of QLinearGradient are the start and stop position, interpreted in "real" values (with 0.0 being the top or left and 1.0 right and bottom) due to the specification of the ObjectMode for the coordinate mode: 0, 0 indicates the top left corner of the bounding rect of the drawn area, 0, 1 the bottom left, which gives a vertical gradient.

    If you wanted a horizontal gradient, those values should become 0, 0, 1, 0. Obviously any value of the y (or x for the case above) is valid, as long as they are identical for both coordinates.

    Note that it's also possible to set a gradient for a plot item or even its internal elements. Those items inherit from QGraphicsWidget, which has its own palette and can draw its background based on the Window color role, as long as we set the autoFillBackground property (similar to QWidget).

    The following example is a revision of the above, setting a dark-gray->black background gradient for the whole view, a red->transparent gradient for the left plot, and a horizontal blue->transparent gradient for the viewbox of the right plot.

    Note that by setting a gradient/brush/color object as a property results in applying the property on the target, but the object is not referenced for that target: changing a gradient after applying it will not implicitly change the target. A similar behavior happens for other complex objects such as QPalette.

    ...
    
    # similar to the example above
    grad = QLinearGradient(0, 0, 0, 1)
    grad.setCoordinateMode(QGradient.CoordinateMode.ObjectMode)
    grad.setStops((
        (0, QColorConstants.DarkGray),
        (1, QColorConstants.Black),
    ))
    
    win.setBackgroundBrush(QBrush(grad))
    
    # changing colors of the gradient will not change the window background
    grad.setStops((
        (0, QColorConstants.Red),
        (1, QColorConstants.Transparent)
    ))
    
    # get the current palette and set the gradient for the Window role
    palette = app.palette()
    # note: PyQt implicitly converts QGradient to QBrush in this case
    palette.setBrush(QPalette.ColorRole.Window, grad)
    
    # set the palette and ensure that the plot draws its background
    plot1.setPalette(palette)
    plot1.setAutoFillBackground(True)
    
    # change the start color of the gradient and its orientation
    grad.setColorAt(0, QColorConstants.Blue)
    grad.setFinalStop(1, 0)
    # update the palette
    palette.setBrush(QPalette.ColorRole.Window, grad)
    
    # do the same as above, but for the *viewbox*
    plot2.vb.setPalette(palette)
    plot2.vb.setAutoFillBackground(True)
    
    win.show()
    app.exec()
    

    Which will give the following:

    Screenshot resulting from the code above