From 069a5bfeeaf2ea412176981c59df023c0231efaf Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Sun, 21 Feb 2016 00:17:17 -0800 Subject: [PATCH] Labels can rotate with line --- examples/plottingItems.py | 12 +-- examples/text.py | 2 +- pyqtgraph/graphicsItems/InfiniteLine.py | 110 +++++++----------------- pyqtgraph/graphicsItems/TextItem.py | 42 ++++++--- 4 files changed, 67 insertions(+), 99 deletions(-) diff --git a/examples/plottingItems.py b/examples/plottingItems.py index ffb808b5..a7926826 100644 --- a/examples/plottingItems.py +++ b/examples/plottingItems.py @@ -17,12 +17,14 @@ win.resize(1000,600) pg.setConfigOptions(antialias=True) 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)) -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) +inf1 = pg.InfiniteLine(movable=True, angle=90, text='x={value:0.2f}', + textOpts={'position':0.2, 'color': (200,200,100), 'fill': (200,200,200,50)}) +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.setTextLocation(position=0.75) -inf2.setTextLocation(shift=0.8) +#inf1.setTextLocation(position=0.75) +#inf2.setTextLocation(shift=0.8) p1.addItem(inf1) p1.addItem(inf2) p1.addItem(inf3) diff --git a/examples/text.py b/examples/text.py index 23f527e3..43302e96 100644 --- a/examples/text.py +++ b/examples/text.py @@ -23,7 +23,7 @@ plot.setWindowTitle('pyqtgraph example: text') curve = plot.plot(x,y) ## add a single curve ## Create text object, use HTML tags to specify color/size -text = pg.TextItem(html='
This is the
PEAK
', anchor=(-0.3,1.3), border='w', fill=(0, 0, 255, 100)) +text = pg.TextItem(html='
This is the
PEAK
', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100)) plot.addItem(text) text.setPos(0, y.max()) diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index f4b25860..e7cc12ce 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -31,9 +31,7 @@ class InfiniteLine(GraphicsObject): sigPositionChanged = QtCore.Signal(object) def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None, - hoverPen=None, label=False, textColor=None, textFill=None, - textPosition=[0.05, 0.5], textFormat="{:.3f}", draggableLabel=False, - suffix=None, name='InfiniteLine'): + hoverPen=None, text=None, textOpts=None, name=None): """ =============== ================================================================== **Arguments:** @@ -49,21 +47,12 @@ class InfiniteLine(GraphicsObject): Default pen is red. bounds Optional [min, max] bounding values. Bounds are only valid if the line is vertical or horizontal. - label if True, a label is displayed next to the line to indicate its - location in data coordinates - textColor color of the label. Can be any argument fn.mkColor can understand. - textFill A brush to use when filling within the border of the text. - textPosition list of float (0-1) that defines when the precise location of the - label. The first float governs the location of the label in the - 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 + text 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. + textOpts A dict of keyword arguments to use when constructing the + text label. See :class:`InfLineLabel`. + name Name of the item =============== ================================================================== """ @@ -79,18 +68,10 @@ class InfiniteLine(GraphicsObject): self.p = [0, 0] self.setAngle(angle) - if textColor is None: - textColor = (200, 200, 100) - self.textColor = textColor - self.textFill = textFill - self.textPosition = textPosition - self.suffix = suffix - - - self.textItem = InfLineLabel(self, fill=textFill) - self.textItem.setParentItem(self) - self.setDraggableLabel(draggableLabel) - self.showLabel(label) + if text is not None: + textOpts = {} if textOpts is None else textOpts + self.textItem = InfLineLabel(self, text=text, **textOpts) + self.textItem.setParentItem(self) self.anchorLeft = (1., 0.5) self.anchorRight = (0., 0.5) @@ -110,8 +91,6 @@ class InfiniteLine(GraphicsObject): self.setHoverPen(hoverPen) self.currentPen = self.pen - self.format = textFormat - self._name = name # 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) """ 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): self._name = name @@ -356,13 +296,21 @@ class InfiniteLine(GraphicsObject): 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.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.movable = movable + self.orthoPos = position # text will always be placed on the line at a position relative to view bounds + self.format = text self.line.sigPositionChanged.connect(self.valueChanged) TextItem.__init__(self, **kwds) self.valueChanged() @@ -412,7 +360,7 @@ class InfLineLabel(TextItem): return 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() if ev.isFinish(): self._moving = False @@ -427,6 +375,10 @@ class InfLineLabel(TextItem): if not ev.isExit() and self.movable: ev.acceptDrags(QtCore.Qt.LeftButton) + def viewTransformChanged(self): + self.updatePosition() + TextItem.viewTransformChanged(self) + def _posToRel(self, pos): # convert local position to relative position along line between view bounds view = self.getViewBox() diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py index c29b4f44..657e425b 100644 --- a/pyqtgraph/graphicsItems/TextItem.py +++ b/pyqtgraph/graphicsItems/TextItem.py @@ -1,13 +1,16 @@ +import numpy as np from ..Qt import QtCore, QtGui from ..Point import Point -from .UIGraphicsItem import * 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). """ - 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:** @@ -20,16 +23,19 @@ class TextItem(UIGraphicsItem): sets the lower-right corner. *border* A pen to use when drawing 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.rotateAxis = None if rotateAxis is None else Point(rotateAxis) #self.angle = 0 - UIGraphicsItem.__init__(self) + GraphicsObject.__init__(self) self.textItem = QtGui.QGraphicsTextItem() self.textItem.setParentItem(self) self._lastTransform = None @@ -101,9 +107,8 @@ class TextItem(UIGraphicsItem): self.updateText() def setAngle(self, angle): - self.textItem.resetTransform() - self.textItem.rotate(angle) - self.updateText() + self.angle = angle + self.updateTransform() def updateText(self): # update text position to obey anchor @@ -120,9 +125,6 @@ class TextItem(UIGraphicsItem): #s = self._exportOpts['resolutionScale'] #self.textItem.scale(s, s) - def viewRangeChanged(self): - self.updateText() - def boundingRect(self): return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect() @@ -160,7 +162,19 @@ class TextItem(UIGraphicsItem): t = pt.inverted()[0] # reset translation 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._lastTransform = pt + + self.updateText() + \ No newline at end of file