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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
|
from math import atan2, degrees
from .. import functions as fn
from ..Point import Point
from ..Qt import QtCore, QtGui, QtWidgets
from .GraphicsObject import GraphicsObject
__all__ = ['TextItem']
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, rotateAxis=None, ensureInBounds=False):
"""
================ =================================================================================
**Arguments:**
*text* The text to display
*color* The color of the text (any format accepted by pg.mkColor)
*html* If specified, this overrides both *text* and *color*
*anchor* A QPointF or (x,y) sequence indicating what region of the text box will
be anchored to the item's position. A value of (0,0) sets the upper-left corner
of the text box to be at the position specified by setPos(), while a value of (1,1)
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.
*ensureInBounds* Ensures that the entire TextItem will be visible when using autorange, but may
produce runaway scaling in certain circumstances (See issue #2642). Setting to
"True" retains legacy behavior.
================ =================================================================================
The effects of the `rotateAxis` and `angle` arguments are added independently. So for example:
* rotateAxis=None, angle=0 -> normal horizontal text
* rotateAxis=None, angle=90 -> normal vertical text
* rotateAxis=(1, 0), angle=0 -> text aligned with x axis of its parent
* rotateAxis=(0, 1), angle=0 -> text aligned with y axis of its parent
* rotateAxis=(1, 0), angle=90 -> text orthogonal to x axis of its parent
"""
self.anchor = Point(anchor)
self.rotateAxis = None if rotateAxis is None else Point(rotateAxis)
#self.angle = 0
GraphicsObject.__init__(self)
self.textItem = QtWidgets.QGraphicsTextItem()
self.textItem.setParentItem(self)
self._lastTransform = None
self._lastScene = None
# Note: The following is pretty scuffed; ideally there would likely be
# some inheritance changes, But this is the least-intrusive thing that
# works for now
if ensureInBounds:
self.dataBounds = None
self._bounds = QtCore.QRectF()
if html is None:
self.setColor(color)
self.setText(text)
else:
self.setHtml(html)
self.fill = fn.mkBrush(fill)
self.border = fn.mkPen(border)
self.setAngle(angle)
def setText(self, text, color=None):
"""
Set the text of this item.
This method sets the plain text of the item; see also setHtml().
"""
if color is not None:
self.setColor(color)
self.setPlainText(text)
def setPlainText(self, text):
"""
Set the plain text to be rendered by this item.
See QtWidgets.QGraphicsTextItem.setPlainText().
"""
if text != self.toPlainText():
self.textItem.setPlainText(text)
self.updateTextPos()
def toPlainText(self):
return self.textItem.toPlainText()
def setHtml(self, html):
"""
Set the HTML code to be rendered by this item.
See QtWidgets.QGraphicsTextItem.setHtml().
"""
if self.toHtml() != html:
self.textItem.setHtml(html)
self.updateTextPos()
def toHtml(self):
return self.textItem.toHtml()
def setTextWidth(self, *args):
"""
Set the width of the text.
If the text requires more space than the width limit, then it will be
wrapped into multiple lines.
See QtWidgets.QGraphicsTextItem.setTextWidth().
"""
self.textItem.setTextWidth(*args)
self.updateTextPos()
def setFont(self, *args):
"""
Set the font for this text.
See QtWidgets.QGraphicsTextItem.setFont().
"""
self.textItem.setFont(*args)
self.updateTextPos()
def setAngle(self, angle):
"""
Set the angle of the text in degrees.
This sets the rotation angle of the text as a whole, measured
counter-clockwise from the x axis of the parent. Note that this rotation
angle does not depend on horizontal/vertical scaling of the parent.
"""
self.angle = angle
self.updateTransform(force=True)
def setAnchor(self, anchor):
self.anchor = Point(anchor)
self.updateTextPos()
def setColor(self, color):
"""
Set the color for this text.
See QtWidgets.QGraphicsItem.setDefaultTextColor().
"""
self.color = fn.mkColor(color)
self.textItem.setDefaultTextColor(self.color)
def updateTextPos(self):
# update text position to obey anchor
r = self.textItem.boundingRect()
tl = self.textItem.mapToParent(r.topLeft())
br = self.textItem.mapToParent(r.bottomRight())
offset = (br - tl) * self.anchor
self.textItem.setPos(-offset)
def dataBounds(self, ax, frac=1.0, orthoRange=None):
"""
Returns only the anchor point for when calulating view ranges.
Sacrifices some visual polish for fixing issue #2642.
"""
if orthoRange:
range_min, range_max = orthoRange[0], orthoRange[1]
if not range_min <= self.anchor[ax] <= range_max:
return [None, None]
return [self.anchor[ax], self.anchor[ax]]
def boundingRect(self):
return self.textItem.mapRectToParent(self.textItem.boundingRect())
def viewTransformChanged(self):
# called whenever view transform has changed.
# Do this here to avoid double-updates when view changes.
self.updateTransform()
def paint(self, p, *args):
# this is not ideal because it requires the transform to be updated at every draw.
# ideally, we would have a sceneTransformChanged event to react to..
s = self.scene()
ls = self._lastScene
if s is not ls:
if ls is not None:
ls.sigPrepareForPaint.disconnect(self.updateTransform)
self._lastScene = s
if s is not None:
s.sigPrepareForPaint.connect(self.updateTransform)
self.updateTransform()
p.setTransform(self.sceneTransform())
if self.border.style() != QtCore.Qt.PenStyle.NoPen or self.fill.style() != QtCore.Qt.BrushStyle.NoBrush:
p.setPen(self.border)
p.setBrush(self.fill)
p.setRenderHint(p.RenderHint.Antialiasing, True)
p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect()))
def setVisible(self, v):
GraphicsObject.setVisible(self, v)
if v:
self.updateTransform()
def updateTransform(self, force=False):
if not self.isVisible():
return
# update transform such that this item has the correct orientation
# and scaling relative to the scene, but inherits its position from its
# parent.
# This is similar to setting ItemIgnoresTransformations = True, but
# does not break mouse interaction and collision detection.
p = self.parentItem()
if p is None:
pt = QtGui.QTransform()
else:
pt = p.sceneTransform()
if not force and pt == self._lastTransform:
return
t = fn.invertQTransform(pt)
# 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 = degrees(atan2(d.y(), d.x()))
angle += a
t.rotate(angle)
self.setTransform(t)
self._lastTransform = pt
self.updateTextPos()
|