File: FillBetweenItem.py

package info (click to toggle)
python-pyqtgraph 0.13.7-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 8,072 kB
  • sloc: python: 54,043; makefile: 127; ansic: 40; sh: 2
file content (157 lines) | stat: -rw-r--r-- 5,550 bytes parent folder | download | duplicates (2)
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)