Added inflinelabel class, label dragging and position update works.

Update to TextItem to allow mouse interaction
This commit is contained in:
Luke Campagnola 2016-02-19 00:41:42 -08:00
parent 010cda004b
commit 5172b782b5
3 changed files with 99 additions and 82 deletions

View File

@ -18,7 +18,7 @@ pg.setConfigOptions(antialias=True)
p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100)) p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100))
inf1 = pg.InfiniteLine(movable=True, angle=90, label=True, textPosition=[0.5, 0.2], textColor=(200,200,100), textFill=(200,200,200,50)) inf1 = pg.InfiniteLine(movable=True, angle=90, label=True, textPosition=[0.5, 0.2], textColor=(200,200,100), textFill=(200,200,200,50))
inf2 = pg.InfiniteLine(movable=True, angle=0, label=True, pen=(0, 0, 200), textColor=(200,0,0), bounds = [-2, 2], suffix="mm", hoverPen=(0,200,0), draggableLabel=True) inf2 = pg.InfiniteLine(movable=True, angle=0, label=True, pen=(0, 0, 200), textColor=(200,0,0), bounds = [-2, 2], suffix="mm", hoverPen=(0,200,0), draggableLabel=True, textFill=0.5)
inf3 = pg.InfiniteLine(movable=True, angle=45) inf3 = pg.InfiniteLine(movable=True, angle=45)
inf1.setPos([2,2]) inf1.setPos([2,2])
inf1.setTextLocation(position=0.75) inf1.setTextLocation(position=0.75)

View File

@ -84,14 +84,13 @@ class InfiniteLine(GraphicsObject):
self.textColor = textColor self.textColor = textColor
self.textFill = textFill self.textFill = textFill
self.textPosition = textPosition self.textPosition = textPosition
self.draggableLabel = draggableLabel
self.suffix = suffix self.suffix = suffix
if (self.angle == 0 or self.angle == 90) and label:
self.textItem = TextItem(fill=textFill) self.textItem = InfLineLabel(self, fill=textFill)
self.textItem.setParentItem(self) self.textItem.setParentItem(self)
else: self.setDraggableLabel(draggableLabel)
self.textItem = None self.showLabel(label)
self.anchorLeft = (1., 0.5) self.anchorLeft = (1., 0.5)
self.anchorRight = (0., 0.5) self.anchorRight = (0., 0.5)
@ -192,53 +191,8 @@ class InfiniteLine(GraphicsObject):
if self.p != newPos: if self.p != newPos:
self.p = newPos self.p = newPos
self._invalidateCache() self._invalidateCache()
if self.textItem is not None and isinstance(self.getViewBox(), ViewBox):
self.updateText()
else: # no label displayed or called just before being dragged for the first time
GraphicsObject.setPos(self, Point(self.p))
self.update()
self.sigPositionChanged.emit(self)
def updateText(self):
"""
Update the content displayed by the textItem. Called only if a textItem
is requested and if the item has already been added to a PlotItem.
"""
rangeX, rangeY = self.getViewBox().viewRange()
xmin, xmax = rangeX
ymin, ymax = rangeY
pos, shift = self.textPosition
if self.angle == 90: # vertical line
diffMin = self.value()-xmin
limInf = shift*(xmax-xmin)
if diffMin < limInf:
self.textItem.anchor = Point(self.anchorRight)
else:
self.textItem.anchor = Point(self.anchorLeft)
fmt = " x = " + self.format
if self.suffix is not None:
fmt = fmt + self.suffix
self.textItem.setText(fmt.format(self.value()), color=self.textColor)
posY = ymin+pos*(ymax-ymin)
self._exactPos = Point(self.value(), posY)
elif self.angle == 0: # horizontal line
diffMin = self.value()-ymin
limInf = shift*(ymax-ymin)
if diffMin < limInf:
self.textItem.anchor = Point(self.anchorUp)
else:
self.textItem.anchor = Point(self.anchorDown)
fmt = " y = " + self.format
if self.suffix is not None:
fmt = fmt + self.suffix
self.textItem.setText(fmt.format(self.value()), color=self.textColor)
posX = xmin+pos*(xmax-xmin)
self._exactPos = Point(posX, self.value())
if self.draggableLabel:
GraphicsObject.setPos(self, Point(self.p)) GraphicsObject.setPos(self, Point(self.p))
else: # precise location needed self.sigPositionChanged.emit(self)
GraphicsObject.setPos(self, self._exactPos)
def getXPos(self): def getXPos(self):
return self.p[0] return self.p[0]
@ -354,8 +308,7 @@ class InfiniteLine(GraphicsObject):
(eg, the view range has changed or the view was resized) (eg, the view range has changed or the view was resized)
""" """
self._invalidateCache() self._invalidateCache()
if isinstance(self.getViewBox(), ViewBox) and self.textItem is not None: self.textItem.updatePosition()
self.updateText()
def showLabel(self, state): def showLabel(self, state):
""" """
@ -367,12 +320,7 @@ class InfiniteLine(GraphicsObject):
state If True, the label is shown. Otherwise, it is hidden. state If True, the label is shown. Otherwise, it is hidden.
============== ====================================================== ============== ======================================================
""" """
if state: self.textItem.setVisible(state)
self.textItem = TextItem(fill=self.textFill)
self.textItem.setParentItem(self)
self.viewTransformChanged()
else:
self.textItem = None
def setTextLocation(self, position=0.05, shift=0.5): def setTextLocation(self, position=0.05, shift=0.5):
""" """
@ -388,10 +336,9 @@ class InfiniteLine(GraphicsObject):
shift float (range of value = [0-1]). shift float (range of value = [0-1]).
============== ====================================================== ============== ======================================================
""" """
pos = np.clip(position, 0, 1) self.textItem.orthoPos = position
shift = np.clip(shift, 0, 1) self.textItem.shiftPos = shift
self.textPosition = [pos, shift] self.textItem.updatePosition()
self.update()
def setDraggableLabel(self, state): def setDraggableLabel(self, state):
""" """
@ -399,11 +346,93 @@ class InfiniteLine(GraphicsObject):
of the line. If True, then the location of the label change during the of the line. If True, then the location of the label change during the
dragging of the line. dragging of the line.
""" """
self.draggableLabel = state self.textItem.setMovable(state)
self.update()
def setName(self, name): def setName(self, name):
self._name = name self._name = name
def name(self): def name(self):
return self._name return self._name
class InfLineLabel(TextItem):
# a text label that attaches itself to an InfiniteLine
def __init__(self, line, **kwds):
self.line = line
self.movable = False
self.dragAxis = None # 0=x, 1=y
self.orthoPos = 0.5 # text will always be placed on the line at a position relative to view bounds
self.format = "{value}"
self.line.sigPositionChanged.connect(self.valueChanged)
TextItem.__init__(self, **kwds)
self.valueChanged()
def valueChanged(self):
if not self.isVisible():
return
value = self.line.value()
self.setText(self.format.format(value=value))
self.updatePosition()
def updatePosition(self):
view = self.getViewBox()
if not self.isVisible() or not isinstance(view, ViewBox):
# not in a viewbox, skip update
return
# 1. determine view extents along line axis
tr = view.childGroup.itemTransform(self.line)[0]
vr = tr.mapRect(view.viewRect())
pt1 = Point(vr.left(), 0)
pt2 = Point(vr.right(), 0)
# 2. pick relative point between extents and set position
pt = pt2 * self.orthoPos + pt1 * (1-self.orthoPos)
self.setPos(pt)
def setVisible(self, v):
TextItem.setVisible(self, v)
if v:
self.updateText()
self.updatePosition()
def setMovable(self, m):
self.movable = m
self.setAcceptHoverEvents(m)
def mouseDragEvent(self, ev):
if self.movable and ev.button() == QtCore.Qt.LeftButton:
if ev.isStart():
self._moving = True
self._cursorOffset = self._posToRel(ev.buttonDownPos())
self._startPosition = self.orthoPos
ev.accept()
if not self._moving:
return
rel = self._posToRel(ev.pos())
self.orthoPos = self._startPosition + rel - self._cursorOffset
self.updatePosition()
if ev.isFinish():
self._moving = False
def mouseClickEvent(self, ev):
if self.moving and ev.button() == QtCore.Qt.RightButton:
ev.accept()
self.orthoPos = self._startPosition
self.moving = False
def hoverEvent(self, ev):
if not ev.isExit() and self.movable:
ev.acceptDrags(QtCore.Qt.LeftButton)
def _posToRel(self, pos):
# convert local position to relative position along line between view bounds
view = self.getViewBox()
tr = view.childGroup.itemTransform(self.line)[0]
vr = tr.mapRect(view.viewRect())
pos = self.mapToParent(pos)
return (pos.x() - vr.left()) / vr.width()

View File

@ -41,7 +41,7 @@ class TextItem(UIGraphicsItem):
self.fill = fn.mkBrush(fill) self.fill = fn.mkBrush(fill)
self.border = fn.mkPen(border) self.border = fn.mkPen(border)
self.rotate(angle) self.rotate(angle)
self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport #self.textItem.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport
def setText(self, text, color=(200,200,200)): def setText(self, text, color=(200,200,200)):
""" """
@ -114,21 +114,9 @@ class TextItem(UIGraphicsItem):
s = self._exportOpts['resolutionScale'] s = self._exportOpts['resolutionScale']
self.textItem.scale(s, s) self.textItem.scale(s, s)
#br = self.textItem.mapRectToParent(self.textItem.boundingRect()) self.textItem.setTransform(self.sceneTransform().inverted()[0])
self.textItem.setPos(0,0) self.textItem.setPos(0,0)
br = self.textItem.boundingRect() self.textItem.setPos(-self.textItem.mapToParent(Point(0,0)))
apos = self.textItem.mapToParent(Point(br.width()*self.anchor.x(), br.height()*self.anchor.y()))
#print br, apos
self.textItem.setPos(-apos.x(), -apos.y())
#def textBoundingRect(self):
### return the bounds of the text box in device coordinates
#pos = self.mapToDevice(QtCore.QPointF(0,0))
#if pos is None:
#return None
#tbr = self.textItem.boundingRect()
#return QtCore.QRectF(pos.x() - tbr.width()*self.anchor.x(), pos.y() - tbr.height()*self.anchor.y(), tbr.width(), tbr.height())
def viewRangeChanged(self): def viewRangeChanged(self):
self.updateText() self.updateText()