Skip to content

Commit

Permalink
Merge pull request #3006 from j9ac9k/fix-fillbetween-regression
Browse files Browse the repository at this point in the history
Allow users to specify FillRule for FillBetweenItem, undo regressionfrom #2971
  • Loading branch information
j9ac9k committed Apr 29, 2024
2 parents e200849 + a933120 commit 1bd5c01
Showing 1 changed file with 93 additions and 21 deletions.
114 changes: 93 additions & 21 deletions pyqtgraph/graphicsItems/FillBetweenItem.py
@@ -1,5 +1,7 @@
from typing import Union

from .. import functions as fn
from ..Qt import QtGui, QtWidgets
from ..Qt import QtGui, QtWidgets, QtCore
from .PlotCurveItem import PlotCurveItem
from .PlotDataItem import PlotDataItem

Expand All @@ -9,33 +11,104 @@ class FillBetweenItem(QtWidgets.QGraphicsPathItem):
"""
GraphicsItem filling the space between two PlotDataItems.
"""
def __init__(self, curve1=None, curve2=None, brush=None, pen=None):
QtWidgets.QGraphicsPathItem.__init__(self)
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 Exception("Must specify two curves to fill between.")
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 pg.mkBrush()"""
"""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, curve2):
"""Set the curves to fill between.
Arguments must be instances of PlotDataItem or PlotCurveItem.
Added in version 0.9.9
"""
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:
Expand All @@ -45,7 +118,7 @@ def setCurves(self, curve1, curve2):

curves = [curve1, curve2]
for c in curves:
if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem):
if not isinstance(c, (PlotDataItem, PlotCurveItem)):
raise TypeError("Curves must be PlotDataItem or PlotCurveItem.")
self.curves = curves
curve1.sigPlotChanged.connect(self.curveChanged)
Expand All @@ -55,6 +128,7 @@ def setCurves(self, curve1, curve2):

def curveChanged(self):
self.updatePath()

def updatePath(self):
if self.curves is None:
self.setPath(QtGui.QPainterPath())
Expand All @@ -67,19 +141,17 @@ def updatePath(self):
paths.append(c.getPath())

path = QtGui.QPainterPath()
transform = QtGui.QTransform()
path.setFillRule(self.fillRule())

ps1 = paths[0].toSubpathPolygons(transform)
ps2 = paths[1].toReversed().toSubpathPolygons(transform)
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):
intersection = p1.intersected(p2)
if not intersection.isEmpty():
path.addPolygon(intersection)
path.addPolygon(p1 + p2)
path.addPolygon(p1 + p2)

self.setPath(path)

0 comments on commit 1bd5c01

Please sign in to comment.