pyqtgraph/pyqtgraph/graphicsItems/TextItem.py

218 lines
8.1 KiB
Python

from math import atan2, degrees
from ..Qt import QtCore, QtGui
from ..Point import Point
from .. import functions as fn
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):
"""
============== =================================================================================
**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.
============== =================================================================================
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 = QtGui.QGraphicsTextItem()
self.textItem.setParentItem(self)
self._lastTransform = None
self._lastScene = 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 QtGui.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 QtGui.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 QtGui.QGraphicsTextItem.setTextWidth().
"""
self.textItem.setTextWidth(*args)
self.updateTextPos()
def setFont(self, *args):
"""
Set the font for this text.
See QtGui.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 QtGui.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 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 = 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 = degrees(atan2(d.y(), d.x()))
angle += a
t.rotate(angle)
self.setTransform(t)
self._lastTransform = pt
self.updateTextPos()