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
|
show_source = True
|
||||||
statistics = True
|
statistics = True
|
||||||
verbose = 2
|
verbose = 2
|
||||||
select =
|
max-line-length = 88
|
||||||
E101,
|
extend-ignore = E203, W503
|
||||||
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
|
|
@ -46,3 +46,4 @@ Contents:
|
|||||||
uigraphicsitem
|
uigraphicsitem
|
||||||
graphicswidgetanchor
|
graphicswidgetanchor
|
||||||
dateaxisitem
|
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(inf2)
|
||||||
p1.addItem(inf3)
|
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
|
# Add a linear region with a label
|
||||||
lr = pg.LinearRegionItem(values=[70, 80])
|
lr = pg.LinearRegionItem(values=[70, 80])
|
||||||
p1.addItem(lr)
|
p1.addItem(lr)
|
||||||
|
@ -32,6 +32,7 @@ examples = OrderedDict([
|
|||||||
('GraphicsItems', OrderedDict([
|
('GraphicsItems', OrderedDict([
|
||||||
('Scatter Plot', 'ScatterPlot.py'),
|
('Scatter Plot', 'ScatterPlot.py'),
|
||||||
#('PlotItem', 'PlotItem.py'),
|
#('PlotItem', 'PlotItem.py'),
|
||||||
|
('InfiniteLine', 'InfiniteLine.py'),
|
||||||
('IsocurveItem', 'isocurve.py'),
|
('IsocurveItem', 'isocurve.py'),
|
||||||
('GraphItem', 'GraphItem.py'),
|
('GraphItem', 'GraphItem.py'),
|
||||||
('ErrorBarItem', 'ErrorBarItem.py'),
|
('ErrorBarItem', 'ErrorBarItem.py'),
|
||||||
|
@ -232,6 +232,7 @@ from .graphicsItems.FillBetweenItem import *
|
|||||||
from .graphicsItems.LegendItem import *
|
from .graphicsItems.LegendItem import *
|
||||||
from .graphicsItems.ScatterPlotItem import *
|
from .graphicsItems.ScatterPlotItem import *
|
||||||
from .graphicsItems.ItemGroup import *
|
from .graphicsItems.ItemGroup import *
|
||||||
|
from .graphicsItems.TargetItem import *
|
||||||
|
|
||||||
from .widgets.MultiPlotWidget import *
|
from .widgets.MultiPlotWidget import *
|
||||||
from .widgets.ScatterPlotWidget import *
|
from .widgets.ScatterPlotWidget import *
|
||||||
|
@ -39,10 +39,22 @@ _USE_QRECT = QT_LIB not in ['PySide2', 'PySide6']
|
|||||||
|
|
||||||
## Build all symbol paths
|
## Build all symbol paths
|
||||||
name_list = ['o', 's', 't', 't1', 't2', 't3', 'd', '+', 'x', 'p', 'h', 'star',
|
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 = OrderedDict([(name, QtGui.QPainterPath()) for name in name_list])
|
||||||
Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1))
|
||||||
Symbols['s'].addRect(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 = {
|
coords = {
|
||||||
't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)],
|
't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)],
|
||||||
't1': [(-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
|
from ..Qt import QtGui, QtCore
|
||||||
import numpy as np
|
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
|
from .UIGraphicsItem import UIGraphicsItem
|
||||||
from .TextItem import TextItem
|
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).
|
"""Draws a draggable target symbol (circle plus crosshair).
|
||||||
|
|
||||||
The size of TargetItem will remain fixed on screen even as the view is zoomed.
|
The size of TargetItem will remain fixed on screen even as the view is zoomed.
|
||||||
Includes an optional text label.
|
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)):
|
sigPositionChanged = QtCore.Signal(object)
|
||||||
GraphicsObject.__init__(self)
|
sigPositionChangeFinished = QtCore.Signal(object)
|
||||||
self._bounds = None
|
|
||||||
self._radii = radii
|
def __init__(
|
||||||
self._picture = None
|
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.movable = movable
|
||||||
self.moving = False
|
self.moving = False
|
||||||
self.label = None
|
self._label = None
|
||||||
self.labelAngle = 0
|
self.mouseHovering = False
|
||||||
self.pen = fn.mkPen(pen)
|
|
||||||
self.brush = fn.mkBrush(brush)
|
|
||||||
|
|
||||||
def setLabel(self, label):
|
if radii is not None:
|
||||||
if label is None:
|
warnings.warn(
|
||||||
if self.label is not None:
|
"'radii' is now deprecated, and will be removed in 0.13.0. Use 'size' "
|
||||||
self.label.scene().removeItem(self.label)
|
"parameter instead",
|
||||||
self.label = None
|
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:
|
else:
|
||||||
if self.label is None:
|
raise TypeError("Unknown type provided as symbol")
|
||||||
self.label = TextItem()
|
|
||||||
self.label.setParentItem(self)
|
|
||||||
self.label.setText(label)
|
|
||||||
self._updateLabel()
|
|
||||||
|
|
||||||
def setLabelAngle(self, angle):
|
self.scale = size
|
||||||
if self.labelAngle != angle:
|
self.setPath(self._path)
|
||||||
self.labelAngle = angle
|
self.setLabel(label, labelOpts)
|
||||||
self._updateLabel()
|
|
||||||
|
@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):
|
def boundingRect(self):
|
||||||
if self._picture is None:
|
return self.shape().boundingRect()
|
||||||
self._drawPicture()
|
|
||||||
return self._bounds
|
|
||||||
|
|
||||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
def paint(self, p, *_):
|
||||||
return [0, 0]
|
p.setPen(self.currentPen)
|
||||||
|
p.setBrush(self.currentBrush)
|
||||||
|
p.drawPath(self.shape())
|
||||||
|
|
||||||
def viewTransformChanged(self):
|
def setPath(self, path):
|
||||||
self._picture = None
|
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.prepareGeometryChange()
|
||||||
self._updateLabel()
|
return self._shape
|
||||||
|
|
||||||
def _updateLabel(self):
|
def generateShape(self):
|
||||||
if self.label is None:
|
dt = self.deviceTransform()
|
||||||
return
|
if dt is None:
|
||||||
|
self._shape = self._path
|
||||||
# find an optimal location for text at the given angle
|
return None
|
||||||
angle = self.labelAngle * np.pi / 180.
|
v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
|
||||||
lbr = self.label.boundingRect()
|
dti = fn.invertQTransform(dt)
|
||||||
center = lbr.center()
|
devPos = dt.map(QtCore.QPointF(0, 0))
|
||||||
a = abs(np.sin(angle) * lbr.height()*0.5)
|
tr = QtGui.QTransform()
|
||||||
b = abs(np.cos(angle) * lbr.width()*0.5)
|
tr.translate(devPos.x(), devPos.y())
|
||||||
r = max(self._radii) + 2 + max(a, b)
|
va = atan2(v.y(), v.x())
|
||||||
pos = self.mapFromScene(self.mapToScene(QtCore.QPointF(0, 0)) + r * QtCore.QPointF(np.cos(angle), -np.sin(angle)) - center)
|
tr.rotate(va * 180.0 / pi)
|
||||||
self.label.setPos(pos)
|
tr.scale(self.scale, self.scale)
|
||||||
|
return dti.map(tr.map(self._path))
|
||||||
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 mouseDragEvent(self, ev):
|
def mouseDragEvent(self, ev):
|
||||||
if not self.movable:
|
if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0:
|
||||||
return
|
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()
|
ev.accept()
|
||||||
|
if ev.isStart():
|
||||||
|
self.symbolOffset = self.pos() - self.mapToView(ev.buttonDownPos())
|
||||||
|
self.moving = True
|
||||||
|
|
||||||
if not self.moving:
|
if not self.moving:
|
||||||
return
|
return
|
||||||
|
self.setPos(self.symbolOffset + self.mapToView(ev.pos()))
|
||||||
|
|
||||||
self.setPos(self.cursorOffset + self.mapToParent(ev.pos()))
|
|
||||||
if ev.isFinish():
|
if ev.isFinish():
|
||||||
self.moving = False
|
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):
|
def hoverEvent(self, ev):
|
||||||
if self.movable:
|
if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||||
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