InfiniteLine: add markers and ability to limit drawing region
This commit is contained in:
parent
9d0779cc32
commit
653c91a683
@ -31,7 +31,8 @@ class InfiniteLine(GraphicsObject):
|
||||
sigPositionChanged = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None,
|
||||
hoverPen=None, label=None, labelOpts=None, name=None):
|
||||
hoverPen=None, label=None, labelOpts=None, span=(0, 1), markers=None,
|
||||
name=None):
|
||||
"""
|
||||
=============== ==================================================================
|
||||
**Arguments:**
|
||||
@ -41,22 +42,28 @@ class InfiniteLine(GraphicsObject):
|
||||
pen Pen to use when drawing line. Can be any arguments that are valid
|
||||
for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
|
||||
yellow.
|
||||
hoverPen Pen to use when the mouse cursor hovers over the line.
|
||||
Only used when movable=True.
|
||||
movable If True, the line can be dragged to a new position by the user.
|
||||
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
||||
line is vertical or horizontal.
|
||||
hoverPen Pen to use when drawing line when hovering over it. Can be any
|
||||
arguments that are valid for :func:`mkPen <pyqtgraph.mkPen>`.
|
||||
Default pen is red.
|
||||
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
||||
line is vertical or horizontal.
|
||||
label Text to be displayed in a label attached to the line, or
|
||||
None to show no label (default is None). May optionally
|
||||
include formatting strings to display the line value.
|
||||
labelOpts A dict of keyword arguments to use when constructing the
|
||||
text label. See :class:`InfLineLabel`.
|
||||
span Optional tuple (min, max) giving the range over the view to draw
|
||||
the line. For example, with a vertical line, use span=(0.5, 1)
|
||||
to draw only on the top half of the view.
|
||||
markers List of (marker, position, size) tuples, one per marker to display
|
||||
on the line. See the addMarker method.
|
||||
name Name of the item
|
||||
=============== ==================================================================
|
||||
"""
|
||||
self._boundingRect = None
|
||||
self._line = None
|
||||
|
||||
self._name = name
|
||||
|
||||
@ -79,12 +86,26 @@ class InfiniteLine(GraphicsObject):
|
||||
if pen is None:
|
||||
pen = (200, 200, 100)
|
||||
self.setPen(pen)
|
||||
|
||||
if hoverPen is None:
|
||||
self.setHoverPen(color=(255,0,0), width=self.pen.width())
|
||||
else:
|
||||
self.setHoverPen(hoverPen)
|
||||
|
||||
self.span = span
|
||||
self.currentPen = self.pen
|
||||
|
||||
self.markers = []
|
||||
self._maxMarkerSize = 0
|
||||
if markers is not None:
|
||||
for m in markers:
|
||||
self.addMarker(*m)
|
||||
|
||||
# Cache variables for managing bounds
|
||||
self._endPoints = [0, 1] #
|
||||
self._bounds = None
|
||||
self._lastViewSize = None
|
||||
|
||||
if label is not None:
|
||||
labelOpts = {} if labelOpts is None else labelOpts
|
||||
self.label = InfLineLabel(self, text=label, **labelOpts)
|
||||
@ -99,6 +120,11 @@ class InfiniteLine(GraphicsObject):
|
||||
self.maxRange = bounds
|
||||
self.setValue(self.value())
|
||||
|
||||
def bounds(self):
|
||||
"""Return the (minimum, maximum) values allowed when dragging.
|
||||
"""
|
||||
return self.maxRange[:]
|
||||
|
||||
def setPen(self, *args, **kwargs):
|
||||
"""Set the pen for drawing the line. Allowable arguments are any that are valid
|
||||
for :func:`mkPen <pyqtgraph.mkPen>`."""
|
||||
@ -115,11 +141,70 @@ class InfiniteLine(GraphicsObject):
|
||||
If the line is not movable, then hovering is also disabled.
|
||||
|
||||
Added in version 0.9.9."""
|
||||
# If user did not supply a width, then copy it from pen
|
||||
widthSpecified = ((len(args) == 1 and
|
||||
(isinstance(args[0], QtGui.QPen) or
|
||||
(isinstance(args[0], dict) and 'width' in args[0]))
|
||||
) or 'width' in kwargs)
|
||||
self.hoverPen = fn.mkPen(*args, **kwargs)
|
||||
if not widthSpecified:
|
||||
self.hoverPen.setWidth(self.pen.width())
|
||||
|
||||
if self.mouseHovering:
|
||||
self.currentPen = self.hoverPen
|
||||
self.update()
|
||||
|
||||
def addMarker(self, marker, position=0.5, size=10.0):
|
||||
"""Add a marker to be displayed on the line.
|
||||
|
||||
============= =========================================================
|
||||
**Arguments**
|
||||
marker String indicating the style of marker to add:
|
||||
'<|', '|>', '>|', '|<', '<|>', '>|<', '^', 'v', 'o'
|
||||
position Position (0.0-1.0) along the visible extent of the line
|
||||
to place the marker. Default is 0.5.
|
||||
size Size of the marker in pixels. Default is 10.0.
|
||||
============= =========================================================
|
||||
"""
|
||||
path = QtGui.QPainterPath()
|
||||
if marker == 'o':
|
||||
path.addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||
if '<|' in marker:
|
||||
p = QtGui.QPolygonF([Point(0.5, 0), Point(0, -0.5), Point(-0.5, 0)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
if '|>' in marker:
|
||||
p = QtGui.QPolygonF([Point(0.5, 0), Point(0, 0.5), Point(-0.5, 0)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
if '>|' in marker:
|
||||
p = QtGui.QPolygonF([Point(0.5, -0.5), Point(0, 0), Point(-0.5, -0.5)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
if '|<' in marker:
|
||||
p = QtGui.QPolygonF([Point(0.5, 0.5), Point(0, 0), Point(-0.5, 0.5)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
if '^' in marker:
|
||||
p = QtGui.QPolygonF([Point(0, -0.5), Point(0.5, 0), Point(0, 0.5)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
if 'v' in marker:
|
||||
p = QtGui.QPolygonF([Point(0, -0.5), Point(-0.5, 0), Point(0, 0.5)])
|
||||
path.addPolygon(p)
|
||||
path.closeSubpath()
|
||||
|
||||
self.markers.append((path, position, size))
|
||||
self._maxMarkerSize = max([m[2] / 2. for m in self.markers])
|
||||
self.update()
|
||||
|
||||
def clearMarkers(self):
|
||||
""" Remove all markers from this line.
|
||||
"""
|
||||
self.markers = []
|
||||
self._maxMarkerSize = 0
|
||||
self.update()
|
||||
|
||||
def setAngle(self, angle):
|
||||
"""
|
||||
Takes angle argument in degrees.
|
||||
@ -128,7 +213,7 @@ class InfiniteLine(GraphicsObject):
|
||||
Note that the use of value() and setValue() changes if the line is
|
||||
not vertical or horizontal.
|
||||
"""
|
||||
self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135
|
||||
self.angle = angle #((angle+45) % 180) - 45 ## -45 <= angle < 135
|
||||
self.resetTransform()
|
||||
self.rotate(self.angle)
|
||||
self.update()
|
||||
@ -200,33 +285,96 @@ class InfiniteLine(GraphicsObject):
|
||||
#print "ignore", change
|
||||
#return GraphicsObject.itemChange(self, change, val)
|
||||
|
||||
def setSpan(self, mn, mx):
|
||||
if self.span != (mn, mx):
|
||||
self.span = (mn, mx)
|
||||
self.update()
|
||||
|
||||
def _invalidateCache(self):
|
||||
self._line = None
|
||||
self._boundingRect = None
|
||||
|
||||
def boundingRect(self):
|
||||
if self._boundingRect is None:
|
||||
def _computeBoundingRect(self):
|
||||
#br = UIGraphicsItem.boundingRect(self)
|
||||
br = self.viewRect()
|
||||
if br is None:
|
||||
vr = self.viewRect() # bounds of containing ViewBox mapped to local coords.
|
||||
if vr is None:
|
||||
return QtCore.QRectF()
|
||||
|
||||
## add a 4-pixel radius around the line for mouse interaction.
|
||||
|
||||
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
||||
if px is None:
|
||||
px = 0
|
||||
w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
|
||||
pw = max(self.pen.width() / 2, self.hoverPen.width() / 2)
|
||||
w = max(4, self._maxMarkerSize + pw) + 1
|
||||
w = w * px
|
||||
br = QtCore.QRectF(vr)
|
||||
br.setBottom(-w)
|
||||
br.setTop(w)
|
||||
|
||||
length = br.width()
|
||||
left = br.left() + length * self.span[0]
|
||||
right = br.left() + length * self.span[1]
|
||||
br.setLeft(left - w)
|
||||
br.setRight(right + w)
|
||||
br = br.normalized()
|
||||
self._boundingRect = br
|
||||
self._line = QtCore.QLineF(br.right(), 0.0, br.left(), 0.0)
|
||||
|
||||
vs = self.getViewBox().size()
|
||||
|
||||
if self._bounds != br or self._lastViewSize != vs:
|
||||
self._bounds = br
|
||||
self._lastViewSize = vs
|
||||
self.prepareGeometryChange()
|
||||
|
||||
self._endPoints = (left, right)
|
||||
self._lastViewRect = vr
|
||||
|
||||
return self._bounds
|
||||
|
||||
def boundingRect(self):
|
||||
if self._boundingRect is None:
|
||||
self._boundingRect = self._computeBoundingRect()
|
||||
return self._boundingRect
|
||||
|
||||
def paint(self, p, *args):
|
||||
p.setPen(self.currentPen)
|
||||
p.drawLine(self._line)
|
||||
p.setRenderHint(p.Antialiasing)
|
||||
|
||||
left, right = self._endPoints
|
||||
pen = self.currentPen
|
||||
pen.setJoinStyle(QtCore.Qt.MiterJoin)
|
||||
p.setPen(pen)
|
||||
p.drawLine(Point(left, 0), Point(right, 0))
|
||||
|
||||
|
||||
if len(self.markers) == 0:
|
||||
return
|
||||
|
||||
# paint markers in native coordinate system
|
||||
tr = p.transform()
|
||||
p.resetTransform()
|
||||
|
||||
start = tr.map(Point(left, 0))
|
||||
end = tr.map(Point(right, 0))
|
||||
up = tr.map(Point(left, 1))
|
||||
dif = end - start
|
||||
length = Point(dif).length()
|
||||
angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi
|
||||
|
||||
p.translate(start)
|
||||
p.rotate(angle)
|
||||
|
||||
up = up - start
|
||||
det = up.x() * dif.y() - dif.x() * up.y()
|
||||
p.scale(1, 1 if det > 0 else -1)
|
||||
|
||||
p.setBrush(fn.mkBrush(self.currentPen.color()))
|
||||
#p.setPen(fn.mkPen(None))
|
||||
tr = p.transform()
|
||||
for path, pos, size in self.markers:
|
||||
p.setTransform(tr)
|
||||
x = length * pos
|
||||
p.translate(x, 0)
|
||||
p.scale(size, size)
|
||||
p.drawPath(path)
|
||||
|
||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||
if axis == 0:
|
||||
|
Loading…
Reference in New Issue
Block a user