1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
from typing import Union
from .. import functions as fn
from ..Qt import QtGui, QtWidgets, QtCore
from .PlotCurveItem import PlotCurveItem
from .PlotDataItem import PlotDataItem
__all__ = ['FillBetweenItem']
class FillBetweenItem(QtWidgets.QGraphicsPathItem):
"""
GraphicsItem filling the space between two PlotDataItems.
"""
def __init__(
self,
curve1: Union[PlotDataItem, PlotCurveItem],
curve2: Union[PlotDataItem, PlotCurveItem],
brush=None,
pen=None,
fillRule: QtCore.Qt.FillRule=QtCore.Qt.FillRule.OddEvenFill
):
"""FillBetweenItem fills a region between two curves with a specified
:class:`~QtGui.QBrush`.
Parameters
----------
curve1 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
Line to draw fill from
curve2 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
Line to draw fill to
brush : color_like, optional
Arguments accepted by :func:`~pyqtgraph.mkBrush`, used
to create the :class:`~QtGui.QBrush` instance used to draw the item
by default None
pen : color_like, optional
Arguments accepted by :func:`~pyqtgraph.mkColor`, used
to create the :class:`~QtGui.QPen` instance used to draw the item
by default ``None``
fillRule : QtCore.Qt.FillRule, optional
FillRule to be applied to the underlying :class:`~QtGui.QPainterPath`
instance, by default ``QtCore.Qt.FillRule.OddEvenFill``
Raises
------
ValueError
Raised when ``None`` is passed in as either ``curve1``
or ``curve2``
TypeError
Raised when either ``curve1`` or ``curve2`` is not either
:class:`~pyqtgraph.PlotDataItem` or :class:`~pyqtgraph.PlotCurveItem`
"""
super().__init__()
self.curves = None
self._fillRule = fillRule
if curve1 is not None and curve2 is not None:
self.setCurves(curve1, curve2)
elif curve1 is not None or curve2 is not None:
raise ValueError("Must specify two curves to fill between.")
if brush is not None:
self.setBrush(brush)
self.setPen(pen)
self.updatePath()
def fillRule(self):
return self._fillRule
def setFillRule(self, fillRule: QtCore.Qt.FillRule=QtCore.Qt.FillRule.OddEvenFill):
"""Set the underlying :class:`~QtGui.QPainterPath` to the specified
:class:`~QtCore.Qt.FillRule`
This can be useful for allowing in the filling of voids.
Parameters
----------
fillRule : QtCore.Qt.FillRule
A member of the :class:`~QtCore.Qt.FillRule` enum
"""
self._fillRule = fillRule
self.updatePath()
def setBrush(self, *args, **kwds):
"""Change the fill brush. Accepts the same arguments as :func:`~pyqtgraph.mkBrush`
"""
QtWidgets.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
def setPen(self, *args, **kwds):
"""Change the fill pen. Accepts the same arguments as :func:`~pyqtgraph.mkColor`
"""
QtWidgets.QGraphicsPathItem.setPen(self, fn.mkPen(*args, **kwds))
def setCurves(
self,
curve1: Union[PlotDataItem, PlotCurveItem],
curve2: Union[PlotDataItem, PlotCurveItem]
):
"""Method to set the Curves to draw the FillBetweenItem between
Parameters
----------
curve1 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
Line to draw fill from
curve2 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
Line to draw fill to
Raises
------
TypeError
Raised when input arguments are not either :class:`~pyqtgraph.PlotDataItem` or
:class:`~pyqtgraph.PlotCurveItem`
"""
if self.curves is not None:
for c in self.curves:
try:
c.sigPlotChanged.disconnect(self.curveChanged)
except (TypeError, RuntimeError):
pass
curves = [curve1, curve2]
for c in curves:
if not isinstance(c, (PlotDataItem, PlotCurveItem)):
raise TypeError("Curves must be PlotDataItem or PlotCurveItem.")
self.curves = curves
curve1.sigPlotChanged.connect(self.curveChanged)
curve2.sigPlotChanged.connect(self.curveChanged)
self.setZValue(min(curve1.zValue(), curve2.zValue())-1)
self.curveChanged()
def curveChanged(self):
self.updatePath()
def updatePath(self):
if self.curves is None:
self.setPath(QtGui.QPainterPath())
return
paths = []
for c in self.curves:
if isinstance(c, PlotDataItem):
paths.append(c.curve.getPath())
elif isinstance(c, PlotCurveItem):
paths.append(c.getPath())
path = QtGui.QPainterPath()
path.setFillRule(self.fillRule())
ps1 = paths[0].toSubpathPolygons()
ps2 = paths[1].toReversed().toSubpathPolygons()
ps2.reverse()
if len(ps1) == 0 or len(ps2) == 0:
self.setPath(QtGui.QPainterPath())
return
for p1, p2 in zip(ps1, ps2):
path.addPolygon(p1 + p2)
self.setPath(path)
|