Commit 5a357ddb authored by Luke Campagnola's avatar Luke Campagnola
Browse files

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
parent bdef8dc4
......@@ -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
self.timer.stop()
self.timer.start((self.delay*1000)+1)
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):
......
......@@ -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:
plusminus = space + u"±" + space
fmt = "%." + str(precision) + u"g%s%s%s%s"
if allowUnicode:
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))
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())
\ No newline at end of file
......@@ -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,11 +197,12 @@ 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)
self.addedItems.append(item)
if not ignoreBounds:
self.addedItems.append(item)
self.updateAutoRange()
#print "addItem:", item, item.boundingRect()
......
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)
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment