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):
|
||||
"""Object which collects rapid-fire signals and condenses them
|
||||
into a single signal. Used, for example, to prevent a SpinBox
|
||||
from generating multiple signals when the mouse wheel is rolled
|
||||
over it.
|
||||
into a single signal or a rate-limited stream of signals.
|
||||
Used, for example, to prevent a SpinBox from generating multiple
|
||||
signals when the mouse wheel is rolled over it.
|
||||
|
||||
Emits sigDelayed after input signals have stopped for a certain period of time.
|
||||
"""
|
||||
|
||||
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:
|
||||
signal - a bound Signal or pyqtSignal instance
|
||||
delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s)
|
||||
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)
|
||||
signal.connect(self.signalReceived)
|
||||
self.signal = signal
|
||||
self.delay = delay
|
||||
self.rateLimit = rateLimit
|
||||
self.args = None
|
||||
self.timer = ThreadsafeTimer.ThreadsafeTimer()
|
||||
self.timer.timeout.connect(self.flush)
|
||||
self.block = False
|
||||
self.slot = slot
|
||||
self.lastFlushTime = None
|
||||
if slot is not None:
|
||||
self.sigDelayed.connect(slot)
|
||||
|
||||
@ -43,8 +47,20 @@ class SignalProxy(QtCore.QObject):
|
||||
if self.block:
|
||||
return
|
||||
self.args = args
|
||||
if self.rateLimit == 0:
|
||||
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):
|
||||
"""If there is a signal queued up, send it now."""
|
||||
@ -54,6 +70,7 @@ class SignalProxy(QtCore.QObject):
|
||||
self.sigDelayed.emit(self.args)
|
||||
self.args = None
|
||||
self.timer.stop()
|
||||
self.lastFlushTime = time()
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
|
13
Transform.py
13
Transform.py
@ -5,8 +5,7 @@ import numpy as np
|
||||
|
||||
class Transform(QtGui.QTransform):
|
||||
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
|
||||
|
||||
This transform always has 0 shear.
|
||||
This transform has no shear; angles are always preserved.
|
||||
"""
|
||||
def __init__(self, init=None):
|
||||
QtGui.QTransform.__init__(self)
|
||||
@ -24,6 +23,16 @@ class Transform(QtGui.QTransform):
|
||||
elif isinstance(init, QtGui.QTransform):
|
||||
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):
|
||||
self._state = {
|
||||
'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.
|
||||
|
||||
Example::
|
||||
|
||||
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"
|
||||
return fmt % (x*p, pref, suffix)
|
||||
else:
|
||||
if allowUnicode:
|
||||
plusminus = space + u"±" + space
|
||||
fmt = "%." + str(precision) + u"g%s%s%s%s"
|
||||
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))
|
||||
|
||||
def siEval(s):
|
||||
|
@ -45,7 +45,7 @@ class GraphicsLayout(GraphicsWidget):
|
||||
self.addItem(vb, row, col, rowspan, colspan)
|
||||
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)
|
||||
self.addItem(text, row, col, rowspan, colspan)
|
||||
return text
|
||||
|
@ -221,7 +221,7 @@ class InfiniteLine(UIGraphicsItem):
|
||||
self.sigPositionChangeFinished.emit(self)
|
||||
|
||||
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)
|
||||
else:
|
||||
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)
|
||||
self.item = QtGui.QGraphicsTextItem(self)
|
||||
self.opts = args
|
||||
if 'color' not in args:
|
||||
self.opts['color'] = 'CCC'
|
||||
else:
|
||||
if isinstance(args['color'], QtGui.QColor):
|
||||
self.opts['color'] = fn.colorStr(args['color'])[:6]
|
||||
self.opts = {
|
||||
'color': 'CCC',
|
||||
'justify': 'center'
|
||||
}
|
||||
self.opts.update(args)
|
||||
self.sizeHint = {}
|
||||
self.setText(text)
|
||||
self.setAngle(angle)
|
||||
@ -47,6 +46,8 @@ class LabelItem(GraphicsWidget):
|
||||
|
||||
optlist = []
|
||||
if 'color' in opts:
|
||||
if isinstance(opts['color'], QtGui.QColor):
|
||||
opts['color'] = fn.colorStr(opts['color'])[:6]
|
||||
optlist.append('color: #' + opts['color'])
|
||||
if 'size' in opts:
|
||||
optlist.append('font-size: ' + opts['size'])
|
||||
@ -58,13 +59,25 @@ class LabelItem(GraphicsWidget):
|
||||
#print full
|
||||
self.item.setHtml(full)
|
||||
self.updateMin()
|
||||
self.resizeEvent(None)
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
c1 = self.boundingRect().center()
|
||||
c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos()
|
||||
dif = c1 - c2
|
||||
self.item.moveBy(dif.x(), dif.y())
|
||||
#c1 = self.boundingRect().center()
|
||||
#c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos()
|
||||
#dif = c1 - c2
|
||||
#self.item.moveBy(dif.x(), dif.y())
|
||||
#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):
|
||||
self.angle = angle
|
||||
@ -76,16 +89,23 @@ class LabelItem(GraphicsWidget):
|
||||
bounds = self.item.mapRectToParent(self.item.boundingRect())
|
||||
self.setMinimumWidth(bounds.width())
|
||||
self.setMinimumHeight(bounds.height())
|
||||
#print self.text, bounds.width(), bounds.height()
|
||||
|
||||
#self.sizeHint = {
|
||||
#QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
||||
#QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
||||
#QtCore.Qt.MaximumSize: (bounds.width()*2, bounds.height()*2),
|
||||
#QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
||||
#}
|
||||
self.sizeHint = {
|
||||
QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()),
|
||||
QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()),
|
||||
QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2),
|
||||
QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this?
|
||||
}
|
||||
|
||||
self.update()
|
||||
|
||||
#def sizeHint(self, hint, constraint):
|
||||
#return self.sizeHint[hint]
|
||||
def sizeHint(self, hint, constraint):
|
||||
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):
|
||||
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'):
|
||||
self.dataItems.append(item)
|
||||
#self.plotChanged()
|
||||
|
@ -197,10 +197,11 @@ class ViewBox(GraphicsWidget):
|
||||
def mouseEnabled(self):
|
||||
return self.state['mouseEnabled'][:]
|
||||
|
||||
def addItem(self, item):
|
||||
def addItem(self, item, ignoreBounds=False):
|
||||
if item.zValue() < self.zValue():
|
||||
item.setZValue(self.zValue()+1)
|
||||
item.setParentItem(self.childGroup)
|
||||
if not ignoreBounds:
|
||||
self.addedItems.append(item)
|
||||
self.updateAutoRange()
|
||||
#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