Skip to content

Commit 1bd5c01

Browse files
authored
Merge pull request pyqtgraph#3006 from j9ac9k/fix-fillbetween-regression
Allow users to specify FillRule for FillBetweenItem, undo regressionfrom pyqtgraph#2971
2 parents e200849 + a933120 commit 1bd5c01

File tree

1 file changed

+93
-21
lines changed

1 file changed

+93
-21
lines changed
Lines changed: 93 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from typing import Union
2+
13
from .. import functions as fn
2-
from ..Qt import QtGui, QtWidgets
4+
from ..Qt import QtGui, QtWidgets, QtCore
35
from .PlotCurveItem import PlotCurveItem
46
from .PlotDataItem import PlotDataItem
57

@@ -9,33 +11,104 @@ class FillBetweenItem(QtWidgets.QGraphicsPathItem):
911
"""
1012
GraphicsItem filling the space between two PlotDataItems.
1113
"""
12-
def __init__(self, curve1=None, curve2=None, brush=None, pen=None):
13-
QtWidgets.QGraphicsPathItem.__init__(self)
14+
def __init__(
15+
self,
16+
curve1: Union[PlotDataItem, PlotCurveItem],
17+
curve2: Union[PlotDataItem, PlotCurveItem],
18+
brush=None,
19+
pen=None,
20+
fillRule: QtCore.Qt.FillRule=QtCore.Qt.FillRule.OddEvenFill
21+
):
22+
"""FillBetweenItem fills a region between two curves with a specified
23+
:class:`~QtGui.QBrush`.
24+
25+
Parameters
26+
----------
27+
curve1 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
28+
Line to draw fill from
29+
curve2 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
30+
Line to draw fill to
31+
brush : color_like, optional
32+
Arguments accepted by :func:`~pyqtgraph.mkBrush`, used
33+
to create the :class:`~QtGui.QBrush` instance used to draw the item
34+
by default None
35+
pen : color_like, optional
36+
Arguments accepted by :func:`~pyqtgraph.mkColor`, used
37+
to create the :class:`~QtGui.QPen` instance used to draw the item
38+
by default ``None``
39+
fillRule : QtCore.Qt.FillRule, optional
40+
FillRule to be applied to the underlying :class:`~QtGui.QPainterPath`
41+
instance, by default ``QtCore.Qt.FillRule.OddEvenFill``
42+
43+
Raises
44+
------
45+
ValueError
46+
Raised when ``None`` is passed in as either ``curve1``
47+
or ``curve2``
48+
TypeError
49+
Raised when either ``curve1`` or ``curve2`` is not either
50+
:class:`~pyqtgraph.PlotDataItem` or :class:`~pyqtgraph.PlotCurveItem`
51+
"""
52+
super().__init__()
1453
self.curves = None
54+
self._fillRule = fillRule
1555
if curve1 is not None and curve2 is not None:
1656
self.setCurves(curve1, curve2)
1757
elif curve1 is not None or curve2 is not None:
18-
raise Exception("Must specify two curves to fill between.")
58+
raise ValueError("Must specify two curves to fill between.")
1959

2060
if brush is not None:
2161
self.setBrush(brush)
2262
self.setPen(pen)
2363
self.updatePath()
64+
65+
def fillRule(self):
66+
return self._fillRule
67+
68+
def setFillRule(self, fillRule: QtCore.Qt.FillRule=QtCore.Qt.FillRule.OddEvenFill):
69+
"""Set the underlying :class:`~QtGui.QPainterPath` to the specified
70+
:class:`~QtCore.Qt.FillRule`
71+
72+
This can be useful for allowing in the filling of voids.
73+
74+
Parameters
75+
----------
76+
fillRule : QtCore.Qt.FillRule
77+
A member of the :class:`~QtCore.Qt.FillRule` enum
78+
"""
79+
self._fillRule = fillRule
80+
self.updatePath()
2481

2582
def setBrush(self, *args, **kwds):
26-
"""Change the fill brush. Accepts the same arguments as pg.mkBrush()"""
83+
"""Change the fill brush. Accepts the same arguments as :func:`~pyqtgraph.mkBrush`
84+
"""
2785
QtWidgets.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
2886

2987
def setPen(self, *args, **kwds):
88+
"""Change the fill pen. Accepts the same arguments as :func:`~pyqtgraph.mkColor`
89+
"""
3090
QtWidgets.QGraphicsPathItem.setPen(self, fn.mkPen(*args, **kwds))
3191

32-
def setCurves(self, curve1, curve2):
33-
"""Set the curves to fill between.
34-
35-
Arguments must be instances of PlotDataItem or PlotCurveItem.
36-
37-
Added in version 0.9.9
38-
"""
92+
def setCurves(
93+
self,
94+
curve1: Union[PlotDataItem, PlotCurveItem],
95+
curve2: Union[PlotDataItem, PlotCurveItem]
96+
):
97+
"""Method to set the Curves to draw the FillBetweenItem between
98+
99+
Parameters
100+
----------
101+
curve1 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
102+
Line to draw fill from
103+
curve2 : :class:`~pyqtgraph.PlotDataItem` | :class:`~pyqtgraph.PlotCurveItem`
104+
Line to draw fill to
105+
106+
Raises
107+
------
108+
TypeError
109+
Raised when input arguments are not either :class:`~pyqtgraph.PlotDataItem` or
110+
:class:`~pyqtgraph.PlotCurveItem`
111+
"""
39112
if self.curves is not None:
40113
for c in self.curves:
41114
try:
@@ -45,7 +118,7 @@ def setCurves(self, curve1, curve2):
45118

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

56129
def curveChanged(self):
57130
self.updatePath()
131+
58132
def updatePath(self):
59133
if self.curves is None:
60134
self.setPath(QtGui.QPainterPath())
@@ -67,19 +141,17 @@ def updatePath(self):
67141
paths.append(c.getPath())
68142

69143
path = QtGui.QPainterPath()
70-
transform = QtGui.QTransform()
144+
path.setFillRule(self.fillRule())
71145

72-
ps1 = paths[0].toSubpathPolygons(transform)
73-
ps2 = paths[1].toReversed().toSubpathPolygons(transform)
146+
ps1 = paths[0].toSubpathPolygons()
147+
ps2 = paths[1].toReversed().toSubpathPolygons()
74148
ps2.reverse()
75149

76150
if len(ps1) == 0 or len(ps2) == 0:
77151
self.setPath(QtGui.QPainterPath())
78152
return
79-
153+
80154
for p1, p2 in zip(ps1, ps2):
81-
intersection = p1.intersected(p2)
82-
if not intersection.isEmpty():
83-
path.addPolygon(intersection)
84-
path.addPolygon(p1 + p2)
155+
path.addPolygon(p1 + p2)
156+
85157
self.setPath(path)

0 commit comments

Comments
 (0)