Several minor bugfixes and features
- Added rate-limited mode to SignalProxy - Added basic text justification to LabelItem - ViewBox.addItem now has ignoreBounds option, which causes the item to be ignored when autoscaling - Added ValueLabel widget - Fixed some autoscaling bugs - InfiniteLine fix - no hilight if movable=False
This commit is contained in:
parent
bdef8dc4c7
commit
5a357ddb2a
@ -7,31 +7,35 @@ __all__ = ['SignalProxy']
|
|||||||
|
|
||||||
class SignalProxy(QtCore.QObject):
|
class SignalProxy(QtCore.QObject):
|
||||||
"""Object which collects rapid-fire signals and condenses them
|
"""Object which collects rapid-fire signals and condenses them
|
||||||
into a single signal. Used, for example, to prevent a SpinBox
|
into a single signal or a rate-limited stream of signals.
|
||||||
from generating multiple signals when the mouse wheel is rolled
|
Used, for example, to prevent a SpinBox from generating multiple
|
||||||
over it.
|
signals when the mouse wheel is rolled over it.
|
||||||
|
|
||||||
Emits sigDelayed after input signals have stopped for a certain period of time.
|
Emits sigDelayed after input signals have stopped for a certain period of time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sigDelayed = QtCore.Signal(object)
|
sigDelayed = QtCore.Signal(object)
|
||||||
|
|
||||||
def __init__(self, signal, delay=0.3, slot=None):
|
def __init__(self, signal, delay=0.3, rateLimit=0, slot=None):
|
||||||
"""Initialization arguments:
|
"""Initialization arguments:
|
||||||
signal - a bound Signal or pyqtSignal instance
|
signal - a bound Signal or pyqtSignal instance
|
||||||
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)
|
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)
|
||||||
slot - Optional function to connect sigDelayed to.
|
slot - Optional function to connect sigDelayed to.
|
||||||
|
rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a
|
||||||
|
steady rate while they are being received.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
QtCore.QObject.__init__(self)
|
QtCore.QObject.__init__(self)
|
||||||
signal.connect(self.signalReceived)
|
signal.connect(self.signalReceived)
|
||||||
self.signal = signal
|
self.signal = signal
|
||||||
self.delay = delay
|
self.delay = delay
|
||||||
|
self.rateLimit = rateLimit
|
||||||
self.args = None
|
self.args = None
|
||||||
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
||||||
self.timer.timeout.connect(self.flush)
|
self.timer.timeout.connect(self.flush)
|
||||||
self.block = False
|
self.block = False
|
||||||
self.slot = slot
|
self.slot = slot
|
||||||
|
self.lastFlushTime = None
|
||||||
if slot is not None:
|
if slot is not None:
|
||||||
self.sigDelayed.connect(slot)
|
self.sigDelayed.connect(slot)
|
||||||
|
|
||||||
@ -43,8 +47,20 @@ class SignalProxy(QtCore.QObject):
|
|||||||
if self.block:
|
if self.block:
|
||||||
return
|
return
|
||||||
self.args = args
|
self.args = args
|
||||||
self.timer.stop()
|
if self.rateLimit == 0:
|
||||||
self.timer.start((self.delay*1000)+1)
|
self.timer.stop()
|
||||||
|
self.timer.start((self.delay*1000)+1)
|
||||||
|
else:
|
||||||
|
now = time()
|
||||||
|
if self.lastFlushTime is None:
|
||||||
|
leakTime = 0
|
||||||
|
else:
|
||||||
|
lastFlush = self.lastFlushTime
|
||||||
|
leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now)
|
||||||
|
|
||||||
|
self.timer.stop()
|
||||||
|
self.timer.start((min(leakTime, self.delay)*1000)+1)
|
||||||
|
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""If there is a signal queued up, send it now."""
|
"""If there is a signal queued up, send it now."""
|
||||||
@ -54,6 +70,7 @@ class SignalProxy(QtCore.QObject):
|
|||||||
self.sigDelayed.emit(self.args)
|
self.sigDelayed.emit(self.args)
|
||||||
self.args = None
|
self.args = None
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
|
self.lastFlushTime = time()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
|
13
Transform.py
13
Transform.py
@ -5,8 +5,7 @@ import numpy as np
|
|||||||
|
|
||||||
class Transform(QtGui.QTransform):
|
class Transform(QtGui.QTransform):
|
||||||
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
|
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
|
||||||
|
This transform has no shear; angles are always preserved.
|
||||||
This transform always has 0 shear.
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, init=None):
|
def __init__(self, init=None):
|
||||||
QtGui.QTransform.__init__(self)
|
QtGui.QTransform.__init__(self)
|
||||||
@ -24,6 +23,16 @@ class Transform(QtGui.QTransform):
|
|||||||
elif isinstance(init, QtGui.QTransform):
|
elif isinstance(init, QtGui.QTransform):
|
||||||
self.setFromQTransform(init)
|
self.setFromQTransform(init)
|
||||||
|
|
||||||
|
|
||||||
|
def getScale(self):
|
||||||
|
return self._state['scale']
|
||||||
|
|
||||||
|
def getAngle(self):
|
||||||
|
return self._state['angle']
|
||||||
|
|
||||||
|
def getTranslation(self):
|
||||||
|
return self._state['pos']
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self._state = {
|
self._state = {
|
||||||
'pos': Point(0,0),
|
'pos': Point(0,0),
|
||||||
|
@ -72,7 +72,6 @@ def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, al
|
|||||||
Return the number x formatted in engineering notation with SI prefix.
|
Return the number x formatted in engineering notation with SI prefix.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
siFormat(0.0001, suffix='V') # returns "100 μV"
|
siFormat(0.0001, suffix='V') # returns "100 μV"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -90,8 +89,11 @@ def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, al
|
|||||||
fmt = "%." + str(precision) + "g%s%s"
|
fmt = "%." + str(precision) + "g%s%s"
|
||||||
return fmt % (x*p, pref, suffix)
|
return fmt % (x*p, pref, suffix)
|
||||||
else:
|
else:
|
||||||
plusminus = space + u"±" + space
|
if allowUnicode:
|
||||||
fmt = "%." + str(precision) + u"g%s%s%s%s"
|
plusminus = space + u"±" + space
|
||||||
|
else:
|
||||||
|
plusminus = " +/- "
|
||||||
|
fmt = "%." + str(precision) + "g%s%s%s%s"
|
||||||
return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal))
|
return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal))
|
||||||
|
|
||||||
def siEval(s):
|
def siEval(s):
|
||||||
|
@ -45,7 +45,7 @@ class GraphicsLayout(GraphicsWidget):
|
|||||||
self.addItem(vb, row, col, rowspan, colspan)
|
self.addItem(vb, row, col, rowspan, colspan)
|
||||||
return vb
|
return vb
|
||||||
|
|
||||||
def addLabel(self, text, row=None, col=None, rowspan=1, colspan=1, **kargs):
|
def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs):
|
||||||
text = LabelItem(text, **kargs)
|
text = LabelItem(text, **kargs)
|
||||||
self.addItem(text, row, col, rowspan, colspan)
|
self.addItem(text, row, col, rowspan, colspan)
|
||||||
return text
|
return text
|
||||||
|
@ -221,7 +221,7 @@ class InfiniteLine(UIGraphicsItem):
|
|||||||
self.sigPositionChangeFinished.emit(self)
|
self.sigPositionChangeFinished.emit(self)
|
||||||
|
|
||||||
def hoverEvent(self, ev):
|
def hoverEvent(self, ev):
|
||||||
if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
|
if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton):
|
||||||
self.currentPen = fn.mkPen(255, 0,0)
|
self.currentPen = fn.mkPen(255, 0,0)
|
||||||
else:
|
else:
|
||||||
self.currentPen = self.pen
|
self.currentPen = self.pen
|
||||||
|
@ -14,15 +14,14 @@ class LabelItem(GraphicsWidget):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, text, parent=None, angle=0, **args):
|
def __init__(self, text=' ', parent=None, angle=0, **args):
|
||||||
GraphicsWidget.__init__(self, parent)
|
GraphicsWidget.__init__(self, parent)
|
||||||
self.item = QtGui.QGraphicsTextItem(self)
|
self.item = QtGui.QGraphicsTextItem(self)
|
||||||
self.opts = args
|
self.opts = {
|
||||||
if 'color' not in args:
|
'color': 'CCC',
|
||||||
self.opts['color'] = 'CCC'
|
'justify': 'center'
|
||||||
else:
|
}
|
||||||
if isinstance(args['color'], QtGui.QColor):
|
self.opts.update(args)
|
||||||
self.opts['color'] = fn.colorStr(args['color'])[:6]
|
|
||||||
self.sizeHint = {}
|
self.sizeHint = {}
|
||||||
self.setText(text)
|
self.setText(text)
|
||||||
self.setAngle(angle)
|
self.setAngle(angle)
|
||||||
@ -47,6 +46,8 @@ class LabelItem(GraphicsWidget):
|
|||||||
|
|
||||||
optlist = []
|
optlist = []
|
||||||
if 'color' in opts:
|
if 'color' in opts:
|
||||||
|
if isinstance(opts['color'], QtGui.QColor):
|
||||||
|
opts['color'] = fn.colorStr(opts['color'])[:6]
|
||||||
optlist.append('color: #' + opts['color'])
|
optlist.append('color: #' + opts['color'])
|
||||||
if 'size' in opts:
|
if 'size' in opts:
|
||||||
optlist.append('font-size: ' + opts['size'])
|
optlist.append('font-size: ' + opts['size'])
|
||||||
@ -58,13 +59,25 @@ class LabelItem(GraphicsWidget):
|
|||||||
#print full
|
#print full
|
||||||
self.item.setHtml(full)
|
self.item.setHtml(full)
|
||||||
self.updateMin()
|
self.updateMin()
|
||||||
|
self.resizeEvent(None)
|
||||||
|
self.update()
|
||||||
|
|
||||||
def resizeEvent(self, ev):
|
def resizeEvent(self, ev):
|
||||||
c1 = self.boundingRect().center()
|
#c1 = self.boundingRect().center()
|
||||||
c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos()
|
#c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos()
|
||||||
dif = c1 - c2
|
#dif = c1 - c2
|
||||||
self.item.moveBy(dif.x(), dif.y())
|
#self.item.moveBy(dif.x(), dif.y())
|
||||||
#print c1, c2, dif, self.item.pos()
|
#print c1, c2, dif, self.item.pos()
|
||||||
|
if self.opts['justify'] == 'left':
|
||||||
|
self.item.setPos(0,0)
|
||||||
|
elif self.opts['justify'] == 'center':
|
||||||
|
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
||||||
|
self.item.setPos(self.width()/2. - bounds.width()/2., 0)
|
||||||
|
elif self.opts['justify'] == 'right':
|
||||||
|
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
||||||
|
self.item.setPos(self.width() - bounds.width(), 0)
|
||||||
|
#if self.width() > 0:
|
||||||
|
#self.item.setTextWidth(self.width())
|
||||||
|
|
||||||
def setAngle(self, angle):
|
def setAngle(self, angle):
|
||||||
self.angle = angle
|
self.angle = angle
|
||||||
@ -76,16 +89,23 @@ class LabelItem(GraphicsWidget):
|
|||||||
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
||||||
self.setMinimumWidth(bounds.width())
|
self.setMinimumWidth(bounds.width())
|
||||||
self.setMinimumHeight(bounds.height())
|
self.setMinimumHeight(bounds.height())
|
||||||
#print self.text, bounds.width(), bounds.height()
|
|
||||||
|
|
||||||
#self.sizeHint = {
|
self.sizeHint = {
|
||||||
#QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
||||||
#QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
||||||
#QtCore.Qt.MaximumSize: (bounds.width()*2, bounds.height()*2),
|
QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2),
|
||||||
#QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
||||||
#}
|
}
|
||||||
|
|
||||||
|
self.update()
|
||||||
|
|
||||||
#def sizeHint(self, hint, constraint):
|
def sizeHint(self, hint, constraint):
|
||||||
#return self.sizeHint[hint]
|
if hint not in self.sizeHint:
|
||||||
|
return QtCore.QSizeF(0, 0)
|
||||||
|
return QtCore.QSizeF(*self.sizeHint[hint])
|
||||||
|
|
||||||
|
#def paint(self, p, *args):
|
||||||
|
#p.setPen(fn.mkPen('r'))
|
||||||
|
#p.drawRect(self.rect())
|
||||||
|
#p.drawRect(self.item.boundingRect())
|
||||||
|
|
@ -690,7 +690,10 @@ class PlotItem(GraphicsWidget):
|
|||||||
|
|
||||||
def addItem(self, item, *args, **kargs):
|
def addItem(self, item, *args, **kargs):
|
||||||
self.items.append(item)
|
self.items.append(item)
|
||||||
self.vb.addItem(item, *args)
|
vbargs = {}
|
||||||
|
if 'ignoreBounds' in kargs:
|
||||||
|
vbargs['ignoreBounds'] = kargs['ignoreBounds']
|
||||||
|
self.vb.addItem(item, *args, **vbargs)
|
||||||
if hasattr(item, 'implements') and item.implements('plotData'):
|
if hasattr(item, 'implements') and item.implements('plotData'):
|
||||||
self.dataItems.append(item)
|
self.dataItems.append(item)
|
||||||
#self.plotChanged()
|
#self.plotChanged()
|
||||||
|
@ -197,11 +197,12 @@ class ViewBox(GraphicsWidget):
|
|||||||
def mouseEnabled(self):
|
def mouseEnabled(self):
|
||||||
return self.state['mouseEnabled'][:]
|
return self.state['mouseEnabled'][:]
|
||||||
|
|
||||||
def addItem(self, item):
|
def addItem(self, item, ignoreBounds=False):
|
||||||
if item.zValue() < self.zValue():
|
if item.zValue() < self.zValue():
|
||||||
item.setZValue(self.zValue()+1)
|
item.setZValue(self.zValue()+1)
|
||||||
item.setParentItem(self.childGroup)
|
item.setParentItem(self.childGroup)
|
||||||
self.addedItems.append(item)
|
if not ignoreBounds:
|
||||||
|
self.addedItems.append(item)
|
||||||
self.updateAutoRange()
|
self.updateAutoRange()
|
||||||
#print "addItem:", item, item.boundingRect()
|
#print "addItem:", item, item.boundingRect()
|
||||||
|
|
||||||
|
67
widgets/ValueLabel.py
Normal file
67
widgets/ValueLabel.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
from pyqtgraph.ptime import time
|
||||||
|
import pyqtgraph as pg
|
||||||
|
|
||||||
|
__all__ = ['ValueLabel']
|
||||||
|
|
||||||
|
class ValueLabel(QtGui.QLabel):
|
||||||
|
"""
|
||||||
|
QLabel specifically for displaying numerical values.
|
||||||
|
Extends QLabel adding some extra functionality:
|
||||||
|
- displaying units with si prefix
|
||||||
|
- built-in exponential averaging
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None):
|
||||||
|
"""
|
||||||
|
Arguments:
|
||||||
|
*suffix* (str or None) The suffix to place after the value
|
||||||
|
*siPrefix* (bool) Whether to add an SI prefix to the units and display a scaled value
|
||||||
|
*averageTime* (float) The length of time in seconds to average values. If this value
|
||||||
|
is 0, then no averaging is performed. As this value increases
|
||||||
|
the display value will appear to change more slowly and smoothly.
|
||||||
|
*formatStr* (str) Optionally, provide a format string to use when displaying text. The text will
|
||||||
|
be generated by calling formatStr.format(value=, avgValue=, suffix=)
|
||||||
|
(see Python documentation on str.format)
|
||||||
|
This option is not compatible with siPrefix
|
||||||
|
"""
|
||||||
|
QtGui.QLabel.__init__(self, parent)
|
||||||
|
self.values = []
|
||||||
|
self.averageTime = averageTime ## no averaging by default
|
||||||
|
self.suffix = suffix
|
||||||
|
self.siPrefix = siPrefix
|
||||||
|
if formatStr is None:
|
||||||
|
formatStr = '{avgValue:0.2g} {suffix}'
|
||||||
|
self.formatStr = formatStr
|
||||||
|
|
||||||
|
def setValue(self, value):
|
||||||
|
now = time()
|
||||||
|
self.values.append((now, value))
|
||||||
|
cutoff = now - self.averageTime
|
||||||
|
while len(self.values) > 0 and self.values[0][0] < cutoff:
|
||||||
|
self.values.pop(0)
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def setFormatStr(self, text):
|
||||||
|
self.formatStr = text
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
|
def averageValue(self):
|
||||||
|
return reduce(lambda a,b: a+b, [v[1] for v in self.values]) / float(len(self.values))
|
||||||
|
|
||||||
|
|
||||||
|
def paintEvent(self, ev):
|
||||||
|
self.setText(self.generateText())
|
||||||
|
return QtGui.QLabel.paintEvent(self, ev)
|
||||||
|
|
||||||
|
def generateText(self):
|
||||||
|
if len(self.values) == 0:
|
||||||
|
return ''
|
||||||
|
avg = self.averageValue()
|
||||||
|
val = self.values[-1][1]
|
||||||
|
if self.siPrefix:
|
||||||
|
return pg.siFormat(avg, suffix=self.suffix)
|
||||||
|
else:
|
||||||
|
return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user