Merge branch 'develop' into image-testing
Conflicts: pyqtgraph/tests/__init__.py
This commit is contained in:
commit
c65ca6d243
45
examples/InfiniteLine.py
Normal file
45
examples/InfiniteLine.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This example demonstrates some of the plotting items available in pyqtgraph.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import numpy as np
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
win = pg.GraphicsWindow(title="Plotting items examples")
|
||||||
|
win.resize(1000,600)
|
||||||
|
|
||||||
|
# Enable antialiasing for prettier plots
|
||||||
|
pg.setConfigOptions(antialias=True)
|
||||||
|
|
||||||
|
# Create a plot with some random data
|
||||||
|
p1 = win.addPlot(title="Plot Items example", y=np.random.normal(size=100, scale=10), pen=0.5)
|
||||||
|
p1.setYRange(-40, 40)
|
||||||
|
|
||||||
|
# Add three infinite lines with labels
|
||||||
|
inf1 = pg.InfiniteLine(movable=True, angle=90, label='x={value:0.2f}',
|
||||||
|
labelOpts={'position':0.1, 'color': (200,200,100), 'fill': (200,200,200,50), 'movable': True})
|
||||||
|
inf2 = pg.InfiniteLine(movable=True, angle=0, pen=(0, 0, 200), bounds = [-20, 20], hoverPen=(0,200,0), label='y={value:0.2f}mm',
|
||||||
|
labelOpts={'color': (200,0,0), 'movable': True, 'fill': (0, 0, 200, 100)})
|
||||||
|
inf3 = pg.InfiniteLine(movable=True, angle=45, pen='g', label='diagonal',
|
||||||
|
labelOpts={'rotateAxis': [1, 0], 'fill': (0, 200, 0, 100), 'movable': True})
|
||||||
|
inf1.setPos([2,2])
|
||||||
|
p1.addItem(inf1)
|
||||||
|
p1.addItem(inf2)
|
||||||
|
p1.addItem(inf3)
|
||||||
|
|
||||||
|
# Add a linear region with a label
|
||||||
|
lr = pg.LinearRegionItem(values=[70, 80])
|
||||||
|
p1.addItem(lr)
|
||||||
|
label = pg.InfLineLabel(lr.lines[1], "region 1", position=0.95, rotateAxis=(1,0), anchor=(1, 1))
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
38
examples/Symbols.py
Executable file
38
examples/Symbols.py
Executable file
@ -0,0 +1,38 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
This example shows all the scatter plot symbols available in pyqtgraph.
|
||||||
|
|
||||||
|
These symbols are used to mark point locations for scatter plots and some line
|
||||||
|
plots, similar to "markers" in matplotlib and vispy.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
win = pg.GraphicsWindow(title="Scatter Plot Symbols")
|
||||||
|
win.resize(1000,600)
|
||||||
|
|
||||||
|
pg.setConfigOptions(antialias=True)
|
||||||
|
|
||||||
|
plot = win.addPlot(title="Plotting with symbols")
|
||||||
|
plot.addLegend()
|
||||||
|
plot.plot([0, 1, 2, 3, 4], pen=(0,0,200), symbolBrush=(0,0,200), symbolPen='w', symbol='o', symbolSize=14, name="symbol='o'")
|
||||||
|
plot.plot([1, 2, 3, 4, 5], pen=(0,128,0), symbolBrush=(0,128,0), symbolPen='w', symbol='t', symbolSize=14, name="symbol='t'")
|
||||||
|
plot.plot([2, 3, 4, 5, 6], pen=(19,234,201), symbolBrush=(19,234,201), symbolPen='w', symbol='t1', symbolSize=14, name="symbol='t1'")
|
||||||
|
plot.plot([3, 4, 5, 6, 7], pen=(195,46,212), symbolBrush=(195,46,212), symbolPen='w', symbol='t2', symbolSize=14, name="symbol='t2'")
|
||||||
|
plot.plot([4, 5, 6, 7, 8], pen=(250,194,5), symbolBrush=(250,194,5), symbolPen='w', symbol='t3', symbolSize=14, name="symbol='t3'")
|
||||||
|
plot.plot([5, 6, 7, 8, 9], pen=(54,55,55), symbolBrush=(55,55,55), symbolPen='w', symbol='s', symbolSize=14, name="symbol='s'")
|
||||||
|
plot.plot([6, 7, 8, 9, 10], pen=(0,114,189), symbolBrush=(0,114,189), symbolPen='w', symbol='p', symbolSize=14, name="symbol='p'")
|
||||||
|
plot.plot([7, 8, 9, 10, 11], pen=(217,83,25), symbolBrush=(217,83,25), symbolPen='w', symbol='h', symbolSize=14, name="symbol='h'")
|
||||||
|
plot.plot([8, 9, 10, 11, 12], pen=(237,177,32), symbolBrush=(237,177,32), symbolPen='w', symbol='star', symbolSize=14, name="symbol='star'")
|
||||||
|
plot.plot([9, 10, 11, 12, 13], pen=(126,47,142), symbolBrush=(126,47,142), symbolPen='w', symbol='+', symbolSize=14, name="symbol='+'")
|
||||||
|
plot.plot([10, 11, 12, 13, 14], pen=(119,172,48), symbolBrush=(119,172,48), symbolPen='w', symbol='d', symbolSize=14, name="symbol='d'")
|
||||||
|
plot.setXRange(-2, 4)
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
52
examples/infiniteline_performance.py
Normal file
52
examples/infiniteline_performance.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore
|
||||||
|
import numpy as np
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.ptime import time
|
||||||
|
app = QtGui.QApplication([])
|
||||||
|
|
||||||
|
p = pg.plot()
|
||||||
|
p.setWindowTitle('pyqtgraph performance: InfiniteLine')
|
||||||
|
p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
||||||
|
p.setLabel('bottom', 'Index', units='B')
|
||||||
|
curve = p.plot()
|
||||||
|
|
||||||
|
# Add a large number of horizontal InfiniteLine to plot
|
||||||
|
for i in range(100):
|
||||||
|
line = pg.InfiniteLine(pos=np.random.randint(5000), movable=True)
|
||||||
|
p.addItem(line)
|
||||||
|
|
||||||
|
data = np.random.normal(size=(50, 5000))
|
||||||
|
ptr = 0
|
||||||
|
lastTime = time()
|
||||||
|
fps = None
|
||||||
|
|
||||||
|
|
||||||
|
def update():
|
||||||
|
global curve, data, ptr, p, lastTime, fps
|
||||||
|
curve.setData(data[ptr % 10])
|
||||||
|
ptr += 1
|
||||||
|
now = time()
|
||||||
|
dt = now - lastTime
|
||||||
|
lastTime = now
|
||||||
|
if fps is None:
|
||||||
|
fps = 1.0/dt
|
||||||
|
else:
|
||||||
|
s = np.clip(dt*3., 0, 1)
|
||||||
|
fps = fps * (1-s) + (1.0/dt) * s
|
||||||
|
p.setTitle('%0.2f fps' % fps)
|
||||||
|
app.processEvents() # force complete redraw for every plot
|
||||||
|
|
||||||
|
|
||||||
|
timer = QtCore.QTimer()
|
||||||
|
timer.timeout.connect(update)
|
||||||
|
timer.start(0)
|
||||||
|
|
||||||
|
|
||||||
|
# Start Qt event loop unless running in interactive mode.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
@ -23,7 +23,7 @@ plot.setWindowTitle('pyqtgraph example: text')
|
|||||||
curve = plot.plot(x,y) ## add a single curve
|
curve = plot.plot(x,y) ## add a single curve
|
||||||
|
|
||||||
## Create text object, use HTML tags to specify color/size
|
## Create text object, use HTML tags to specify color/size
|
||||||
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,1.3), border='w', fill=(0, 0, 255, 100))
|
text = pg.TextItem(html='<div style="text-align: center"><span style="color: #FFF;">This is the</span><br><span style="color: #FF0; font-size: 16pt;">PEAK</span></div>', anchor=(-0.3,0.5), angle=45, border='w', fill=(0, 0, 255, 100))
|
||||||
plot.addItem(text)
|
plot.addItem(text)
|
||||||
text.setPos(0, y.max())
|
text.setPos(0, y.max())
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ examples = OrderedDict([
|
|||||||
('Console', 'ConsoleWidget.py'),
|
('Console', 'ConsoleWidget.py'),
|
||||||
('Histograms', 'histogram.py'),
|
('Histograms', 'histogram.py'),
|
||||||
('Beeswarm plot', 'beeswarm.py'),
|
('Beeswarm plot', 'beeswarm.py'),
|
||||||
|
('Symbols', 'Symbols.py'),
|
||||||
('Auto-range', 'PlotAutoRange.py'),
|
('Auto-range', 'PlotAutoRange.py'),
|
||||||
('Remote Plotting', 'RemoteSpeedTest.py'),
|
('Remote Plotting', 'RemoteSpeedTest.py'),
|
||||||
('Scrolling plots', 'scrollingPlots.py'),
|
('Scrolling plots', 'scrollingPlots.py'),
|
||||||
|
@ -98,6 +98,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||||||
self.lastDrag = None
|
self.lastDrag = None
|
||||||
self.hoverItems = weakref.WeakKeyDictionary()
|
self.hoverItems = weakref.WeakKeyDictionary()
|
||||||
self.lastHoverEvent = None
|
self.lastHoverEvent = None
|
||||||
|
self.minDragTime = 0.5 # drags shorter than 0.5 sec are interpreted as clicks
|
||||||
|
|
||||||
self.contextMenu = [QtGui.QAction("Export...", self)]
|
self.contextMenu = [QtGui.QAction("Export...", self)]
|
||||||
self.contextMenu[0].triggered.connect(self.showExportDialog)
|
self.contextMenu[0].triggered.connect(self.showExportDialog)
|
||||||
@ -173,7 +174,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||||||
if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet
|
if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet
|
||||||
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0]
|
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0]
|
||||||
dist = Point(ev.screenPos() - cev.screenPos())
|
dist = Point(ev.screenPos() - cev.screenPos())
|
||||||
if dist.length() < self._moveDistance and now - cev.time() < 0.5:
|
if dist.length() < self._moveDistance and now - cev.time() < self.minDragTime:
|
||||||
continue
|
continue
|
||||||
init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True
|
init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True
|
||||||
self.dragButtons.append(int(btn))
|
self.dragButtons.append(int(btn))
|
||||||
@ -187,9 +188,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
|||||||
if len(self.dragButtons) == 0:
|
if len(self.dragButtons) == 0:
|
||||||
self.sendHoverEvents(ev, exitOnly=True)
|
self.sendHoverEvents(ev, exitOnly=True)
|
||||||
|
|
||||||
|
|
||||||
def mouseReleaseEvent(self, ev):
|
def mouseReleaseEvent(self, ev):
|
||||||
#print 'sceneRelease'
|
|
||||||
if self.mouseGrabberItem() is None:
|
if self.mouseGrabberItem() is None:
|
||||||
if ev.button() in self.dragButtons:
|
if ev.button() in self.dragButtons:
|
||||||
if self.sendDragEvent(ev, final=True):
|
if self.sendDragEvent(ev, final=True):
|
||||||
|
@ -9,7 +9,7 @@ This module exists to smooth out some of the differences between PySide and PyQt
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys, re
|
import sys, re, time
|
||||||
|
|
||||||
from .python2_3 import asUnicode
|
from .python2_3 import asUnicode
|
||||||
|
|
||||||
@ -45,6 +45,15 @@ if QT_LIB == PYSIDE:
|
|||||||
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
|
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
|
||||||
try:
|
try:
|
||||||
from PySide import QtTest
|
from PySide import QtTest
|
||||||
|
if not hasattr(QtTest.QTest, 'qWait'):
|
||||||
|
@staticmethod
|
||||||
|
def qWait(msec):
|
||||||
|
start = time.time()
|
||||||
|
QtGui.QApplication.processEvents()
|
||||||
|
while time.time() < start + msec * 0.001:
|
||||||
|
QtGui.QApplication.processEvents()
|
||||||
|
QtTest.QTest.qWait = qWait
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
import PySide
|
import PySide
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
from ..Qt import QtGui, QtCore
|
from ..Qt import QtGui, QtCore
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
|
from .TextItem import TextItem
|
||||||
|
from .ViewBox import ViewBox
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['InfiniteLine']
|
__all__ = ['InfiniteLine', 'InfLineLabel']
|
||||||
|
|
||||||
|
|
||||||
class InfiniteLine(GraphicsObject):
|
class InfiniteLine(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||||
@ -26,7 +30,8 @@ class InfiniteLine(GraphicsObject):
|
|||||||
sigPositionChangeFinished = QtCore.Signal(object)
|
sigPositionChangeFinished = QtCore.Signal(object)
|
||||||
sigPositionChanged = QtCore.Signal(object)
|
sigPositionChanged = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
|
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None,
|
||||||
|
hoverPen=None, label=None, labelOpts=None, name=None):
|
||||||
"""
|
"""
|
||||||
=============== ==================================================================
|
=============== ==================================================================
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
@ -37,10 +42,23 @@ class InfiniteLine(GraphicsObject):
|
|||||||
for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
|
for :func:`mkPen <pyqtgraph.mkPen>`. Default pen is transparent
|
||||||
yellow.
|
yellow.
|
||||||
movable If True, the line can be dragged to a new position by the user.
|
movable If True, the line can be dragged to a new position by the user.
|
||||||
|
hoverPen Pen to use when drawing line when hovering over it. Can be any
|
||||||
|
arguments that are valid for :func:`mkPen <pyqtgraph.mkPen>`.
|
||||||
|
Default pen is red.
|
||||||
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
bounds Optional [min, max] bounding values. Bounds are only valid if the
|
||||||
line is vertical or horizontal.
|
line is vertical or horizontal.
|
||||||
|
label 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.
|
||||||
|
labelOpts A dict of keyword arguments to use when constructing the
|
||||||
|
text label. See :class:`InfLineLabel`.
|
||||||
|
name Name of the item
|
||||||
=============== ==================================================================
|
=============== ==================================================================
|
||||||
"""
|
"""
|
||||||
|
self._boundingRect = None
|
||||||
|
self._line = None
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
|
|
||||||
@ -53,17 +71,24 @@ class InfiniteLine(GraphicsObject):
|
|||||||
self.mouseHovering = False
|
self.mouseHovering = False
|
||||||
self.p = [0, 0]
|
self.p = [0, 0]
|
||||||
self.setAngle(angle)
|
self.setAngle(angle)
|
||||||
|
|
||||||
if pos is None:
|
if pos is None:
|
||||||
pos = Point(0,0)
|
pos = Point(0,0)
|
||||||
self.setPos(pos)
|
self.setPos(pos)
|
||||||
|
|
||||||
if pen is None:
|
if pen is None:
|
||||||
pen = (200, 200, 100)
|
pen = (200, 200, 100)
|
||||||
|
|
||||||
self.setPen(pen)
|
self.setPen(pen)
|
||||||
|
if hoverPen is None:
|
||||||
self.setHoverPen(color=(255,0,0), width=self.pen.width())
|
self.setHoverPen(color=(255,0,0), width=self.pen.width())
|
||||||
|
else:
|
||||||
|
self.setHoverPen(hoverPen)
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
|
|
||||||
|
if label is not None:
|
||||||
|
labelOpts = {} if labelOpts is None else labelOpts
|
||||||
|
self.label = InfLineLabel(self, text=label, **labelOpts)
|
||||||
|
|
||||||
def setMovable(self, m):
|
def setMovable(self, m):
|
||||||
"""Set whether the line is movable by the user."""
|
"""Set whether the line is movable by the user."""
|
||||||
self.movable = m
|
self.movable = m
|
||||||
@ -136,8 +161,8 @@ class InfiniteLine(GraphicsObject):
|
|||||||
|
|
||||||
if self.p != newPos:
|
if self.p != newPos:
|
||||||
self.p = newPos
|
self.p = newPos
|
||||||
|
self._invalidateCache()
|
||||||
GraphicsObject.setPos(self, Point(self.p))
|
GraphicsObject.setPos(self, Point(self.p))
|
||||||
self.update()
|
|
||||||
self.sigPositionChanged.emit(self)
|
self.sigPositionChanged.emit(self)
|
||||||
|
|
||||||
def getXPos(self):
|
def getXPos(self):
|
||||||
@ -175,23 +200,33 @@ class InfiniteLine(GraphicsObject):
|
|||||||
#print "ignore", change
|
#print "ignore", change
|
||||||
#return GraphicsObject.itemChange(self, change, val)
|
#return GraphicsObject.itemChange(self, change, val)
|
||||||
|
|
||||||
|
def _invalidateCache(self):
|
||||||
|
self._line = None
|
||||||
|
self._boundingRect = None
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
|
if self._boundingRect is None:
|
||||||
#br = UIGraphicsItem.boundingRect(self)
|
#br = UIGraphicsItem.boundingRect(self)
|
||||||
br = self.viewRect()
|
br = self.viewRect()
|
||||||
## add a 4-pixel radius around the line for mouse interaction.
|
if br is None:
|
||||||
|
return QtCore.QRectF()
|
||||||
|
|
||||||
|
## add a 4-pixel radius around the line for mouse interaction.
|
||||||
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
|
||||||
if px is None:
|
if px is None:
|
||||||
px = 0
|
px = 0
|
||||||
w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
|
w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
|
||||||
br.setBottom(-w)
|
br.setBottom(-w)
|
||||||
br.setTop(w)
|
br.setTop(w)
|
||||||
return br.normalized()
|
|
||||||
|
br = br.normalized()
|
||||||
|
self._boundingRect = br
|
||||||
|
self._line = QtCore.QLineF(br.right(), 0.0, br.left(), 0.0)
|
||||||
|
return self._boundingRect
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
br = self.boundingRect()
|
|
||||||
p.setPen(self.currentPen)
|
p.setPen(self.currentPen)
|
||||||
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
p.drawLine(self._line)
|
||||||
|
|
||||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||||
if axis == 0:
|
if axis == 0:
|
||||||
@ -240,3 +275,195 @@ class InfiniteLine(GraphicsObject):
|
|||||||
else:
|
else:
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
def viewTransformChanged(self):
|
||||||
|
"""
|
||||||
|
Called whenever the transformation matrix of the view has changed.
|
||||||
|
(eg, the view range has changed or the view was resized)
|
||||||
|
"""
|
||||||
|
self._invalidateCache()
|
||||||
|
|
||||||
|
def setName(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
|
||||||
|
class InfLineLabel(TextItem):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
* Optionally aligns to its parent line.
|
||||||
|
|
||||||
|
=============== ==================================================================
|
||||||
|
**Arguments:**
|
||||||
|
line The InfiniteLine to which this label will be attached.
|
||||||
|
text String to display in the label. May contain a {value} formatting
|
||||||
|
string to display the current value of the line.
|
||||||
|
movable Bool; if True, then the label can be dragged along the line.
|
||||||
|
position Relative position (0.0-1.0) within the view to position the label
|
||||||
|
along the line.
|
||||||
|
anchors List of (x,y) pairs giving the text anchor positions that should
|
||||||
|
be used when the line is moved to one side of the view or the
|
||||||
|
other. This allows text to switch to the opposite side of the line
|
||||||
|
as it approaches the edge of the view. These are automatically
|
||||||
|
selected for some common cases, but may be specified if the
|
||||||
|
default values give unexpected results.
|
||||||
|
=============== ==================================================================
|
||||||
|
|
||||||
|
All extra keyword arguments are passed to TextItem. A particularly useful
|
||||||
|
option here is to use `rotateAxis=(1, 0)`, which will cause the text to
|
||||||
|
be automatically rotated parallel to the line.
|
||||||
|
"""
|
||||||
|
def __init__(self, line, text="", movable=False, position=0.5, anchors=None, **kwds):
|
||||||
|
self.line = line
|
||||||
|
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)
|
||||||
|
self._endpoints = (None, None)
|
||||||
|
if anchors is None:
|
||||||
|
# automatically pick sensible anchors
|
||||||
|
rax = kwds.get('rotateAxis', None)
|
||||||
|
if rax is not None:
|
||||||
|
if tuple(rax) == (1,0):
|
||||||
|
anchors = [(0.5, 0), (0.5, 1)]
|
||||||
|
else:
|
||||||
|
anchors = [(0, 0.5), (1, 0.5)]
|
||||||
|
else:
|
||||||
|
if line.angle % 180 == 0:
|
||||||
|
anchors = [(0.5, 0), (0.5, 1)]
|
||||||
|
else:
|
||||||
|
anchors = [(0, 0.5), (1, 0.5)]
|
||||||
|
|
||||||
|
self.anchors = anchors
|
||||||
|
TextItem.__init__(self, **kwds)
|
||||||
|
self.setParentItem(line)
|
||||||
|
self.valueChanged()
|
||||||
|
|
||||||
|
def valueChanged(self):
|
||||||
|
if not self.isVisible():
|
||||||
|
return
|
||||||
|
value = self.line.value()
|
||||||
|
self.setText(self.format.format(value=value))
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def getEndpoints(self):
|
||||||
|
# calculate points where line intersects view box
|
||||||
|
# (in line coordinates)
|
||||||
|
if self._endpoints[0] is None:
|
||||||
|
lr = self.line.boundingRect()
|
||||||
|
pt1 = Point(lr.left(), 0)
|
||||||
|
pt2 = Point(lr.right(), 0)
|
||||||
|
|
||||||
|
if self.line.angle % 90 != 0:
|
||||||
|
# more expensive to find text position for oblique lines.
|
||||||
|
view = self.getViewBox()
|
||||||
|
if not self.isVisible() or not isinstance(view, ViewBox):
|
||||||
|
# not in a viewbox, skip update
|
||||||
|
return (None, None)
|
||||||
|
p = QtGui.QPainterPath()
|
||||||
|
p.moveTo(pt1)
|
||||||
|
p.lineTo(pt2)
|
||||||
|
p = self.line.itemTransform(view)[0].map(p)
|
||||||
|
vr = QtGui.QPainterPath()
|
||||||
|
vr.addRect(view.boundingRect())
|
||||||
|
paths = vr.intersected(p).toSubpathPolygons(QtGui.QTransform())
|
||||||
|
if len(paths) > 0:
|
||||||
|
l = list(paths[0])
|
||||||
|
pt1 = self.line.mapFromItem(view, l[0])
|
||||||
|
pt2 = self.line.mapFromItem(view, l[1])
|
||||||
|
self._endpoints = (pt1, pt2)
|
||||||
|
return self._endpoints
|
||||||
|
|
||||||
|
def updatePosition(self):
|
||||||
|
# update text position to relative view location along line
|
||||||
|
self._endpoints = (None, None)
|
||||||
|
pt1, pt2 = self.getEndpoints()
|
||||||
|
if pt1 is None:
|
||||||
|
return
|
||||||
|
pt = pt2 * self.orthoPos + pt1 * (1-self.orthoPos)
|
||||||
|
self.setPos(pt)
|
||||||
|
|
||||||
|
# update anchor to keep text visible as it nears the view box edge
|
||||||
|
vr = self.line.viewRect()
|
||||||
|
if vr is not None:
|
||||||
|
self.setAnchor(self.anchors[0 if vr.center().y() < 0 else 1])
|
||||||
|
|
||||||
|
def setVisible(self, v):
|
||||||
|
TextItem.setVisible(self, v)
|
||||||
|
if v:
|
||||||
|
self.updateText()
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def setMovable(self, m):
|
||||||
|
"""Set whether this label is movable by dragging along the line.
|
||||||
|
"""
|
||||||
|
self.movable = m
|
||||||
|
self.setAcceptHoverEvents(m)
|
||||||
|
|
||||||
|
def setPosition(self, p):
|
||||||
|
"""Set the relative position (0.0-1.0) of this label within the view box
|
||||||
|
and along the line.
|
||||||
|
|
||||||
|
For horizontal (angle=0) and vertical (angle=90) lines, a value of 0.0
|
||||||
|
places the text at the bottom or left of the view, respectively.
|
||||||
|
"""
|
||||||
|
self.orthoPos = p
|
||||||
|
self.updatePosition()
|
||||||
|
|
||||||
|
def setFormat(self, text):
|
||||||
|
"""Set the text format string for this label.
|
||||||
|
|
||||||
|
May optionally contain "{value}" to include the lines current value
|
||||||
|
(the text will be reformatted whenever the line is moved).
|
||||||
|
"""
|
||||||
|
self.format = format
|
||||||
|
self.valueChanged()
|
||||||
|
|
||||||
|
def mouseDragEvent(self, ev):
|
||||||
|
if self.movable and ev.button() == QtCore.Qt.LeftButton:
|
||||||
|
if ev.isStart():
|
||||||
|
self._moving = True
|
||||||
|
self._cursorOffset = self._posToRel(ev.buttonDownPos())
|
||||||
|
self._startPosition = self.orthoPos
|
||||||
|
ev.accept()
|
||||||
|
|
||||||
|
if not self._moving:
|
||||||
|
return
|
||||||
|
|
||||||
|
rel = self._posToRel(ev.pos())
|
||||||
|
self.orthoPos = np.clip(self._startPosition + rel - self._cursorOffset, 0, 1)
|
||||||
|
self.updatePosition()
|
||||||
|
if ev.isFinish():
|
||||||
|
self._moving = False
|
||||||
|
|
||||||
|
def mouseClickEvent(self, ev):
|
||||||
|
if self.moving and ev.button() == QtCore.Qt.RightButton:
|
||||||
|
ev.accept()
|
||||||
|
self.orthoPos = self._startPosition
|
||||||
|
self.moving = False
|
||||||
|
|
||||||
|
def hoverEvent(self, ev):
|
||||||
|
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
|
||||||
|
pt1, pt2 = self.getEndpoints()
|
||||||
|
if pt1 is None:
|
||||||
|
return 0
|
||||||
|
view = self.getViewBox()
|
||||||
|
pos = self.mapToParent(pos)
|
||||||
|
return (pos.x() - pt1.x()) / (pt2.x()-pt1.x())
|
||||||
|
@ -19,17 +19,28 @@ __all__ = ['ScatterPlotItem', 'SpotItem']
|
|||||||
|
|
||||||
|
|
||||||
## Build all symbol paths
|
## Build all symbol paths
|
||||||
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']])
|
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 't1', 't2', 't3','d', '+', 'x', 'p', 'h', 'star']])
|
||||||
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))
|
||||||
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)],
|
||||||
|
't2': [(-0.5, -0.5), (-0.5, 0.5), (0.5, 0)],
|
||||||
|
't3': [(0.5, 0.5), (0.5, -0.5), (-0.5, 0)],
|
||||||
'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)],
|
'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)],
|
||||||
'+': [
|
'+': [
|
||||||
(-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5),
|
(-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5),
|
||||||
(0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05),
|
(0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05),
|
||||||
(0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05)
|
(0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05)
|
||||||
],
|
],
|
||||||
|
'p': [(0, -0.5), (-0.4755, -0.1545), (-0.2939, 0.4045),
|
||||||
|
(0.2939, 0.4045), (0.4755, -0.1545)],
|
||||||
|
'h': [(0.433, 0.25), (0., 0.5), (-0.433, 0.25), (-0.433, -0.25),
|
||||||
|
(0, -0.5), (0.433, -0.25)],
|
||||||
|
'star': [(0, -0.5), (-0.1123, -0.1545), (-0.4755, -0.1545),
|
||||||
|
(-0.1816, 0.059), (-0.2939, 0.4045), (0, 0.1910),
|
||||||
|
(0.2939, 0.4045), (0.1816, 0.059), (0.4755, -0.1545),
|
||||||
|
(0.1123, -0.1545)]
|
||||||
}
|
}
|
||||||
for k, c in coords.items():
|
for k, c in coords.items():
|
||||||
Symbols[k].moveTo(*c[0])
|
Symbols[k].moveTo(*c[0])
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
|
import numpy as np
|
||||||
from ..Qt import QtCore, QtGui
|
from ..Qt import QtCore, QtGui
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from .UIGraphicsItem import *
|
|
||||||
from .. import functions as fn
|
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).
|
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:**
|
**Arguments:**
|
||||||
@ -20,19 +23,31 @@ class TextItem(UIGraphicsItem):
|
|||||||
sets the lower-right corner.
|
sets the lower-right corner.
|
||||||
*border* A pen to use when drawing the border
|
*border* A pen to use when drawing the border
|
||||||
*fill* A brush to use when filling within 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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
## 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.anchor = Point(anchor)
|
||||||
|
self.rotateAxis = None if rotateAxis is None else Point(rotateAxis)
|
||||||
#self.angle = 0
|
#self.angle = 0
|
||||||
UIGraphicsItem.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
self.textItem = QtGui.QGraphicsTextItem()
|
self.textItem = QtGui.QGraphicsTextItem()
|
||||||
self.textItem.setParentItem(self)
|
self.textItem.setParentItem(self)
|
||||||
self.lastTransform = None
|
self._lastTransform = None
|
||||||
self._bounds = QtCore.QRectF()
|
self._bounds = QtCore.QRectF()
|
||||||
if html is None:
|
if html is None:
|
||||||
self.setText(text, color)
|
self.setText(text, color)
|
||||||
@ -40,8 +55,7 @@ class TextItem(UIGraphicsItem):
|
|||||||
self.setHtml(html)
|
self.setHtml(html)
|
||||||
self.fill = fn.mkBrush(fill)
|
self.fill = fn.mkBrush(fill)
|
||||||
self.border = fn.mkPen(border)
|
self.border = fn.mkPen(border)
|
||||||
self.rotate(angle)
|
self.setAngle(angle)
|
||||||
self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport
|
|
||||||
|
|
||||||
def setText(self, text, color=(200,200,200)):
|
def setText(self, text, color=(200,200,200)):
|
||||||
"""
|
"""
|
||||||
@ -52,14 +66,7 @@ class TextItem(UIGraphicsItem):
|
|||||||
color = fn.mkColor(color)
|
color = fn.mkColor(color)
|
||||||
self.textItem.setDefaultTextColor(color)
|
self.textItem.setDefaultTextColor(color)
|
||||||
self.textItem.setPlainText(text)
|
self.textItem.setPlainText(text)
|
||||||
self.updateText()
|
self.updateTextPos()
|
||||||
#html = '<span style="color: #%s; text-align: center;">%s</span>' % (color, text)
|
|
||||||
#self.setHtml(html)
|
|
||||||
|
|
||||||
def updateAnchor(self):
|
|
||||||
pass
|
|
||||||
#self.resetTransform()
|
|
||||||
#self.translate(0, 20)
|
|
||||||
|
|
||||||
def setPlainText(self, *args):
|
def setPlainText(self, *args):
|
||||||
"""
|
"""
|
||||||
@ -68,7 +75,7 @@ class TextItem(UIGraphicsItem):
|
|||||||
See QtGui.QGraphicsTextItem.setPlainText().
|
See QtGui.QGraphicsTextItem.setPlainText().
|
||||||
"""
|
"""
|
||||||
self.textItem.setPlainText(*args)
|
self.textItem.setPlainText(*args)
|
||||||
self.updateText()
|
self.updateTextPos()
|
||||||
|
|
||||||
def setHtml(self, *args):
|
def setHtml(self, *args):
|
||||||
"""
|
"""
|
||||||
@ -77,7 +84,7 @@ class TextItem(UIGraphicsItem):
|
|||||||
See QtGui.QGraphicsTextItem.setHtml().
|
See QtGui.QGraphicsTextItem.setHtml().
|
||||||
"""
|
"""
|
||||||
self.textItem.setHtml(*args)
|
self.textItem.setHtml(*args)
|
||||||
self.updateText()
|
self.updateTextPos()
|
||||||
|
|
||||||
def setTextWidth(self, *args):
|
def setTextWidth(self, *args):
|
||||||
"""
|
"""
|
||||||
@ -89,7 +96,7 @@ class TextItem(UIGraphicsItem):
|
|||||||
See QtGui.QGraphicsTextItem.setTextWidth().
|
See QtGui.QGraphicsTextItem.setTextWidth().
|
||||||
"""
|
"""
|
||||||
self.textItem.setTextWidth(*args)
|
self.textItem.setTextWidth(*args)
|
||||||
self.updateText()
|
self.updateTextPos()
|
||||||
|
|
||||||
def setFont(self, *args):
|
def setFont(self, *args):
|
||||||
"""
|
"""
|
||||||
@ -98,50 +105,43 @@ class TextItem(UIGraphicsItem):
|
|||||||
See QtGui.QGraphicsTextItem.setFont().
|
See QtGui.QGraphicsTextItem.setFont().
|
||||||
"""
|
"""
|
||||||
self.textItem.setFont(*args)
|
self.textItem.setFont(*args)
|
||||||
self.updateText()
|
self.updateTextPos()
|
||||||
|
|
||||||
#def setAngle(self, angle):
|
def setAngle(self, angle):
|
||||||
#self.angle = angle
|
self.angle = angle
|
||||||
#self.updateText()
|
self.updateTransform()
|
||||||
|
|
||||||
|
def setAnchor(self, anchor):
|
||||||
|
self.anchor = Point(anchor)
|
||||||
|
self.updateTextPos()
|
||||||
|
|
||||||
def updateText(self):
|
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)
|
||||||
|
|
||||||
## Needed to maintain font size when rendering to image with increased resolution
|
### Needed to maintain font size when rendering to image with increased resolution
|
||||||
self.textItem.resetTransform()
|
#self.textItem.resetTransform()
|
||||||
#self.textItem.rotate(self.angle)
|
##self.textItem.rotate(self.angle)
|
||||||
if self._exportOpts is not False and 'resolutionScale' in self._exportOpts:
|
#if self._exportOpts is not False and 'resolutionScale' in self._exportOpts:
|
||||||
s = self._exportOpts['resolutionScale']
|
#s = self._exportOpts['resolutionScale']
|
||||||
self.textItem.scale(s, s)
|
#self.textItem.scale(s, s)
|
||||||
|
|
||||||
#br = self.textItem.mapRectToParent(self.textItem.boundingRect())
|
|
||||||
self.textItem.setPos(0,0)
|
|
||||||
br = self.textItem.boundingRect()
|
|
||||||
apos = self.textItem.mapToParent(Point(br.width()*self.anchor.x(), br.height()*self.anchor.y()))
|
|
||||||
#print br, apos
|
|
||||||
self.textItem.setPos(-apos.x(), -apos.y())
|
|
||||||
|
|
||||||
#def textBoundingRect(self):
|
|
||||||
### return the bounds of the text box in device coordinates
|
|
||||||
#pos = self.mapToDevice(QtCore.QPointF(0,0))
|
|
||||||
#if pos is None:
|
|
||||||
#return None
|
|
||||||
#tbr = self.textItem.boundingRect()
|
|
||||||
#return QtCore.QRectF(pos.x() - tbr.width()*self.anchor.x(), pos.y() - tbr.height()*self.anchor.y(), tbr.width(), tbr.height())
|
|
||||||
|
|
||||||
|
|
||||||
def viewRangeChanged(self):
|
|
||||||
self.updateText()
|
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect()
|
return self.textItem.mapToParent(self.textItem.boundingRect()).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):
|
def paint(self, p, *args):
|
||||||
tr = p.transform()
|
# this is not ideal because it causes another update to be scheduled.
|
||||||
if self.lastTransform is not None:
|
# ideally, we would have a sceneTransformChanged event to react to..
|
||||||
if tr != self.lastTransform:
|
self.updateTransform()
|
||||||
self.viewRangeChanged()
|
|
||||||
self.lastTransform = tr
|
|
||||||
|
|
||||||
if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush:
|
if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush:
|
||||||
p.setPen(self.border)
|
p.setPen(self.border)
|
||||||
@ -149,4 +149,37 @@ class TextItem(UIGraphicsItem):
|
|||||||
p.setRenderHint(p.Antialiasing, True)
|
p.setRenderHint(p.Antialiasing, True)
|
||||||
p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect()))
|
p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect()))
|
||||||
|
|
||||||
|
def updateTransform(self):
|
||||||
|
# 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 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 = np.arctan2(d.y(), d.x()) * 180 / np.pi
|
||||||
|
angle += a
|
||||||
|
t.rotate(angle)
|
||||||
|
|
||||||
|
self.setTransform(t)
|
||||||
|
|
||||||
|
self._lastTransform = pt
|
||||||
|
|
||||||
|
self.updateTextPos()
|
||||||
|
|
||||||
|
|
96
pyqtgraph/graphicsItems/tests/test_InfiniteLine.py
Normal file
96
pyqtgraph/graphicsItems/tests/test_InfiniteLine.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtGui, QtCore, QtTest
|
||||||
|
from pyqtgraph.tests import mouseDrag, mouseMove
|
||||||
|
pg.mkQApp()
|
||||||
|
|
||||||
|
|
||||||
|
def test_InfiniteLine():
|
||||||
|
# Test basic InfiniteLine API
|
||||||
|
plt = pg.plot()
|
||||||
|
plt.setXRange(-10, 10)
|
||||||
|
plt.setYRange(-10, 10)
|
||||||
|
plt.resize(600, 600)
|
||||||
|
|
||||||
|
# seemingly arbitrary requirements; might need longer wait time for some platforms..
|
||||||
|
QtTest.QTest.qWaitForWindowShown(plt)
|
||||||
|
QtTest.QTest.qWait(100)
|
||||||
|
|
||||||
|
vline = plt.addLine(x=1)
|
||||||
|
assert vline.angle == 90
|
||||||
|
br = vline.mapToView(QtGui.QPolygonF(vline.boundingRect()))
|
||||||
|
assert br.containsPoint(pg.Point(1, 5), QtCore.Qt.OddEvenFill)
|
||||||
|
assert not br.containsPoint(pg.Point(5, 0), QtCore.Qt.OddEvenFill)
|
||||||
|
hline = plt.addLine(y=0)
|
||||||
|
assert hline.angle == 0
|
||||||
|
assert hline.boundingRect().contains(pg.Point(5, 0))
|
||||||
|
assert not hline.boundingRect().contains(pg.Point(0, 5))
|
||||||
|
|
||||||
|
vline.setValue(2)
|
||||||
|
assert vline.value() == 2
|
||||||
|
vline.setPos(pg.Point(4, -5))
|
||||||
|
assert vline.value() == 4
|
||||||
|
|
||||||
|
oline = pg.InfiniteLine(angle=30)
|
||||||
|
plt.addItem(oline)
|
||||||
|
oline.setPos(pg.Point(1, -1))
|
||||||
|
assert oline.angle == 30
|
||||||
|
assert oline.pos() == pg.Point(1, -1)
|
||||||
|
assert oline.value() == [1, -1]
|
||||||
|
|
||||||
|
# test bounding rect for oblique line
|
||||||
|
br = oline.mapToScene(oline.boundingRect())
|
||||||
|
pos = oline.mapToScene(pg.Point(2, 0))
|
||||||
|
assert br.containsPoint(pos, QtCore.Qt.OddEvenFill)
|
||||||
|
px = pg.Point(-0.5, -1.0 / 3**0.5)
|
||||||
|
assert br.containsPoint(pos + 5 * px, QtCore.Qt.OddEvenFill)
|
||||||
|
assert not br.containsPoint(pos + 7 * px, QtCore.Qt.OddEvenFill)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mouseInteraction():
|
||||||
|
plt = pg.plot()
|
||||||
|
plt.scene().minDragTime = 0 # let us simulate mouse drags very quickly.
|
||||||
|
vline = plt.addLine(x=0, movable=True)
|
||||||
|
plt.addItem(vline)
|
||||||
|
hline = plt.addLine(y=0, movable=True)
|
||||||
|
hline2 = plt.addLine(y=-1, movable=False)
|
||||||
|
plt.setXRange(-10, 10)
|
||||||
|
plt.setYRange(-10, 10)
|
||||||
|
|
||||||
|
# test horizontal drag
|
||||||
|
pos = plt.plotItem.vb.mapViewToScene(pg.Point(0,5)).toPoint()
|
||||||
|
pos2 = pos - QtCore.QPoint(200, 200)
|
||||||
|
mouseMove(plt, pos)
|
||||||
|
assert vline.mouseHovering is True and hline.mouseHovering is False
|
||||||
|
mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton)
|
||||||
|
px = vline.pixelLength(pg.Point(1, 0), ortho=True)
|
||||||
|
assert abs(vline.value() - plt.plotItem.vb.mapSceneToView(pos2).x()) <= px
|
||||||
|
|
||||||
|
# test missed drag
|
||||||
|
pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)).toPoint()
|
||||||
|
pos = pos + QtCore.QPoint(0, 6)
|
||||||
|
pos2 = pos + QtCore.QPoint(-20, -20)
|
||||||
|
mouseMove(plt, pos)
|
||||||
|
assert vline.mouseHovering is False and hline.mouseHovering is False
|
||||||
|
mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton)
|
||||||
|
assert hline.value() == 0
|
||||||
|
|
||||||
|
# test vertical drag
|
||||||
|
pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)).toPoint()
|
||||||
|
pos2 = pos - QtCore.QPoint(50, 50)
|
||||||
|
mouseMove(plt, pos)
|
||||||
|
assert vline.mouseHovering is False and hline.mouseHovering is True
|
||||||
|
mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton)
|
||||||
|
px = hline.pixelLength(pg.Point(1, 0), ortho=True)
|
||||||
|
assert abs(hline.value() - plt.plotItem.vb.mapSceneToView(pos2).y()) <= px
|
||||||
|
|
||||||
|
# test non-interactive line
|
||||||
|
pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,-1)).toPoint()
|
||||||
|
pos2 = pos - QtCore.QPoint(50, 50)
|
||||||
|
mouseMove(plt, pos)
|
||||||
|
assert hline2.mouseHovering == False
|
||||||
|
mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton)
|
||||||
|
assert hline2.value() == -1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test_mouseInteraction()
|
@ -1 +1,2 @@
|
|||||||
from .image_testing import assertImageApproved
|
from .image_testing import assertImageApproved
|
||||||
|
from .ui_testing import mousePress, mouseMove, mouseRelease, mouseDrag, mouseClick
|
||||||
|
55
pyqtgraph/tests/ui_testing.py
Normal file
55
pyqtgraph/tests/ui_testing.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
# Functions for generating user input events.
|
||||||
|
# We would like to use QTest for this purpose, but it seems to be broken.
|
||||||
|
# See: http://stackoverflow.com/questions/16299779/qt-qgraphicsview-unit-testing-how-to-keep-the-mouse-in-a-pressed-state
|
||||||
|
|
||||||
|
from ..Qt import QtCore, QtGui, QT_LIB
|
||||||
|
|
||||||
|
|
||||||
|
def mousePress(widget, pos, button, modifier=None):
|
||||||
|
if isinstance(widget, QtGui.QGraphicsView):
|
||||||
|
widget = widget.viewport()
|
||||||
|
if modifier is None:
|
||||||
|
modifier = QtCore.Qt.NoModifier
|
||||||
|
if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF):
|
||||||
|
pos = pos.toPoint()
|
||||||
|
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, pos, button, QtCore.Qt.NoButton, modifier)
|
||||||
|
QtGui.QApplication.sendEvent(widget, event)
|
||||||
|
|
||||||
|
|
||||||
|
def mouseRelease(widget, pos, button, modifier=None):
|
||||||
|
if isinstance(widget, QtGui.QGraphicsView):
|
||||||
|
widget = widget.viewport()
|
||||||
|
if modifier is None:
|
||||||
|
modifier = QtCore.Qt.NoModifier
|
||||||
|
if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF):
|
||||||
|
pos = pos.toPoint()
|
||||||
|
event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, pos, button, QtCore.Qt.NoButton, modifier)
|
||||||
|
QtGui.QApplication.sendEvent(widget, event)
|
||||||
|
|
||||||
|
|
||||||
|
def mouseMove(widget, pos, buttons=None, modifier=None):
|
||||||
|
if isinstance(widget, QtGui.QGraphicsView):
|
||||||
|
widget = widget.viewport()
|
||||||
|
if modifier is None:
|
||||||
|
modifier = QtCore.Qt.NoModifier
|
||||||
|
if buttons is None:
|
||||||
|
buttons = QtCore.Qt.NoButton
|
||||||
|
if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF):
|
||||||
|
pos = pos.toPoint()
|
||||||
|
event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, pos, QtCore.Qt.NoButton, buttons, modifier)
|
||||||
|
QtGui.QApplication.sendEvent(widget, event)
|
||||||
|
|
||||||
|
|
||||||
|
def mouseDrag(widget, pos1, pos2, button, modifier=None):
|
||||||
|
mouseMove(widget, pos1)
|
||||||
|
mousePress(widget, pos1, button, modifier)
|
||||||
|
mouseMove(widget, pos2, button, modifier)
|
||||||
|
mouseRelease(widget, pos2, button, modifier)
|
||||||
|
|
||||||
|
|
||||||
|
def mouseClick(widget, pos, button, modifier=None):
|
||||||
|
mouseMove(widget, pos)
|
||||||
|
mousePress(widget, pos, button, modifier)
|
||||||
|
mouseRelease(widget, pos, button, modifier)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user