Improve target item - incorporate bits from PR 313 (#1318)
Overhaul TargetItem based on @lesauxvi 's PR #313
This commit is contained in:
parent
bb90ef1ec9
commit
5a08650853
46
.flake8
46
.flake8
@ -3,47 +3,5 @@ exclude = .git,.tox,__pycache__,doc,old,build,dist
|
||||
show_source = True
|
||||
statistics = True
|
||||
verbose = 2
|
||||
select =
|
||||
E101,
|
||||
E112,
|
||||
E122,
|
||||
E125,
|
||||
E133,
|
||||
E223,
|
||||
E224,
|
||||
E242,
|
||||
E273,
|
||||
E274,
|
||||
E901,
|
||||
E902,
|
||||
W191,
|
||||
W601,
|
||||
W602,
|
||||
W603,
|
||||
W604,
|
||||
E124,
|
||||
E231,
|
||||
E211,
|
||||
E261,
|
||||
E271,
|
||||
E272,
|
||||
E304,
|
||||
F401,
|
||||
F402,
|
||||
F403,
|
||||
F404,
|
||||
E501,
|
||||
E502,
|
||||
E702,
|
||||
E703,
|
||||
E711,
|
||||
E712,
|
||||
E721,
|
||||
F811,
|
||||
F812,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F841,
|
||||
W292
|
||||
max-line-length = 88
|
||||
extend-ignore = E203, W503
|
@ -46,3 +46,4 @@ Contents:
|
||||
uigraphicsitem
|
||||
graphicswidgetanchor
|
||||
dateaxisitem
|
||||
targetitem
|
||||
|
17
doc/source/graphicsItems/targetitem.rst
Normal file
17
doc/source/graphicsItems/targetitem.rst
Normal file
@ -0,0 +1,17 @@
|
||||
TargetItem
|
||||
==========
|
||||
|
||||
.. autoclass:: pyqtgraph.TargetItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.TargetItem.__init__
|
||||
|
||||
|
||||
TargetLabel
|
||||
==================
|
||||
|
||||
.. autoclass:: pyqtgraph.TargetLabel
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.TargetLabel.__init__
|
||||
|
@ -32,6 +32,53 @@ p1.addItem(inf1)
|
||||
p1.addItem(inf2)
|
||||
p1.addItem(inf3)
|
||||
|
||||
targetItem1 = pg.TargetItem(
|
||||
label=True,
|
||||
symbol="crosshair",
|
||||
labelOpts={
|
||||
"angle": 0
|
||||
}
|
||||
)
|
||||
|
||||
targetItem2 = pg.TargetItem(
|
||||
pos=(30, 5),
|
||||
size=20,
|
||||
label="vert={1:0.2f}",
|
||||
symbol="star",
|
||||
pen="#F4511E",
|
||||
labelOpts={
|
||||
"angle": 45,
|
||||
"offset": QtCore.QPoint(15, 15)
|
||||
}
|
||||
)
|
||||
|
||||
targetItem3 = pg.TargetItem(
|
||||
pos=(10, 10),
|
||||
size=10,
|
||||
label="Third Label",
|
||||
symbol="x",
|
||||
pen="#00ACC1",
|
||||
labelOpts={
|
||||
"anchor": QtCore.QPointF(0.5, 0.5),
|
||||
"offset": QtCore.QPointF(30, 0),
|
||||
"color": "#558B2F",
|
||||
"rotateAxis": (0, 1)
|
||||
}
|
||||
)
|
||||
|
||||
def callableFunction(x, y):
|
||||
return f"Square Values: ({x**2:.4f}, {y**2:.4f})"
|
||||
|
||||
targetItem4 = pg.TargetItem(
|
||||
pos=(10, -10),
|
||||
label=callableFunction
|
||||
)
|
||||
|
||||
p1.addItem(targetItem1)
|
||||
p1.addItem(targetItem2)
|
||||
p1.addItem(targetItem3)
|
||||
p1.addItem(targetItem4)
|
||||
|
||||
# Add a linear region with a label
|
||||
lr = pg.LinearRegionItem(values=[70, 80])
|
||||
p1.addItem(lr)
|
||||
|
@ -32,6 +32,7 @@ examples = OrderedDict([
|
||||
('GraphicsItems', OrderedDict([
|
||||
('Scatter Plot', 'ScatterPlot.py'),
|
||||
#('PlotItem', 'PlotItem.py'),
|
||||
('InfiniteLine', 'InfiniteLine.py'),
|
||||
('IsocurveItem', 'isocurve.py'),
|
||||
('GraphItem', 'GraphItem.py'),
|
||||
('ErrorBarItem', 'ErrorBarItem.py'),
|
||||
|
@ -232,6 +232,7 @@ from .graphicsItems.FillBetweenItem import *
|
||||
from .graphicsItems.LegendItem import *
|
||||
from .graphicsItems.ScatterPlotItem import *
|
||||
from .graphicsItems.ItemGroup import *
|
||||
from .graphicsItems.TargetItem import *
|
||||
|
||||
from .widgets.MultiPlotWidget import *
|
||||
from .widgets.ScatterPlotWidget import *
|
||||
|
@ -39,10 +39,22 @@ _USE_QRECT = QT_LIB not in ['PySide2', 'PySide6']
|
||||
|
||||
## Build all symbol paths
|
||||
name_list = ['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star',
|
||||
'arrow_up', 'arrow_right', 'arrow_down', 'arrow_left']
|
||||
'arrow_up', 'arrow_right', 'arrow_down', 'arrow_left', 'crosshair']
|
||||
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in name_list])
|
||||
Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||
Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||
|
||||
def makeCrosshair(r=0.5, w=1, h=1):
|
||||
path = QtGui.QPainterPath()
|
||||
rect = QtCore.QRectF(-r, -r, r * 2, r * 2)
|
||||
path.addEllipse(rect)
|
||||
path.moveTo(-w, 0)
|
||||
path.lineTo(w, 0)
|
||||
path.moveTo(0, -h)
|
||||
path.lineTo(0, h)
|
||||
return path
|
||||
Symbols['crosshair'] = makeCrosshair()
|
||||
|
||||
coords = {
|
||||
't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)],
|
||||
't1': [(-0.5, 0.5), (0, -0.5), (0.5, 0.5)],
|
||||
|
@ -1,132 +1,477 @@
|
||||
from math import atan2, pi
|
||||
|
||||
from ..Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
from ..Point import Point
|
||||
from .. import functions as fn
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .UIGraphicsItem import UIGraphicsItem
|
||||
from .TextItem import TextItem
|
||||
from .ScatterPlotItem import Symbols, makeCrosshair
|
||||
from .ViewBox import ViewBox
|
||||
import string
|
||||
import warnings
|
||||
|
||||
|
||||
class TargetItem(GraphicsObject):
|
||||
class TargetItem(UIGraphicsItem):
|
||||
"""Draws a draggable target symbol (circle plus crosshair).
|
||||
|
||||
The size of TargetItem will remain fixed on screen even as the view is zoomed.
|
||||
Includes an optional text label.
|
||||
"""
|
||||
sigDragged = QtCore.Signal(object)
|
||||
|
||||
def __init__(self, movable=True, radii=(5, 10, 10), pen=(255, 255, 0), brush=(0, 0, 255, 100)):
|
||||
GraphicsObject.__init__(self)
|
||||
self._bounds = None
|
||||
self._radii = radii
|
||||
self._picture = None
|
||||
sigPositionChanged = QtCore.Signal(object)
|
||||
sigPositionChangeFinished = QtCore.Signal(object)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
pos=None,
|
||||
size=10,
|
||||
radii=None,
|
||||
symbol="crosshair",
|
||||
pen=None,
|
||||
hoverPen=None,
|
||||
brush=None,
|
||||
hoverBrush=None,
|
||||
movable=True,
|
||||
label=None,
|
||||
labelOpts=None,
|
||||
):
|
||||
r"""
|
||||
Parameters
|
||||
----------
|
||||
pos : list, tuple, QPointF, QPoint, Optional
|
||||
Initial position of the symbol. Default is (0, 0)
|
||||
size : int
|
||||
Size of the symbol in pixels. Default is 10.
|
||||
radii : tuple of int
|
||||
Deprecated. Gives size of crosshair in screen pixels.
|
||||
pen : QPen, tuple, list or str
|
||||
Pen to use when drawing line. Can be any arguments that are valid
|
||||
for :func:`~pyqtgraph.mkPen`. Default pen is transparent yellow.
|
||||
brush : QBrush, tuple, list, or str
|
||||
Defines the brush that fill the symbol. Can be any arguments that
|
||||
is valid for :func:`~pyqtgraph.mkBrush`. Default is transparent
|
||||
blue.
|
||||
movable : bool
|
||||
If True, the symbol can be dragged to a new position by the user.
|
||||
hoverPen : QPen, tuple, list, or str
|
||||
Pen to use when drawing symbol when hovering over it. Can be any
|
||||
arguments that are valid for :func:`~pyqtgraph.mkPen`. Default pen
|
||||
is red.
|
||||
hoverBrush : QBrush, tuple, list or str
|
||||
Brush to use to fill the symbol when hovering over it. Can be any
|
||||
arguments that is valid for :func:`~pyqtgraph.mkBrush`. Default is
|
||||
transparent blue.
|
||||
symbol : QPainterPath or str
|
||||
QPainterPath to use for drawing the target, should be centered at
|
||||
``(0, 0)`` with ``max(width, height) == 1.0``. Alternatively a string
|
||||
which can be any symbol accepted by
|
||||
:func:`~pyqtgraph.ScatterPlotItem.setData`
|
||||
label : bool, str or callable, optional
|
||||
Text to be displayed in a label attached to the symbol, or None to
|
||||
show no label (default is None). May optionally include formatting
|
||||
strings to display the symbol value, or a callable that accepts x
|
||||
and y as inputs. If True, the label is ``x = {: >.3n}\ny = {: >.3n}``
|
||||
False or None will result in no text being displayed
|
||||
labelOpts : dict
|
||||
A dict of keyword arguments to use when constructing the text
|
||||
label. See :class:`TargetLabel` and :class:`~pyqtgraph.TextItem`
|
||||
"""
|
||||
super().__init__(self)
|
||||
self.movable = movable
|
||||
self.moving = False
|
||||
self.label = None
|
||||
self.labelAngle = 0
|
||||
self.pen = fn.mkPen(pen)
|
||||
self.brush = fn.mkBrush(brush)
|
||||
self._label = None
|
||||
self.mouseHovering = False
|
||||
|
||||
def setLabel(self, label):
|
||||
if label is None:
|
||||
if self.label is not None:
|
||||
self.label.scene().removeItem(self.label)
|
||||
self.label = None
|
||||
if radii is not None:
|
||||
warnings.warn(
|
||||
"'radii' is now deprecated, and will be removed in 0.13.0. Use 'size' "
|
||||
"parameter instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
symbol = makeCrosshair(*radii)
|
||||
size = 1
|
||||
|
||||
if pen is None:
|
||||
pen = (255, 255, 0)
|
||||
self.setPen(pen)
|
||||
|
||||
if hoverPen is None:
|
||||
hoverPen = (255, 0, 255)
|
||||
self.setHoverPen(hoverPen)
|
||||
|
||||
if brush is None:
|
||||
brush = (0, 0, 255, 50)
|
||||
self.setBrush(brush)
|
||||
|
||||
if hoverBrush is None:
|
||||
hoverBrush = (0, 255, 255, 100)
|
||||
self.setHoverBrush(hoverBrush)
|
||||
|
||||
self.currentPen = self.pen
|
||||
self.currentBrush = self.brush
|
||||
|
||||
self._shape = None
|
||||
|
||||
self._pos = Point(0, 0)
|
||||
if pos is None:
|
||||
pos = Point(0, 0)
|
||||
self.setPos(pos)
|
||||
|
||||
if isinstance(symbol, str):
|
||||
try:
|
||||
self._path = Symbols[symbol]
|
||||
except KeyError:
|
||||
raise KeyError("symbol name found in available Symbols")
|
||||
elif isinstance(symbol, QtGui.QPainterPath):
|
||||
self._path = symbol
|
||||
else:
|
||||
if self.label is None:
|
||||
self.label = TextItem()
|
||||
self.label.setParentItem(self)
|
||||
self.label.setText(label)
|
||||
self._updateLabel()
|
||||
raise TypeError("Unknown type provided as symbol")
|
||||
|
||||
def setLabelAngle(self, angle):
|
||||
if self.labelAngle != angle:
|
||||
self.labelAngle = angle
|
||||
self._updateLabel()
|
||||
self.scale = size
|
||||
self.setPath(self._path)
|
||||
self.setLabel(label, labelOpts)
|
||||
|
||||
@property
|
||||
def sigDragged(self):
|
||||
warnings.warn(
|
||||
"'sigDragged' has been deprecated and will be removed in 0.13.0. Use "
|
||||
"`sigPositionChanged` instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.sigPositionChangeFinished
|
||||
|
||||
def setPos(self, pos):
|
||||
"""Method to set the position to ``(x, y)`` within the plot view
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pos : tuple, list, QPointF, QPoint, or pg.Point
|
||||
Container that consists of ``(x, y)`` representation of where the
|
||||
TargetItem should be placed
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the type of ``pos`` does not match the known types to extract
|
||||
coordinate info from, a TypeError is raised
|
||||
"""
|
||||
if isinstance(pos, Point):
|
||||
newPos = pos
|
||||
elif isinstance(pos, (tuple, list)):
|
||||
newPos = Point(pos)
|
||||
elif isinstance(pos, (QtCore.QPointF, QtCore.QPoint)):
|
||||
newPos = Point(pos.x(), pos.y())
|
||||
else:
|
||||
raise TypeError
|
||||
if self._pos != newPos:
|
||||
self._pos = newPos
|
||||
super().setPos(self._pos)
|
||||
self.sigPositionChanged.emit(self)
|
||||
|
||||
def setBrush(self, *args, **kwargs):
|
||||
"""Set the brush that fills the symbol. Allowable arguments are any that
|
||||
are valid for :func:`~pyqtgraph.mkBrush`.
|
||||
"""
|
||||
self.brush = fn.mkBrush(*args, **kwargs)
|
||||
if not self.mouseHovering:
|
||||
self.currentBrush = self.brush
|
||||
self.update()
|
||||
|
||||
def setHoverBrush(self, *args, **kwargs):
|
||||
"""Set the brush that fills the symbol when hovering over it. Allowable
|
||||
arguments are any that are valid for :func:`~pyqtgraph.mkBrush`.
|
||||
"""
|
||||
self.hoverBrush = fn.mkBrush(*args, **kwargs)
|
||||
if self.mouseHovering:
|
||||
self.currentBrush = self.hoverBrush
|
||||
self.update()
|
||||
|
||||
def setPen(self, *args, **kwargs):
|
||||
"""Set the pen for drawing the symbol. Allowable arguments are any that
|
||||
are valid for :func:`~pyqtgraph.mkPen`."""
|
||||
self.pen = fn.mkPen(*args, **kwargs)
|
||||
if not self.mouseHovering:
|
||||
self.currentPen = self.pen
|
||||
self.update()
|
||||
|
||||
def setHoverPen(self, *args, **kwargs):
|
||||
"""Set the pen for drawing the symbol when hovering over it. Allowable
|
||||
arguments are any that are valid for
|
||||
:func:`~pyqtgraph.mkPen`."""
|
||||
self.hoverPen = fn.mkPen(*args, **kwargs)
|
||||
if self.mouseHovering:
|
||||
self.currentPen = self.hoverPen
|
||||
self.update()
|
||||
|
||||
def boundingRect(self):
|
||||
if self._picture is None:
|
||||
self._drawPicture()
|
||||
return self._bounds
|
||||
return self.shape().boundingRect()
|
||||
|
||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||
return [0, 0]
|
||||
def paint(self, p, *_):
|
||||
p.setPen(self.currentPen)
|
||||
p.setBrush(self.currentBrush)
|
||||
p.drawPath(self.shape())
|
||||
|
||||
def viewTransformChanged(self):
|
||||
self._picture = None
|
||||
def setPath(self, path):
|
||||
if path != self._path:
|
||||
self._path = path
|
||||
self._shape = None
|
||||
return None
|
||||
|
||||
def shape(self):
|
||||
if self._shape is None:
|
||||
s = self.generateShape()
|
||||
if s is None:
|
||||
return self._path
|
||||
self._shape = s
|
||||
|
||||
# beware--this can cause the view to adjust
|
||||
# which would immediately invalidate the shape.
|
||||
self.prepareGeometryChange()
|
||||
self._updateLabel()
|
||||
return self._shape
|
||||
|
||||
def _updateLabel(self):
|
||||
if self.label is None:
|
||||
return
|
||||
|
||||
# find an optimal location for text at the given angle
|
||||
angle = self.labelAngle * np.pi / 180.
|
||||
lbr = self.label.boundingRect()
|
||||
center = lbr.center()
|
||||
a = abs(np.sin(angle) * lbr.height()*0.5)
|
||||
b = abs(np.cos(angle) * lbr.width()*0.5)
|
||||
r = max(self._radii) + 2 + max(a, b)
|
||||
pos = self.mapFromScene(self.mapToScene(QtCore.QPointF(0, 0)) + r * QtCore.QPointF(np.cos(angle), -np.sin(angle)) - center)
|
||||
self.label.setPos(pos)
|
||||
|
||||
def paint(self, p, *args):
|
||||
if self._picture is None:
|
||||
self._drawPicture()
|
||||
self._picture.play(p)
|
||||
|
||||
def _drawPicture(self):
|
||||
self._picture = QtGui.QPicture()
|
||||
p = QtGui.QPainter(self._picture)
|
||||
p.setRenderHint(p.Antialiasing)
|
||||
|
||||
# Note: could do this with self.pixelLength, but this is faster.
|
||||
o = self.mapToScene(QtCore.QPointF(0, 0))
|
||||
dx = (self.mapToScene(QtCore.QPointF(1, 0)) - o).x()
|
||||
dy = (self.mapToScene(QtCore.QPointF(0, 1)) - o).y()
|
||||
if dx == 0 or dy == 0:
|
||||
p.end()
|
||||
self._bounds = QtCore.QRectF()
|
||||
return
|
||||
px = abs(1.0 / dx)
|
||||
py = abs(1.0 / dy)
|
||||
|
||||
r, w, h = self._radii
|
||||
w = w * px
|
||||
h = h * py
|
||||
rx = r * px
|
||||
ry = r * py
|
||||
rect = QtCore.QRectF(-rx, -ry, rx*2, ry*2)
|
||||
p.setPen(self.pen)
|
||||
p.setBrush(self.brush)
|
||||
p.drawEllipse(rect)
|
||||
p.drawLine(Point(-w, 0), Point(w, 0))
|
||||
p.drawLine(Point(0, -h), Point(0, h))
|
||||
p.end()
|
||||
|
||||
bx = max(w, rx)
|
||||
by = max(h, ry)
|
||||
self._bounds = QtCore.QRectF(-bx, -by, bx*2, by*2)
|
||||
def generateShape(self):
|
||||
dt = self.deviceTransform()
|
||||
if dt is None:
|
||||
self._shape = self._path
|
||||
return None
|
||||
v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
|
||||
dti = fn.invertQTransform(dt)
|
||||
devPos = dt.map(QtCore.QPointF(0, 0))
|
||||
tr = QtGui.QTransform()
|
||||
tr.translate(devPos.x(), devPos.y())
|
||||
va = atan2(v.y(), v.x())
|
||||
tr.rotate(va * 180.0 / pi)
|
||||
tr.scale(self.scale, self.scale)
|
||||
return dti.map(tr.map(self._path))
|
||||
|
||||
def mouseDragEvent(self, ev):
|
||||
if not self.movable:
|
||||
if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0:
|
||||
return
|
||||
if ev.button() == QtCore.Qt.LeftButton:
|
||||
if ev.isStart():
|
||||
self.moving = True
|
||||
self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos())
|
||||
self.startPosition = self.pos()
|
||||
ev.accept()
|
||||
if ev.isStart():
|
||||
self.symbolOffset = self.pos() - self.mapToView(ev.buttonDownPos())
|
||||
self.moving = True
|
||||
|
||||
if not self.moving:
|
||||
return
|
||||
self.setPos(self.symbolOffset + self.mapToView(ev.pos()))
|
||||
|
||||
self.setPos(self.cursorOffset + self.mapToParent(ev.pos()))
|
||||
if ev.isFinish():
|
||||
self.moving = False
|
||||
self.sigDragged.emit(self)
|
||||
self.sigPositionChangeFinished.emit(self)
|
||||
|
||||
def mouseClickEvent(self, ev):
|
||||
if self.moving and ev.button() == QtCore.Qt.RightButton:
|
||||
ev.accept()
|
||||
self.moving = False
|
||||
self.sigPositionChanged.emit(self)
|
||||
self.sigPositionChangeFinished.emit(self)
|
||||
|
||||
def setMouseHover(self, hover):
|
||||
# Inform the item that the mouse is(not) hovering over it
|
||||
if self.mouseHovering is hover:
|
||||
return
|
||||
self.mouseHovering = hover
|
||||
if hover:
|
||||
self.currentBrush = self.hoverBrush
|
||||
self.currentPen = self.hoverPen
|
||||
else:
|
||||
self.currentBrush = self.brush
|
||||
self.currentPen = self.pen
|
||||
self.update()
|
||||
|
||||
def hoverEvent(self, ev):
|
||||
if self.movable:
|
||||
ev.acceptDrags(QtCore.Qt.LeftButton)
|
||||
if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||
self.setMouseHover(True)
|
||||
else:
|
||||
self.setMouseHover(False)
|
||||
|
||||
def viewTransformChanged(self):
|
||||
GraphicsObject.viewTransformChanged(self)
|
||||
self._shape = None # invalidate shape, recompute later if requested.
|
||||
self.update()
|
||||
|
||||
def pos(self):
|
||||
"""Provides the current position of the TargetItem
|
||||
|
||||
Returns
|
||||
-------
|
||||
Point
|
||||
pg.Point of the current position of the TargetItem
|
||||
"""
|
||||
return self._pos
|
||||
|
||||
def label(self):
|
||||
"""Provides the TargetLabel if it exists
|
||||
|
||||
Returns
|
||||
-------
|
||||
TargetLabel or None
|
||||
If a TargetLabel exists for this TargetItem, return that, otherwise
|
||||
return None
|
||||
"""
|
||||
return self._label
|
||||
|
||||
def setLabel(self, text=None, labelOpts=None):
|
||||
"""Method to call to enable or disable the TargetLabel for displaying text
|
||||
|
||||
Parameters
|
||||
----------
|
||||
text : Callable or str, optional
|
||||
Details how to format the text, by default None
|
||||
If None, do not show any text next to the TargetItem
|
||||
If Callable, then the label will display the result of ``text(x, y)``
|
||||
If a fromatted string, then the output of ``text.format(x, y)`` will be
|
||||
displayed
|
||||
If a non-formatted string, then the text label will display ``text``, by
|
||||
default None
|
||||
labelOpts : dictionary, optional
|
||||
These arguments are passed on to :class:`~pyqtgraph.TextItem`
|
||||
"""
|
||||
if not text:
|
||||
if self._label is not None and self._label.scene() is not None:
|
||||
# remove the label if it's already added
|
||||
self._label.scene().removeItem(self._label)
|
||||
self._label = None
|
||||
else:
|
||||
# provide default text if text is True
|
||||
if text is True:
|
||||
# convert to default value or empty string
|
||||
text = "x = {: .3n}\ny = {: .3n}"
|
||||
|
||||
labelOpts = {} if labelOpts is None else labelOpts
|
||||
if self._label is not None:
|
||||
self._label.scene().removeItem(self._label)
|
||||
self._label = TargetLabel(self, text=text, **labelOpts)
|
||||
|
||||
def setLabelAngle(self, angle):
|
||||
warnings.warn(
|
||||
"TargetItem.setLabelAngle is deprecated and will be removed in 0.13.0."
|
||||
"Use TargetItem.label().setAngle() instead",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
if self.label() is not None and angle != self.label().angle():
|
||||
self.label().setAngle(angle)
|
||||
return None
|
||||
|
||||
|
||||
class TargetLabel(TextItem):
|
||||
"""A TextItem that attaches itself to a TargetItem.
|
||||
|
||||
This class extends TextItem with the following features :
|
||||
* Automatically positions adjacent to the symbol at a fixed position.
|
||||
* Automatically reformats text when the symbol location has changed.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : TargetItem
|
||||
The TargetItem to which this label will be attached to.
|
||||
text : str or callable, Optional
|
||||
Governs the text displayed, can be a fixed string or a format string
|
||||
that accepts the x, and y position of the target item; or be a callable
|
||||
method that accepts a tuple (x, y) and returns a string to be displayed.
|
||||
If None, an empty string is used. Default is None
|
||||
offset : tuple or list or QPointF or QPoint
|
||||
Position to set the anchor of the TargetLabel away from the center of
|
||||
the target in pixels, by default it is (20, 0).
|
||||
anchor : tuple, list, QPointF or QPoint
|
||||
Position to rotate the TargetLabel about, and position to set the
|
||||
offset value to see :class:`~pyqtgraph.TextItem` for more inforation.
|
||||
kwargs : dict of arguments that are passed on to
|
||||
:class:`~pyqtgraph.TextItem` constructor, excluding text parameter
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
target,
|
||||
text="",
|
||||
offset=(20, 0),
|
||||
anchor=(0, 0.5),
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(anchor=anchor, **kwargs)
|
||||
self.setParentItem(target)
|
||||
self.target = target
|
||||
self.setFormat(text)
|
||||
|
||||
if isinstance(offset, Point):
|
||||
self.offset = offset
|
||||
elif isinstance(offset, (tuple, list)):
|
||||
self.offset = Point(*offset)
|
||||
elif isinstance(offset, (QtCore.QPoint, QtCore.QPointF)):
|
||||
self.offset = Point(offset.x(), offset.y())
|
||||
else:
|
||||
raise TypeError("Offset parameter is the wrong data type")
|
||||
self.target.sigPositionChanged.connect(self.valueChanged)
|
||||
self.valueChanged()
|
||||
|
||||
def format(self):
|
||||
return self._format
|
||||
|
||||
def setFormat(self, text):
|
||||
"""Method to set how the TargetLabel should display the text. This
|
||||
method should be called from TargetItem.setLabel directly.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
text : Callable or str
|
||||
Details how to format the text.
|
||||
If Callable, then the label will display the result of ``text(x, y)``
|
||||
If a fromatted string, then the output of ``text.format(x, y)`` will be
|
||||
displayed
|
||||
If a non-formatted string, then the text label will display ``text``
|
||||
"""
|
||||
if not callable(text):
|
||||
parsed = list(string.Formatter().parse(text))
|
||||
if parsed and parsed[0][1] is not None:
|
||||
self.setProperty("formattableText", True)
|
||||
else:
|
||||
self.setText(text)
|
||||
self.setProperty("formattableText", False)
|
||||
else:
|
||||
self.setProperty("formattableText", False)
|
||||
self._format = text
|
||||
self.valueChanged()
|
||||
|
||||
def valueChanged(self):
|
||||
x, y = self.target.pos()
|
||||
if self.property("formattableText"):
|
||||
self.setText(self._format.format(float(x), float(y)))
|
||||
elif callable(self._format):
|
||||
self.setText(self._format(x, y))
|
||||
|
||||
def viewTransformChanged(self):
|
||||
viewbox = self.getViewBox()
|
||||
if isinstance(viewbox, ViewBox):
|
||||
viewPixelSize = viewbox.viewPixelSize()
|
||||
scaledOffset = QtCore.QPointF(
|
||||
self.offset.x() * viewPixelSize[0], self.offset.y() * viewPixelSize[1]
|
||||
)
|
||||
self.setPos(scaledOffset)
|
||||
return super().viewTransformChanged()
|
||||
|
||||
def mouseClickEvent(self, ev):
|
||||
return self.parentItem().mouseClickEvent(ev)
|
||||
|
||||
def mouseDragEvent(self, ev):
|
||||
targetItem = self.parentItem()
|
||||
if not targetItem.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0:
|
||||
return
|
||||
ev.accept()
|
||||
if ev.isStart():
|
||||
targetItem.symbolOffset = targetItem.pos() - self.mapToView(
|
||||
ev.buttonDownPos()
|
||||
)
|
||||
targetItem.moving = True
|
||||
|
||||
if not targetItem.moving:
|
||||
return
|
||||
targetItem.setPos(targetItem.symbolOffset + self.mapToView(ev.pos()))
|
||||
|
||||
if ev.isFinish():
|
||||
targetItem.moving = False
|
||||
targetItem.sigPositionChangeFinished.emit(self)
|
||||
|
Loading…
Reference in New Issue
Block a user