Labels can rotate with line

This commit is contained in:
Luke Campagnola 2016-02-21 00:17:17 -08:00
parent a8510c3354
commit 069a5bfeea
4 changed files with 67 additions and 99 deletions

View File

@ -17,12 +17,14 @@ win.resize(1000,600)
pg.setConfigOptions(antialias=True) 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, text='x={value:0.2f}',
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) textOpts={'position':0.2, 'color': (200,200,100), 'fill': (200,200,200,50)})
inf3 = pg.InfiniteLine(movable=True, angle=45) inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-2, 2], hoverPen=(0,200,0), text='y={value:0.2f}mm',
textOpts={'color': (200,0,0), 'movable': True, 'fill': 0.5})
inf3 = pg.InfiniteLine(movable=True, angle=45, text='diagonal', textOpts={'rotateAxis': [1, 0]})
inf1.setPos([2,2]) inf1.setPos([2,2])
inf1.setTextLocation(position=0.75) #inf1.setTextLocation(position=0.75)
inf2.setTextLocation(shift=0.8) #inf2.setTextLocation(shift=0.8)
p1.addItem(inf1) p1.addItem(inf1)
p1.addItem(inf2) p1.addItem(inf2)
p1.addItem(inf3) p1.addItem(inf3)

View File

@ -23,7 +23,7 @@ plot.setWindowTitle('pyqtgraph example: text')
curve = plot.plot(x,y) ## add a single curve curve = plot.plot(x,y) ## add a single curve
## Create text object, use HTML tags to specify color/size ## Create text object, use HTML tags to specify color/size
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,1.3), border='w', fill=(0, 0, 255, 100)) text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100))
plot.addItem(text) plot.addItem(text)
text.setPos(0, y.max()) text.setPos(0, y.max())

View File

@ -31,9 +31,7 @@ class InfiniteLine(GraphicsObject):
sigPositionChanged = QtCore.Signal(object) sigPositionChanged = QtCore.Signal(object)
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None, def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None,
hoverPen=None, label=False, textColor=None, textFill=None, hoverPen=None, text=None, textOpts=None, name=None):
textPosition=[0.05, 0.5], textFormat="{:.3f}", draggableLabel=False,
suffix=None, name='InfiniteLine'):
""" """
=============== ================================================================== =============== ==================================================================
**Arguments:** **Arguments:**
@ -49,21 +47,12 @@ class InfiniteLine(GraphicsObject):
Default pen is red. Default pen is red.
bounds Optional [min, max] bounding values. Bounds are only valid if the bounds Optional [min, max] bounding values. Bounds are only valid if the
line is vertical or horizontal. line is vertical or horizontal.
label if True, a label is displayed next to the line to indicate its text Text to be displayed in a label attached to the line, or
location in data coordinates None to show no label (default is None). May optionally
textColor color of the label. Can be any argument fn.mkColor can understand. include formatting strings to display the line value.
textFill A brush to use when filling within the border of the text. textOpts A dict of keyword arguments to use when constructing the
textPosition list of float (0-1) that defines when the precise location of the text label. See :class:`InfLineLabel`.
label. The first float governs the location of the label in the name Name of the item
direction of the line, whereas the second one governs the shift
of the label from one side of the line to the other in the
orthogonal direction.
textFormat Any new python 3 str.format() format.
draggableLabel Bool. If True, the user can relocate the label during the dragging.
If set to True, the first entry of textPosition is no longer
useful.
suffix If not None, corresponds to the unit to show next to the label
name name of the item
=============== ================================================================== =============== ==================================================================
""" """
@ -79,18 +68,10 @@ class InfiniteLine(GraphicsObject):
self.p = [0, 0] self.p = [0, 0]
self.setAngle(angle) self.setAngle(angle)
if textColor is None: if text is not None:
textColor = (200, 200, 100) textOpts = {} if textOpts is None else textOpts
self.textColor = textColor self.textItem = InfLineLabel(self, text=text, **textOpts)
self.textFill = textFill self.textItem.setParentItem(self)
self.textPosition = textPosition
self.suffix = suffix
self.textItem = InfLineLabel(self, fill=textFill)
self.textItem.setParentItem(self)
self.setDraggableLabel(draggableLabel)
self.showLabel(label)
self.anchorLeft = (1., 0.5) self.anchorLeft = (1., 0.5)
self.anchorRight = (0., 0.5) self.anchorRight = (0., 0.5)
@ -110,8 +91,6 @@ class InfiniteLine(GraphicsObject):
self.setHoverPen(hoverPen) self.setHoverPen(hoverPen)
self.currentPen = self.pen self.currentPen = self.pen
self.format = textFormat
self._name = name self._name = name
# Cache complex value for drawing speed-up (#PR267) # Cache complex value for drawing speed-up (#PR267)
@ -308,46 +287,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()
self.textItem.updatePosition()
def showLabel(self, state):
"""
Display or not the label indicating the location of the line in data
coordinates.
============== ======================================================
**Arguments:**
state If True, the label is shown. Otherwise, it is hidden.
============== ======================================================
"""
self.textItem.setVisible(state)
def setTextLocation(self, position=0.05, shift=0.5):
"""
Set the parameters that defines the location of the label on the axis.
The position *parameter* governs the location of the label in the
direction of the line, whereas the *shift* governs the shift of the
label from one side of the line to the other in the orthogonal
direction.
============== ======================================================
**Arguments:**
position float (range of value = [0-1])
shift float (range of value = [0-1]).
============== ======================================================
"""
self.textItem.orthoPos = position
self.textItem.shiftPos = shift
self.textItem.updatePosition()
def setDraggableLabel(self, state):
"""
Set the state of the label regarding its behaviour during the dragging
of the line. If True, then the location of the label change during the
dragging of the line.
"""
self.textItem.setMovable(state)
def setName(self, name): def setName(self, name):
self._name = name self._name = name
@ -356,13 +296,21 @@ class InfiniteLine(GraphicsObject):
class InfLineLabel(TextItem): class InfLineLabel(TextItem):
# a text label that attaches itself to an InfiniteLine """
def __init__(self, line, **kwds): A TextItem that attaches itself to an InfiniteLine.
This class extends TextItem with the following features:
* Automatically positions adjacent to the line at a fixed position along
the line and within the view box.
* Automatically reformats text when the line value has changed.
* Can optionally be dragged to change its location along the line.
"""
def __init__(self, line, text="", movable=False, position=0.5, **kwds):
self.line = line self.line = line
self.movable = False self.movable = movable
self.dragAxis = None # 0=x, 1=y self.orthoPos = position # text will always be placed on the line at a position relative to view bounds
self.orthoPos = 0.5 # text will always be placed on the line at a position relative to view bounds self.format = text
self.format = "{value}"
self.line.sigPositionChanged.connect(self.valueChanged) self.line.sigPositionChanged.connect(self.valueChanged)
TextItem.__init__(self, **kwds) TextItem.__init__(self, **kwds)
self.valueChanged() self.valueChanged()
@ -412,7 +360,7 @@ class InfLineLabel(TextItem):
return return
rel = self._posToRel(ev.pos()) rel = self._posToRel(ev.pos())
self.orthoPos = self._startPosition + rel - self._cursorOffset self.orthoPos = np.clip(self._startPosition + rel - self._cursorOffset, 0, 1)
self.updatePosition() self.updatePosition()
if ev.isFinish(): if ev.isFinish():
self._moving = False self._moving = False
@ -427,6 +375,10 @@ class InfLineLabel(TextItem):
if not ev.isExit() and self.movable: if not ev.isExit() and self.movable:
ev.acceptDrags(QtCore.Qt.LeftButton) ev.acceptDrags(QtCore.Qt.LeftButton)
def viewTransformChanged(self):
self.updatePosition()
TextItem.viewTransformChanged(self)
def _posToRel(self, pos): def _posToRel(self, pos):
# convert local position to relative position along line between view bounds # convert local position to relative position along line between view bounds
view = self.getViewBox() view = self.getViewBox()

View File

@ -1,13 +1,16 @@
import numpy as np
from ..Qt import QtCore, QtGui from ..Qt import QtCore, QtGui
from ..Point import Point from ..Point import Point
from .UIGraphicsItem import *
from .. import functions as fn from .. import functions as fn
from .GraphicsObject import GraphicsObject
class TextItem(UIGraphicsItem):
class TextItem(GraphicsObject):
""" """
GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox).
""" """
def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0): def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0),
border=None, fill=None, angle=0, rotateAxis=None):
""" """
============== ================================================================================= ============== =================================================================================
**Arguments:** **Arguments:**
@ -20,16 +23,19 @@ class TextItem(UIGraphicsItem):
sets the lower-right corner. sets the lower-right corner.
*border* A pen to use when drawing the border *border* A pen to use when drawing the border
*fill* A brush to use when filling within the border *fill* A brush to use when filling within the border
*angle* Angle in degrees to rotate text. Default is 0; text will be displayed upright.
*rotateAxis* If None, then a text angle of 0 always points along the +x axis of the scene.
If a QPointF or (x,y) sequence is given, then it represents a vector direction
in the parent's coordinate system that the 0-degree line will be aligned to. This
Allows text to follow both the position and orientation of its parent while still
discarding any scale and shear factors.
============== ================================================================================= ============== =================================================================================
""" """
## not working yet
#*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's
#transformation will be ignored)
self.anchor = Point(anchor) self.anchor = Point(anchor)
self.rotateAxis = None if rotateAxis is None else Point(rotateAxis)
#self.angle = 0 #self.angle = 0
UIGraphicsItem.__init__(self) GraphicsObject.__init__(self)
self.textItem = QtGui.QGraphicsTextItem() self.textItem = QtGui.QGraphicsTextItem()
self.textItem.setParentItem(self) self.textItem.setParentItem(self)
self._lastTransform = None self._lastTransform = None
@ -101,9 +107,8 @@ class TextItem(UIGraphicsItem):
self.updateText() self.updateText()
def setAngle(self, angle): def setAngle(self, angle):
self.textItem.resetTransform() self.angle = angle
self.textItem.rotate(angle) self.updateTransform()
self.updateText()
def updateText(self): def updateText(self):
# update text position to obey anchor # update text position to obey anchor
@ -120,9 +125,6 @@ class TextItem(UIGraphicsItem):
#s = self._exportOpts['resolutionScale'] #s = self._exportOpts['resolutionScale']
#self.textItem.scale(s, s) #self.textItem.scale(s, s)
def viewRangeChanged(self):
self.updateText()
def boundingRect(self): def boundingRect(self):
return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect() return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect()
@ -160,7 +162,19 @@ class TextItem(UIGraphicsItem):
t = pt.inverted()[0] t = pt.inverted()[0]
# reset translation # reset translation
t.setMatrix(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), 0, 0, t.m33()) t.setMatrix(t.m11(), t.m12(), t.m13(), t.m21(), t.m22(), t.m23(), 0, 0, t.m33())
# apply rotation
angle = -self.angle
if self.rotateAxis is not None:
d = pt.map(self.rotateAxis) - pt.map(Point(0, 0))
a = np.arctan2(d.y(), d.x()) * 180 / np.pi
angle += a
t.rotate(angle)
self.setTransform(t) self.setTransform(t)
self._lastTransform = pt self._lastTransform = pt
self.updateText()