copy from acq4

This commit is contained in:
Luke Campagnola 2013-07-03 11:20:49 -04:00
parent 008ca76d53
commit 8c13a3e7e3
19 changed files with 613 additions and 204 deletions

View File

@ -80,6 +80,12 @@ class Point(QtCore.QPointF):
def __div__(self, a): def __div__(self, a):
return self._math_('__div__', a) return self._math_('__div__', a)
def __truediv__(self, a):
return self._math_('__truediv__', a)
def __rtruediv__(self, a):
return self._math_('__rtruediv__', a)
def __rpow__(self, a): def __rpow__(self, a):
return self._math_('__rpow__', a) return self._math_('__rpow__', a)

View File

@ -130,11 +130,14 @@ class SRTTransform(QtGui.QTransform):
self._state['angle'] = angle self._state['angle'] = angle
self.update() self.update()
def __div__(self, t): def __truediv__(self, t):
"""A / B == B^-1 * A""" """A / B == B^-1 * A"""
dt = t.inverted()[0] * self dt = t.inverted()[0] * self
return SRTTransform(dt) return SRTTransform(dt)
def __div__(self, t):
return self.__truediv__(t)
def __mul__(self, t): def __mul__(self, t):
return SRTTransform(QtGui.QTransform.__mul__(self, t)) return SRTTransform(QtGui.QTransform.__mul__(self, t))

View File

@ -123,7 +123,6 @@ class SRTTransform3D(pg.Transform3D):
m = self.matrix().reshape(4,4) m = self.matrix().reshape(4,4)
## translation is 4th column ## translation is 4th column
self._state['pos'] = m[:3,3] self._state['pos'] = m[:3,3]
## scale is vector-length of first three columns ## scale is vector-length of first three columns
scale = (m[:3,:3]**2).sum(axis=0)**0.5 scale = (m[:3,:3]**2).sum(axis=0)**0.5
## see whether there is an inversion ## see whether there is an inversion
@ -141,18 +140,30 @@ class SRTTransform3D(pg.Transform3D):
print("Scale: %s" % str(scale)) print("Scale: %s" % str(scale))
print("Original matrix: %s" % str(m)) print("Original matrix: %s" % str(m))
raise raise
eigIndex = np.argwhere(np.abs(evals-1) < 1e-7) eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)
if len(eigIndex) < 1: if len(eigIndex) < 1:
print("eigenvalues: %s" % str(evals)) print("eigenvalues: %s" % str(evals))
print("eigenvectors: %s" % str(evecs)) print("eigenvectors: %s" % str(evecs))
print("index: %s, %s" % (str(eigIndex), str(evals-1))) print("index: %s, %s" % (str(eigIndex), str(evals-1)))
raise Exception("Could not determine rotation axis.") raise Exception("Could not determine rotation axis.")
axis = evecs[eigIndex[0,0]].real axis = evecs[:,eigIndex[0,0]].real
axis /= ((axis**2).sum())**0.5 axis /= ((axis**2).sum())**0.5
self._state['axis'] = axis self._state['axis'] = axis
## trace(r) == 2 cos(angle) + 1, so: ## trace(r) == 2 cos(angle) + 1, so:
self._state['angle'] = np.arccos((r.trace()-1)*0.5) * 180 / np.pi cos = (r.trace()-1)*0.5 ## this only gets us abs(angle)
## The off-diagonal values can be used to correct the angle ambiguity,
## but we need to figure out which element to use:
axisInd = np.argmax(np.abs(axis))
rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd]
## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd];
## solve for sin(angle)
sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd])
## finally, we get the complete angle from arctan(sin/cos)
self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi
if self._state['angle'] == 0: if self._state['angle'] == 0:
self._state['axis'] = (0,0,1) self._state['axis'] = (0,0,1)

View File

@ -28,6 +28,15 @@ def ftrace(func):
return rv return rv
return w return w
def warnOnException(func):
"""Decorator which catches/ignores exceptions and prints a stack trace."""
def w(*args, **kwds):
try:
func(*args, **kwds)
except:
printExc('Ignored exception:')
return w
def getExc(indent=4, prefix='| '): def getExc(indent=4, prefix='| '):
tb = traceback.format_exc() tb = traceback.format_exc()
lines = [] lines = []

View File

@ -24,6 +24,14 @@ class BinOpNode(Node):
}) })
def process(self, **args): def process(self, **args):
if isinstance(self.fn, tuple):
for name in self.fn:
try:
fn = getattr(args['A'], name)
break
except AttributeError:
pass
else:
fn = getattr(args['A'], self.fn) fn = getattr(args['A'], self.fn)
out = fn(args['B']) out = fn(args['B'])
if out is NotImplemented: if out is NotImplemented:
@ -60,5 +68,7 @@ class DivideNode(BinOpNode):
"""Returns A / B. Does not check input types.""" """Returns A / B. Does not check input types."""
nodeName = 'Divide' nodeName = 'Divide'
def __init__(self, name): def __init__(self, name):
BinOpNode.__init__(self, name, '__div__') # try truediv first, followed by div
BinOpNode.__init__(self, name, ('__truediv__', '__div__'))

View File

@ -264,6 +264,7 @@ def mkPen(*args, **kargs):
color = kargs.get('color', None) color = kargs.get('color', None)
width = kargs.get('width', 1) width = kargs.get('width', 1)
style = kargs.get('style', None) style = kargs.get('style', None)
dash = kargs.get('dash', None)
cosmetic = kargs.get('cosmetic', True) cosmetic = kargs.get('cosmetic', True)
hsv = kargs.get('hsv', None) hsv = kargs.get('hsv', None)
@ -291,6 +292,8 @@ def mkPen(*args, **kargs):
pen.setCosmetic(cosmetic) pen.setCosmetic(cosmetic)
if style is not None: if style is not None:
pen.setStyle(style) pen.setStyle(style)
if dash is not None:
pen.setDashPattern(dash)
return pen return pen
def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):
@ -1948,6 +1951,8 @@ def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
s2 = spacing**2 s2 = spacing**2
yvals = np.empty(len(data)) yvals = np.empty(len(data))
if len(data) == 0:
return yvals
yvals[0] = 0 yvals[0] = 0
for i in range(1,len(data)): for i in range(1,len(data)):
x = data[i] # current x value to be placed x = data[i] # current x value to be placed

View File

@ -42,12 +42,18 @@ class AxisItem(GraphicsWidget):
self.label.rotate(-90) self.label.rotate(-90)
self.style = { self.style = {
'tickTextOffset': 3, ## spacing between text and axis 'tickTextOffset': (5, 2), ## (horizontal, vertical) spacing between text and axis
'tickTextWidth': 30, ## space reserved for tick text 'tickTextWidth': 30, ## space reserved for tick text
'tickTextHeight': 18, 'tickTextHeight': 18,
'autoExpandTextSpace': True, ## automatically expand text space if needed 'autoExpandTextSpace': True, ## automatically expand text space if needed
'tickFont': None, 'tickFont': None,
'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick
'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally.
(0, 0.8), ## never fill more than 80% of the axis
(2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis
(4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis
(6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis
]
} }
self.textWidth = 30 ## Keeps track of maximum width / height of tick text self.textWidth = 30 ## Keeps track of maximum width / height of tick text
@ -209,14 +215,14 @@ class AxisItem(GraphicsWidget):
## to accomodate. ## to accomodate.
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
mx = max(self.textWidth, x) mx = max(self.textWidth, x)
if mx > self.textWidth: if mx > self.textWidth or mx < self.textWidth-10:
self.textWidth = mx self.textWidth = mx
if self.style['autoExpandTextSpace'] is True: if self.style['autoExpandTextSpace'] is True:
self.setWidth() self.setWidth()
#return True ## size has changed #return True ## size has changed
else: else:
mx = max(self.textHeight, x) mx = max(self.textHeight, x)
if mx > self.textHeight: if mx > self.textHeight or mx < self.textHeight-10:
self.textHeight = mx self.textHeight = mx
if self.style['autoExpandTextSpace'] is True: if self.style['autoExpandTextSpace'] is True:
self.setHeight() self.setHeight()
@ -236,7 +242,7 @@ class AxisItem(GraphicsWidget):
h = self.textHeight h = self.textHeight
else: else:
h = self.style['tickTextHeight'] h = self.style['tickTextHeight']
h += max(0, self.tickLength) + self.style['tickTextOffset'] h += max(0, self.tickLength) + self.style['tickTextOffset'][1]
if self.label.isVisible(): if self.label.isVisible():
h += self.label.boundingRect().height() * 0.8 h += self.label.boundingRect().height() * 0.8
self.setMaximumHeight(h) self.setMaximumHeight(h)
@ -252,7 +258,7 @@ class AxisItem(GraphicsWidget):
w = self.textWidth w = self.textWidth
else: else:
w = self.style['tickTextWidth'] w = self.style['tickTextWidth']
w += max(0, self.tickLength) + self.style['tickTextOffset'] w += max(0, self.tickLength) + self.style['tickTextOffset'][0]
if self.label.isVisible(): if self.label.isVisible():
w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
self.setMaximumWidth(w) self.setMaximumWidth(w)
@ -430,7 +436,7 @@ class AxisItem(GraphicsWidget):
return [] return []
## decide optimal minor tick spacing in pixels (this is just aesthetics) ## decide optimal minor tick spacing in pixels (this is just aesthetics)
pixelSpacing = np.log(size+10) * 5 pixelSpacing = size / np.log(size)
optimalTickCount = max(2., size / pixelSpacing) optimalTickCount = max(2., size / pixelSpacing)
## optimal minor tick spacing ## optimal minor tick spacing
@ -720,7 +726,7 @@ class AxisItem(GraphicsWidget):
textOffset = self.style['tickTextOffset'] ## spacing between axis and text textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text
#if self.style['autoExpandTextSpace'] is True: #if self.style['autoExpandTextSpace'] is True:
#textWidth = self.textWidth #textWidth = self.textWidth
#textHeight = self.textHeight #textHeight = self.textHeight
@ -728,7 +734,7 @@ class AxisItem(GraphicsWidget):
#textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text
#textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text
textSize2 = 0
textRects = [] textRects = []
textSpecs = [] ## list of draw textSpecs = [] ## list of draw
for i in range(len(tickLevels)): for i in range(len(tickLevels)):
@ -770,9 +776,16 @@ class AxisItem(GraphicsWidget):
textSize = np.sum([r.width() for r in textRects]) textSize = np.sum([r.width() for r in textRects])
textSize2 = np.max([r.height() for r in textRects]) textSize2 = np.max([r.height() for r in textRects])
## If the strings are too crowded, stop drawing text now ## If the strings are too crowded, stop drawing text now.
## We use three different crowding limits based on the number
## of texts drawn so far.
textFillRatio = float(textSize) / lengthInPixels textFillRatio = float(textSize) / lengthInPixels
if textFillRatio > 0.7: finished = False
for nTexts, limit in self.style['textFillLimits']:
if len(textSpecs) >= nTexts and textFillRatio >= limit:
finished = True
break
if finished:
break break
#spacing, values = tickLevels[best] #spacing, values = tickLevels[best]

View File

@ -533,6 +533,7 @@ class GraphicsItem(object):
def viewTransformChanged(self): def viewTransformChanged(self):
""" """
Called whenever the transformation matrix of the view has changed. Called whenever the transformation matrix of the view has changed.
(eg, the view range has changed or the view was resized)
""" """
pass pass

View File

@ -375,6 +375,7 @@ class PlotCurveItem(GraphicsObject):
return QtGui.QPainterPath() return QtGui.QPainterPath()
return self.path return self.path
@pg.debug.warnOnException ## raising an exception here causes crash
def paint(self, p, opt, widget): def paint(self, p, opt, widget):
prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
if self.xData is None: if self.xData is None:

View File

@ -84,24 +84,28 @@ class PlotDataItem(GraphicsObject):
**Optimization keyword arguments:** **Optimization keyword arguments:**
============ ===================================================================== ================ =====================================================================
antialias (bool) By default, antialiasing is disabled to improve performance. antialias (bool) By default, antialiasing is disabled to improve performance.
Note that in some cases (in particluar, when pxMode=True), points Note that in some cases (in particluar, when pxMode=True), points
will be rendered antialiased even if this is set to False. will be rendered antialiased even if this is set to False.
decimate (int) Sub-sample data by selecting every nth sample before plotting decimate deprecated.
onlyVisible (bool) If True, only plot data that is visible within the X range of downsample (int) Reduce the number of samples displayed by this value
the containing ViewBox. This can improve performance when plotting downsampleMethod 'subsample': Downsample by taking the first of N samples.
very large data sets where only a fraction of the data is visible This method is fastest and least accurate.
at any time. 'mean': Downsample by taking the mean of N samples.
autoResample (bool) If True, resample the data before plotting to avoid plotting 'peak': Downsample by drawing a saw wave that follows the min
and max of the original data. This method produces the best
visual representation of the data but is slower.
autoDownsample (bool) If True, resample the data before plotting to avoid plotting
multiple line segments per pixel. This can improve performance when multiple line segments per pixel. This can improve performance when
viewing very high-density data, but increases the initial overhead viewing very high-density data, but increases the initial overhead
and memory usage. and memory usage.
sampleRate (float) The sample rate of the data along the X axis (for data with clipToView (bool) If True, only plot data that is visible within the X range of
a fixed sample rate). Providing this value improves performance of the containing ViewBox. This can improve performance when plotting
the *onlyVisible* and *autoResample* options. very large data sets where only a fraction of the data is visible
at any time.
identical *deprecated* identical *deprecated*
============ ===================================================================== ================ =====================================================================
**Meta-info keyword arguments:** **Meta-info keyword arguments:**
@ -131,7 +135,6 @@ class PlotDataItem(GraphicsObject):
self.opts = { self.opts = {
'fftMode': False, 'fftMode': False,
'logMode': [False, False], 'logMode': [False, False],
'downsample': False,
'alphaHint': 1.0, 'alphaHint': 1.0,
'alphaMode': False, 'alphaMode': False,
@ -149,6 +152,11 @@ class PlotDataItem(GraphicsObject):
'antialias': pg.getConfigOption('antialias'), 'antialias': pg.getConfigOption('antialias'),
'pointMode': None, 'pointMode': None,
'downsample': 1,
'autoDownsample': False,
'downsampleMethod': 'peak',
'clipToView': False,
'data': None, 'data': None,
} }
self.setData(*args, **kargs) self.setData(*args, **kargs)
@ -175,6 +183,7 @@ class PlotDataItem(GraphicsObject):
return return
self.opts['fftMode'] = mode self.opts['fftMode'] = mode
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
self.xClean = self.yClean = None
self.updateItems() self.updateItems()
self.informViewBoundsChanged() self.informViewBoundsChanged()
@ -183,6 +192,7 @@ class PlotDataItem(GraphicsObject):
return return
self.opts['logMode'] = [xMode, yMode] self.opts['logMode'] = [xMode, yMode]
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
self.xClean = self.yClean = None
self.updateItems() self.updateItems()
self.informViewBoundsChanged() self.informViewBoundsChanged()
@ -269,13 +279,51 @@ class PlotDataItem(GraphicsObject):
#self.scatter.setSymbolSize(symbolSize) #self.scatter.setSymbolSize(symbolSize)
self.updateItems() self.updateItems()
def setDownsampling(self, ds): def setDownsampling(self, ds=None, auto=None, method=None):
if self.opts['downsample'] == ds: """
return Set the downsampling mode of this item. Downsampling reduces the number
of samples drawn to increase performance.
=========== =================================================================
Arguments
ds (int) Reduce visible plot samples by this factor. To disable,
set ds=1.
auto (bool) If True, automatically pick *ds* based on visible range
mode 'subsample': Downsample by taking the first of N samples.
This method is fastest and least accurate.
'mean': Downsample by taking the mean of N samples.
'peak': Downsample by drawing a saw wave that follows the min
and max of the original data. This method produces the best
visual representation of the data but is slower.
=========== =================================================================
"""
changed = False
if ds is not None:
if self.opts['downsample'] != ds:
changed = True
self.opts['downsample'] = ds self.opts['downsample'] = ds
if auto is not None and self.opts['autoDownsample'] != auto:
self.opts['autoDownsample'] = auto
changed = True
if method is not None:
if self.opts['downsampleMethod'] != method:
changed = True
self.opts['downsampleMethod'] = method
if changed:
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
self.updateItems() self.updateItems()
def setClipToView(self, clip):
if self.opts['clipToView'] == clip:
return
self.opts['clipToView'] = clip
self.xDisp = self.yDisp = None
self.updateItems()
def setData(self, *args, **kargs): def setData(self, *args, **kargs):
""" """
Clear any data displayed by this item and display new data. Clear any data displayed by this item and display new data.
@ -315,7 +363,7 @@ class PlotDataItem(GraphicsObject):
raise Exception('Invalid data type %s' % type(data)) raise Exception('Invalid data type %s' % type(data))
elif len(args) == 2: elif len(args) == 2:
seq = ('listOfValues', 'MetaArray') seq = ('listOfValues', 'MetaArray', 'empty')
if dataType(args[0]) not in seq or dataType(args[1]) not in seq: if dataType(args[0]) not in seq or dataType(args[1]) not in seq:
raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1]))))
if not isinstance(args[0], np.ndarray): if not isinstance(args[0], np.ndarray):
@ -376,6 +424,7 @@ class PlotDataItem(GraphicsObject):
self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by
self.yData = y.view(np.ndarray) self.yData = y.view(np.ndarray)
self.xClean = self.yClean = None
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
prof.mark('set data') prof.mark('set data')
@ -423,23 +472,28 @@ class PlotDataItem(GraphicsObject):
def getData(self): def getData(self):
if self.xData is None: if self.xData is None:
return (None, None) return (None, None)
if self.xDisp is None:
if self.xClean is None:
nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData)
if any(nanMask): if any(nanMask):
self.dataMask = ~nanMask self.dataMask = ~nanMask
x = self.xData[self.dataMask] self.xClean = self.xData[self.dataMask]
y = self.yData[self.dataMask] self.yClean = self.yData[self.dataMask]
else: else:
self.dataMask = None self.dataMask = None
x = self.xData self.xClean = self.xData
y = self.yData self.yClean = self.yData
if self.xDisp is None:
x = self.xClean
y = self.yClean
ds = self.opts['downsample'] #ds = self.opts['downsample']
if ds > 1: #if isinstance(ds, int) and ds > 1:
x = x[::ds] #x = x[::ds]
#y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing ##y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing
y = y[::ds] #y = y[::ds]
if self.opts['fftMode']: if self.opts['fftMode']:
f = np.fft.fft(y) / len(y) f = np.fft.fft(y) / len(y)
y = abs(f[1:len(f)/2]) y = abs(f[1:len(f)/2])
@ -457,6 +511,53 @@ class PlotDataItem(GraphicsObject):
y = y[self.dataMask] y = y[self.dataMask]
else: else:
self.dataMask = None self.dataMask = None
ds = self.opts['downsample']
if not isinstance(ds, int):
ds = 1
if self.opts['autoDownsample']:
# this option presumes that x-values have uniform spacing
range = self.viewRect()
if range is not None:
dx = float(x[-1]-x[0]) / (len(x)-1)
x0 = (range.left()-x[0]) / dx
x1 = (range.right()-x[0]) / dx
width = self.getViewBox().width()
ds = int(max(1, int(0.2 * (x1-x0) / width)))
## downsampling is expensive; delay until after clipping.
if self.opts['clipToView']:
# this option presumes that x-values have uniform spacing
range = self.viewRect()
if range is not None:
dx = float(x[-1]-x[0]) / (len(x)-1)
# clip to visible region extended by downsampling value
x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1)
x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1)
x = x[x0:x1]
y = y[x0:x1]
if ds > 1:
if self.opts['downsampleMethod'] == 'subsample':
x = x[::ds]
y = y[::ds]
elif self.opts['downsampleMethod'] == 'mean':
n = len(x) / ds
x = x[:n*ds:ds]
y = y[:n*ds].reshape(n,ds).mean(axis=1)
elif self.opts['downsampleMethod'] == 'peak':
n = len(x) / ds
x1 = np.empty((n,2))
x1[:] = x[:n*ds:ds,np.newaxis]
x = x1.reshape(n*2)
y1 = np.empty((n,2))
y2 = y[:n*ds].reshape((n, ds))
y1[:,0] = y2.max(axis=1)
y1[:,1] = y2.min(axis=1)
y = y1.reshape(n*2)
self.xDisp = x self.xDisp = x
self.yDisp = y self.yDisp = y
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
@ -542,6 +643,8 @@ class PlotDataItem(GraphicsObject):
#self.scatters = [] #self.scatters = []
self.xData = None self.xData = None
self.yData = None self.yData = None
self.xClean = None
self.yClean = None
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
self.curve.setData([]) self.curve.setData([])
@ -557,6 +660,14 @@ class PlotDataItem(GraphicsObject):
self.sigClicked.emit(self) self.sigClicked.emit(self)
self.sigPointsClicked.emit(self, points) self.sigPointsClicked.emit(self, points)
def viewRangeChanged(self):
# view range has changed; re-plot if needed
if self.opts['clipToView'] or self.opts['autoDownsample']:
self.xDisp = self.yDisp = None
self.updateItems()
def dataType(obj): def dataType(obj):
if hasattr(obj, '__len__') and len(obj) == 0: if hasattr(obj, '__len__') and len(obj) == 0:

View File

@ -256,6 +256,11 @@ class PlotItem(GraphicsWidget):
c.logYCheck.toggled.connect(self.updateLogMode) c.logYCheck.toggled.connect(self.updateLogMode)
c.downsampleSpin.valueChanged.connect(self.updateDownsampling) c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
c.downsampleCheck.toggled.connect(self.updateDownsampling)
c.autoDownsampleCheck.toggled.connect(self.updateDownsampling)
c.subsampleRadio.toggled.connect(self.updateDownsampling)
c.meanRadio.toggled.connect(self.updateDownsampling)
c.clipToViewCheck.toggled.connect(self.updateDownsampling)
self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked)
self.ctrl.averageGroup.toggled.connect(self.avgToggled) self.ctrl.averageGroup.toggled.connect(self.avgToggled)
@ -526,7 +531,8 @@ class PlotItem(GraphicsWidget):
(alpha, auto) = self.alphaState() (alpha, auto) = self.alphaState()
item.setAlpha(alpha, auto) item.setAlpha(alpha, auto)
item.setFftMode(self.ctrl.fftCheck.isChecked()) item.setFftMode(self.ctrl.fftCheck.isChecked())
item.setDownsampling(self.downsampleMode()) item.setDownsampling(*self.downsampleMode())
item.setClipToView(self.clipToViewMode())
item.setPointMode(self.pointMode()) item.setPointMode(self.pointMode())
## Hide older plots if needed ## Hide older plots if needed
@ -568,8 +574,8 @@ class PlotItem(GraphicsWidget):
:func:`InfiniteLine.__init__() <pyqtgraph.InfiniteLine.__init__>`. :func:`InfiniteLine.__init__() <pyqtgraph.InfiniteLine.__init__>`.
Returns the item created. Returns the item created.
""" """
angle = 0 if x is None else 90 pos = kwds.get('pos', x if x is not None else y)
pos = x if x is not None else y angle = kwds.get('angle', 0 if x is None else 90)
line = InfiniteLine(pos, angle, **kwds) line = InfiniteLine(pos, angle, **kwds)
self.addItem(line) self.addItem(line)
if z is not None: if z is not None:
@ -941,23 +947,81 @@ class PlotItem(GraphicsWidget):
self.enableAutoRange() self.enableAutoRange()
self.recomputeAverages() self.recomputeAverages()
def setDownsampling(self, ds=None, auto=None, mode=None):
"""Change the default downsampling mode for all PlotDataItems managed by this plot.
=========== =================================================================
Arguments
ds (int) Reduce visible plot samples by this factor, or
(bool) To enable/disable downsampling without changing the value.
auto (bool) If True, automatically pick *ds* based on visible range
mode 'subsample': Downsample by taking the first of N samples.
This method is fastest and least accurate.
'mean': Downsample by taking the mean of N samples.
'peak': Downsample by drawing a saw wave that follows the min
and max of the original data. This method produces the best
visual representation of the data but is slower.
=========== =================================================================
"""
if ds is not None:
if ds is False:
self.ctrl.downsampleCheck.setChecked(False)
elif ds is True:
self.ctrl.downsampleCheck.setChecked(True)
else:
self.ctrl.downsampleCheck.setChecked(True)
self.ctrl.downsampleSpin.setValue(ds)
if auto is not None:
if auto and ds is not False:
self.ctrl.downsampleCheck.setChecked(True)
self.ctrl.autoDownsampleCheck.setChecked(auto)
if mode is not None:
if mode == 'subsample':
self.ctrl.subsampleRadio.setChecked(True)
elif mode == 'mean':
self.ctrl.meanRadio.setChecked(True)
elif mode == 'peak':
self.ctrl.peakRadio.setChecked(True)
else:
raise ValueError("mode argument must be 'subsample', 'mean', or 'peak'.")
def updateDownsampling(self): def updateDownsampling(self):
ds = self.downsampleMode() ds, auto, method = self.downsampleMode()
clip = self.ctrl.clipToViewCheck.isChecked()
for c in self.curves: for c in self.curves:
c.setDownsampling(ds) c.setDownsampling(ds, auto, method)
c.setClipToView(clip)
self.recomputeAverages() self.recomputeAverages()
def downsampleMode(self): def downsampleMode(self):
if self.ctrl.decimateGroup.isChecked(): if self.ctrl.downsampleCheck.isChecked():
if self.ctrl.manualDecimateRadio.isChecked():
ds = self.ctrl.downsampleSpin.value() ds = self.ctrl.downsampleSpin.value()
else: else:
ds = True ds = 1
else:
ds = False auto = self.ctrl.downsampleCheck.isChecked() and self.ctrl.autoDownsampleCheck.isChecked()
return ds
if self.ctrl.subsampleRadio.isChecked():
method = 'subsample'
elif self.ctrl.meanRadio.isChecked():
method = 'mean'
elif self.ctrl.peakRadio.isChecked():
method = 'peak'
return ds, auto, method
def setClipToView(self, clip):
"""Set the default clip-to-view mode for all PlotDataItems managed by this plot.
If *clip* is True, then PlotDataItems will attempt to draw only points within the visible
range of the ViewBox."""
self.ctrl.clipToViewCheck.setChecked(clip)
def clipToViewMode(self):
return self.ctrl.clipToViewCheck.isChecked()
def updateDecimation(self): def updateDecimation(self):
if self.ctrl.maxTracesCheck.isChecked(): if self.ctrl.maxTracesCheck.isChecked():

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>258</width> <width>481</width>
<height>605</height> <height>840</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -16,8 +16,8 @@
<widget class="QGroupBox" name="averageGroup"> <widget class="QGroupBox" name="averageGroup">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>0</x>
<y>200</y> <y>640</y>
<width>242</width> <width>242</width>
<height>182</height> <height>182</height>
</rect> </rect>
@ -46,21 +46,15 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QGroupBox" name="decimateGroup"> <widget class="QFrame" name="decimateGroup">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>10</x>
<y>70</y> <y>140</y>
<width>242</width> <width>191</width>
<height>160</height> <height>171</height>
</rect> </rect>
</property> </property>
<property name="title">
<string>Downsample</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<property name="margin"> <property name="margin">
<number>0</number> <number>0</number>
@ -68,40 +62,17 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="7" column="0" colspan="3">
<widget class="QRadioButton" name="manualDecimateRadio"> <widget class="QCheckBox" name="clipToViewCheck">
<property name="toolTip">
<string>Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.</string>
</property>
<property name="text"> <property name="text">
<string>Manual</string> <string>Clip to View</string>
</property>
<property name="checked">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="8" column="0" colspan="2">
<widget class="QSpinBox" name="downsampleSpin">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100000</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="autoDecimateRadio">
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="maxTracesCheck"> <widget class="QCheckBox" name="maxTracesCheck">
<property name="toolTip"> <property name="toolTip">
<string>If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.</string> <string>If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.</string>
@ -111,14 +82,34 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="0" column="0" colspan="3">
<widget class="QCheckBox" name="downsampleCheck">
<property name="text">
<string>Downsample</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="QRadioButton" name="peakRadio">
<property name="toolTip">
<string>Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.</string>
</property>
<property name="text">
<string>Peak</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QSpinBox" name="maxTracesSpin"> <widget class="QSpinBox" name="maxTracesSpin">
<property name="toolTip"> <property name="toolTip">
<string>If multiple curves are displayed in this plot, check &quot;Max Traces&quot; and set this value to limit the number of traces that are displayed.</string> <string>If multiple curves are displayed in this plot, check &quot;Max Traces&quot; and set this value to limit the number of traces that are displayed.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="9" column="0" colspan="3">
<widget class="QCheckBox" name="forgetTracesCheck"> <widget class="QCheckBox" name="forgetTracesCheck">
<property name="toolTip"> <property name="toolTip">
<string>If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).</string> <string>If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).</string>
@ -128,6 +119,74 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" colspan="2">
<widget class="QRadioButton" name="meanRadio">
<property name="toolTip">
<string>Downsample by taking the mean of N samples.</string>
</property>
<property name="text">
<string>Mean</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QRadioButton" name="subsampleRadio">
<property name="toolTip">
<string>Downsample by taking the first of N samples. This method is fastest and least accurate.</string>
</property>
<property name="text">
<string>Subsample</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="autoDownsampleCheck">
<property name="toolTip">
<string>Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.</string>
</property>
<property name="text">
<string>Auto</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="downsampleSpin">
<property name="toolTip">
<string>Downsample data before plotting. (plot every Nth sample)</string>
</property>
<property name="suffix">
<string>x</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100000</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QFrame" name="transformGroup"> <widget class="QFrame" name="transformGroup">

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './graphicsItems/PlotItem/plotConfigTemplate.ui' # Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui'
# #
# Created: Sun Sep 9 14:41:32 2012 # Created: Mon Jul 1 23:21:08 2013
# by: PyQt4 UI code generator 4.9.1 # by: PyQt4 UI code generator 4.9.3
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -17,9 +17,9 @@ except AttributeError:
class Ui_Form(object): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form")) Form.setObjectName(_fromUtf8("Form"))
Form.resize(258, 605) Form.resize(481, 840)
self.averageGroup = QtGui.QGroupBox(Form) self.averageGroup = QtGui.QGroupBox(Form)
self.averageGroup.setGeometry(QtCore.QRect(10, 200, 242, 182)) self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182))
self.averageGroup.setCheckable(True) self.averageGroup.setCheckable(True)
self.averageGroup.setChecked(False) self.averageGroup.setChecked(False)
self.averageGroup.setObjectName(_fromUtf8("averageGroup")) self.averageGroup.setObjectName(_fromUtf8("averageGroup"))
@ -30,37 +30,50 @@ class Ui_Form(object):
self.avgParamList = QtGui.QListWidget(self.averageGroup) self.avgParamList = QtGui.QListWidget(self.averageGroup)
self.avgParamList.setObjectName(_fromUtf8("avgParamList")) self.avgParamList.setObjectName(_fromUtf8("avgParamList"))
self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1)
self.decimateGroup = QtGui.QGroupBox(Form) self.decimateGroup = QtGui.QFrame(Form)
self.decimateGroup.setGeometry(QtCore.QRect(0, 70, 242, 160)) self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171))
self.decimateGroup.setCheckable(True)
self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) self.decimateGroup.setObjectName(_fromUtf8("decimateGroup"))
self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup)
self.gridLayout_4.setMargin(0) self.gridLayout_4.setMargin(0)
self.gridLayout_4.setSpacing(0) self.gridLayout_4.setSpacing(0)
self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4"))
self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup) self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup)
self.manualDecimateRadio.setChecked(True) self.clipToViewCheck.setObjectName(_fromUtf8("clipToViewCheck"))
self.manualDecimateRadio.setObjectName(_fromUtf8("manualDecimateRadio")) self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3)
self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1) self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck"))
self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2)
self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup)
self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck"))
self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3)
self.peakRadio = QtGui.QRadioButton(self.decimateGroup)
self.peakRadio.setChecked(True)
self.peakRadio.setObjectName(_fromUtf8("peakRadio"))
self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2)
self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup)
self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin"))
self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1)
self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck"))
self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3)
self.meanRadio = QtGui.QRadioButton(self.decimateGroup)
self.meanRadio.setObjectName(_fromUtf8("meanRadio"))
self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2)
self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup)
self.subsampleRadio.setObjectName(_fromUtf8("subsampleRadio"))
self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2)
self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup)
self.autoDownsampleCheck.setChecked(True)
self.autoDownsampleCheck.setObjectName(_fromUtf8("autoDownsampleCheck"))
self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1)
spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum)
self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1)
self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup)
self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMinimum(1)
self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setMaximum(100000)
self.downsampleSpin.setProperty("value", 1) self.downsampleSpin.setProperty("value", 1)
self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin"))
self.gridLayout_4.addWidget(self.downsampleSpin, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1)
self.autoDecimateRadio = QtGui.QRadioButton(self.decimateGroup)
self.autoDecimateRadio.setChecked(False)
self.autoDecimateRadio.setObjectName(_fromUtf8("autoDecimateRadio"))
self.gridLayout_4.addWidget(self.autoDecimateRadio, 1, 0, 1, 1)
self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck"))
self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1)
self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup)
self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin"))
self.gridLayout_4.addWidget(self.maxTracesSpin, 2, 1, 1, 1)
self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck"))
self.gridLayout_4.addWidget(self.forgetTracesCheck, 3, 0, 1, 2)
self.transformGroup = QtGui.QFrame(Form) self.transformGroup = QtGui.QFrame(Form)
self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79))
self.transformGroup.setObjectName(_fromUtf8("transformGroup")) self.transformGroup.setObjectName(_fromUtf8("transformGroup"))
@ -129,14 +142,24 @@ class Ui_Form(object):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8))
self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8))
self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8))
self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8))
self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8))
self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8))
self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8))
self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8))
self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8))
self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8))
self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8))
self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8))
self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Form implementation generated from reading ui file './graphicsItems/PlotItem/plotConfigTemplate.ui' # Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui'
# #
# Created: Sun Sep 9 14:41:32 2012 # Created: Mon Jul 1 23:21:08 2013
# by: pyside-uic 0.2.13 running on PySide 1.1.0 # by: pyside-uic 0.2.13 running on PySide 1.1.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@ -12,9 +12,9 @@ from PySide import QtCore, QtGui
class Ui_Form(object): class Ui_Form(object):
def setupUi(self, Form): def setupUi(self, Form):
Form.setObjectName("Form") Form.setObjectName("Form")
Form.resize(258, 605) Form.resize(481, 840)
self.averageGroup = QtGui.QGroupBox(Form) self.averageGroup = QtGui.QGroupBox(Form)
self.averageGroup.setGeometry(QtCore.QRect(10, 200, 242, 182)) self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182))
self.averageGroup.setCheckable(True) self.averageGroup.setCheckable(True)
self.averageGroup.setChecked(False) self.averageGroup.setChecked(False)
self.averageGroup.setObjectName("averageGroup") self.averageGroup.setObjectName("averageGroup")
@ -25,37 +25,50 @@ class Ui_Form(object):
self.avgParamList = QtGui.QListWidget(self.averageGroup) self.avgParamList = QtGui.QListWidget(self.averageGroup)
self.avgParamList.setObjectName("avgParamList") self.avgParamList.setObjectName("avgParamList")
self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1)
self.decimateGroup = QtGui.QGroupBox(Form) self.decimateGroup = QtGui.QFrame(Form)
self.decimateGroup.setGeometry(QtCore.QRect(0, 70, 242, 160)) self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171))
self.decimateGroup.setCheckable(True)
self.decimateGroup.setObjectName("decimateGroup") self.decimateGroup.setObjectName("decimateGroup")
self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup)
self.gridLayout_4.setContentsMargins(0, 0, 0, 0) self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
self.gridLayout_4.setSpacing(0) self.gridLayout_4.setSpacing(0)
self.gridLayout_4.setObjectName("gridLayout_4") self.gridLayout_4.setObjectName("gridLayout_4")
self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup) self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup)
self.manualDecimateRadio.setChecked(True) self.clipToViewCheck.setObjectName("clipToViewCheck")
self.manualDecimateRadio.setObjectName("manualDecimateRadio") self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3)
self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1) self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.maxTracesCheck.setObjectName("maxTracesCheck")
self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2)
self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup)
self.downsampleCheck.setObjectName("downsampleCheck")
self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3)
self.peakRadio = QtGui.QRadioButton(self.decimateGroup)
self.peakRadio.setChecked(True)
self.peakRadio.setObjectName("peakRadio")
self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2)
self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup)
self.maxTracesSpin.setObjectName("maxTracesSpin")
self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1)
self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.forgetTracesCheck.setObjectName("forgetTracesCheck")
self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3)
self.meanRadio = QtGui.QRadioButton(self.decimateGroup)
self.meanRadio.setObjectName("meanRadio")
self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2)
self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup)
self.subsampleRadio.setObjectName("subsampleRadio")
self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2)
self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup)
self.autoDownsampleCheck.setChecked(True)
self.autoDownsampleCheck.setObjectName("autoDownsampleCheck")
self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1)
spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum)
self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1)
self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup)
self.downsampleSpin.setMinimum(1) self.downsampleSpin.setMinimum(1)
self.downsampleSpin.setMaximum(100000) self.downsampleSpin.setMaximum(100000)
self.downsampleSpin.setProperty("value", 1) self.downsampleSpin.setProperty("value", 1)
self.downsampleSpin.setObjectName("downsampleSpin") self.downsampleSpin.setObjectName("downsampleSpin")
self.gridLayout_4.addWidget(self.downsampleSpin, 0, 1, 1, 1) self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1)
self.autoDecimateRadio = QtGui.QRadioButton(self.decimateGroup)
self.autoDecimateRadio.setChecked(False)
self.autoDecimateRadio.setObjectName("autoDecimateRadio")
self.gridLayout_4.addWidget(self.autoDecimateRadio, 1, 0, 1, 1)
self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.maxTracesCheck.setObjectName("maxTracesCheck")
self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1)
self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup)
self.maxTracesSpin.setObjectName("maxTracesSpin")
self.gridLayout_4.addWidget(self.maxTracesSpin, 2, 1, 1, 1)
self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup)
self.forgetTracesCheck.setObjectName("forgetTracesCheck")
self.gridLayout_4.addWidget(self.forgetTracesCheck, 3, 0, 1, 2)
self.transformGroup = QtGui.QFrame(Form) self.transformGroup = QtGui.QFrame(Form)
self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79))
self.transformGroup.setObjectName("transformGroup") self.transformGroup.setObjectName("transformGroup")
@ -124,14 +137,24 @@ class Ui_Form(object):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8))
self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8))
self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8))
self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8))
self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8))
self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8))
self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8))
self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8))
self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8))
self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8))
self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8))
self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8))
self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8))
self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8))
self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -15,7 +15,7 @@ __all__ = ['ScatterPlotItem', 'SpotItem']
## Build all symbol paths ## Build all symbol paths
Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+']]) Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']])
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 = {
@ -32,6 +32,9 @@ for k, c in coords.items():
for x,y in c[1:]: for x,y in c[1:]:
Symbols[k].lineTo(x, y) Symbols[k].lineTo(x, y)
Symbols[k].closeSubpath() Symbols[k].closeSubpath()
tr = QtGui.QTransform()
tr.rotate(45)
Symbols['x'] = tr.map(Symbols['+'])
def drawSymbol(painter, symbol, size, pen, brush): def drawSymbol(painter, symbol, size, pen, brush):
@ -690,6 +693,7 @@ class ScatterPlotItem(GraphicsObject):
GraphicsObject.setExportMode(self, *args, **kwds) GraphicsObject.setExportMode(self, *args, **kwds)
self.invalidate() self.invalidate()
@pg.debug.warnOnException ## raising an exception here causes crash
def paint(self, p, *args): def paint(self, p, *args):
#p.setPen(fn.mkPen('r')) #p.setPen(fn.mkPen('r'))

View File

@ -141,6 +141,12 @@ class ViewBox(GraphicsWidget):
self.rbScaleBox.hide() self.rbScaleBox.hide()
self.addItem(self.rbScaleBox, ignoreBounds=True) self.addItem(self.rbScaleBox, ignoreBounds=True)
## show target rect for debugging
self.target = QtGui.QGraphicsRectItem(0, 0, 1, 1)
self.target.setPen(fn.mkPen('r'))
self.target.setParentItem(self)
self.target.hide()
self.axHistory = [] # maintain a history of zoom locations self.axHistory = [] # maintain a history of zoom locations
self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo"
@ -275,6 +281,9 @@ class ViewBox(GraphicsWidget):
""" """
if item.zValue() < self.zValue(): if item.zValue() < self.zValue():
item.setZValue(self.zValue()+1) item.setZValue(self.zValue()+1)
scene = self.scene()
if scene is not None and scene is not item.scene():
scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616
item.setParentItem(self.childGroup) item.setParentItem(self.childGroup)
if not ignoreBounds: if not ignoreBounds:
self.addedItems.append(item) self.addedItems.append(item)
@ -294,7 +303,7 @@ class ViewBox(GraphicsWidget):
for i in self.addedItems[:]: for i in self.addedItems[:]:
self.removeItem(i) self.removeItem(i)
for ch in self.childGroup.childItems(): for ch in self.childGroup.childItems():
ch.setParent(None) ch.setParentItem(None)
def resizeEvent(self, ev): def resizeEvent(self, ev):
#self.setRange(self.range, padding=0) #self.setRange(self.range, padding=0)
@ -389,11 +398,29 @@ class ViewBox(GraphicsWidget):
p = (mx-mn) * xpad p = (mx-mn) * xpad
mn -= p mn -= p
mx += p mx += p
if self.state['targetRange'][ax] != [mn, mx]: if self.state['targetRange'][ax] != [mn, mx]:
self.state['targetRange'][ax] = [mn, mx] self.state['targetRange'][ax] = [mn, mx]
changed[ax] = True changed[ax] = True
aspect = self.state['aspectLocked'] # size ratio / view ratio
if aspect is not False and len(changes) == 1:
## need to adjust orthogonal target range to match
size = [self.width(), self.height()]
tr1 = self.state['targetRange'][ax]
tr2 = self.state['targetRange'][1-ax]
if size[1] == 0 or aspect == 0:
ratio = 1.0
else:
ratio = (size[0] / float(size[1])) / aspect
if ax == 0:
ratio = 1.0 / ratio
w = (tr1[1]-tr1[0]) * ratio
d = 0.5 * (w - (tr2[1]-tr2[0]))
self.state['targetRange'][1-ax] = [tr2[0]-d, tr2[1]+d]
if any(changed) and disableAutoRange: if any(changed) and disableAutoRange:
if all(changed): if all(changed):
ax = ViewBox.XYAxes ax = ViewBox.XYAxes
@ -406,6 +433,8 @@ class ViewBox(GraphicsWidget):
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect()))
if update: if update:
self.updateMatrix(changed) self.updateMatrix(changed)
@ -494,7 +523,7 @@ class ViewBox(GraphicsWidget):
scale = Point(scale) scale = Point(scale)
if self.state['aspectLocked'] is not False: if self.state['aspectLocked'] is not False:
scale[0] = self.state['aspectLocked'] * scale[1] scale[0] = scale[1]
vr = self.targetRect() vr = self.targetRect()
if center is None: if center is None:
@ -707,6 +736,7 @@ class ViewBox(GraphicsWidget):
if self.autoRangeEnabled()[axis] is False: if self.autoRangeEnabled()[axis] is False:
slot() slot()
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
def blockLink(self, b): def blockLink(self, b):
@ -807,13 +837,17 @@ class ViewBox(GraphicsWidget):
""" """
If the aspect ratio is locked, view scaling must always preserve the aspect ratio. If the aspect ratio is locked, view scaling must always preserve the aspect ratio.
By default, the ratio is set to 1; x and y both have the same scaling. By default, the ratio is set to 1; x and y both have the same scaling.
This ratio can be overridden (width/height), or use None to lock in the current ratio. This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio.
""" """
if not lock: if not lock:
self.state['aspectLocked'] = False self.state['aspectLocked'] = False
else: else:
rect = self.rect()
vr = self.viewRect() vr = self.viewRect()
currentRatio = vr.width() / vr.height() if rect.height() == 0 or vr.width() == 0 or vr.height() == 0:
currentRatio = 1.0
else:
currentRatio = (rect.width()/float(rect.height())) / (vr.width()/vr.height())
if ratio is None: if ratio is None:
ratio = currentRatio ratio = currentRatio
self.state['aspectLocked'] = ratio self.state['aspectLocked'] = ratio
@ -1092,10 +1126,10 @@ class ViewBox(GraphicsWidget):
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding()
if xr is None or xr == (None, None) or np.isnan(xr).any() or np.isinf(xr).any(): if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any():
useX = False useX = False
xr = (0,0) xr = (0,0)
if yr is None or yr == (None, None) or np.isnan(yr).any() or np.isinf(yr).any(): if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any():
useY = False useY = False
yr = (0,0) yr = (0,0)
@ -1194,32 +1228,41 @@ class ViewBox(GraphicsWidget):
if changed is None: if changed is None:
changed = [False, False] changed = [False, False]
changed = list(changed) changed = list(changed)
#print "udpateMatrix:"
#print " range:", self.range
tr = self.targetRect() tr = self.targetRect()
bounds = self.rect() #boundingRect() bounds = self.rect()
#print bounds
## set viewRect, given targetRect and possibly aspect ratio constraint ## set viewRect, given targetRect and possibly aspect ratio constraint
if self.state['aspectLocked'] is False or bounds.height() == 0: aspect = self.state['aspectLocked']
if aspect is False or bounds.height() == 0:
self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] self.state['viewRange'] = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
else: else:
viewRatio = bounds.width() / bounds.height() ## aspect is (widget w/h) / (view range w/h)
targetRatio = self.state['aspectLocked'] * tr.width() / tr.height()
## This is the view range aspect ratio we have requested
targetRatio = tr.width() / tr.height()
## This is the view range aspect ratio we need to obey aspect constraint
viewRatio = (bounds.width() / bounds.height()) / aspect
if targetRatio > viewRatio: if targetRatio > viewRatio:
## target is wider than view ## view range needs to be taller than target
dy = 0.5 * (tr.width() / (self.state['aspectLocked'] * viewRatio) - tr.height()) dy = 0.5 * (tr.width() / viewRatio - tr.height())
if dy != 0: if dy != 0:
changed[1] = True changed[1] = True
self.state['viewRange'] = [self.state['targetRange'][0][:], [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]] self.state['viewRange'] = [
self.state['targetRange'][0][:],
[self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
]
else: else:
dx = 0.5 * (tr.height() * viewRatio * self.state['aspectLocked'] - tr.width()) ## view range needs to be wider than target
dx = 0.5 * (tr.height() * viewRatio - tr.width())
if dx != 0: if dx != 0:
changed[0] = True changed[0] = True
self.state['viewRange'] = [[self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx], self.state['targetRange'][1][:]] self.state['viewRange'] = [
[self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx],
self.state['targetRange'][1][:]
]
vr = self.viewRect() vr = self.viewRect()
#print " bounds:", bounds
if vr.height() == 0 or vr.width() == 0: if vr.height() == 0 or vr.width() == 0:
return return
scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height())
@ -1254,6 +1297,12 @@ class ViewBox(GraphicsWidget):
#p.fillRect(bounds, QtGui.QColor(0, 0, 0)) #p.fillRect(bounds, QtGui.QColor(0, 0, 0))
p.drawPath(bounds) p.drawPath(bounds)
#p.setPen(fn.mkPen('r'))
#path = QtGui.QPainterPath()
#path.addRect(self.targetRect())
#tr = self.mapFromView(path)
#p.drawPath(tr)
def updateBackground(self): def updateBackground(self):
bg = self.state['background'] bg = self.state['background']
if bg is None: if bg is None:

View File

@ -328,6 +328,9 @@ class MetaArray(object):
def __div__(self, b): def __div__(self, b):
return self._binop('__div__', b) return self._binop('__div__', b)
def __truediv__(self, b):
return self._binop('__truediv__', b)
def _binop(self, op, b): def _binop(self, op, b):
if isinstance(b, MetaArray): if isinstance(b, MetaArray):
b = b.asarray() b = b.asarray()

View File

@ -887,6 +887,12 @@ class ObjectProxy(object):
def __div__(self, *args): def __div__(self, *args):
return self._getSpecialAttr('__div__')(*args) return self._getSpecialAttr('__div__')(*args)
def __truediv__(self, *args):
return self._getSpecialAttr('__truediv__')(*args)
def __floordiv__(self, *args):
return self._getSpecialAttr('__floordiv__')(*args)
def __mul__(self, *args): def __mul__(self, *args):
return self._getSpecialAttr('__mul__')(*args) return self._getSpecialAttr('__mul__')(*args)
@ -902,6 +908,12 @@ class ObjectProxy(object):
def __idiv__(self, *args): def __idiv__(self, *args):
return self._getSpecialAttr('__idiv__')(*args, _callSync='off') return self._getSpecialAttr('__idiv__')(*args, _callSync='off')
def __itruediv__(self, *args):
return self._getSpecialAttr('__itruediv__')(*args, _callSync='off')
def __ifloordiv__(self, *args):
return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off')
def __imul__(self, *args): def __imul__(self, *args):
return self._getSpecialAttr('__imul__')(*args, _callSync='off') return self._getSpecialAttr('__imul__')(*args, _callSync='off')
@ -914,17 +926,11 @@ class ObjectProxy(object):
def __lshift__(self, *args): def __lshift__(self, *args):
return self._getSpecialAttr('__lshift__')(*args) return self._getSpecialAttr('__lshift__')(*args)
def __floordiv__(self, *args):
return self._getSpecialAttr('__pow__')(*args)
def __irshift__(self, *args): def __irshift__(self, *args):
return self._getSpecialAttr('__rshift__')(*args, _callSync='off') return self._getSpecialAttr('__irshift__')(*args, _callSync='off')
def __ilshift__(self, *args): def __ilshift__(self, *args):
return self._getSpecialAttr('__lshift__')(*args, _callSync='off') return self._getSpecialAttr('__ilshift__')(*args, _callSync='off')
def __ifloordiv__(self, *args):
return self._getSpecialAttr('__pow__')(*args, _callSync='off')
def __eq__(self, *args): def __eq__(self, *args):
return self._getSpecialAttr('__eq__')(*args) return self._getSpecialAttr('__eq__')(*args)
@ -974,6 +980,12 @@ class ObjectProxy(object):
def __rdiv__(self, *args): def __rdiv__(self, *args):
return self._getSpecialAttr('__rdiv__')(*args) return self._getSpecialAttr('__rdiv__')(*args)
def __rfloordiv__(self, *args):
return self._getSpecialAttr('__rfloordiv__')(*args)
def __rtruediv__(self, *args):
return self._getSpecialAttr('__rtruediv__')(*args)
def __rmul__(self, *args): def __rmul__(self, *args):
return self._getSpecialAttr('__rmul__')(*args) return self._getSpecialAttr('__rmul__')(*args)
@ -986,9 +998,6 @@ class ObjectProxy(object):
def __rlshift__(self, *args): def __rlshift__(self, *args):
return self._getSpecialAttr('__rlshift__')(*args) return self._getSpecialAttr('__rlshift__')(*args)
def __rfloordiv__(self, *args):
return self._getSpecialAttr('__rpow__')(*args)
def __rand__(self, *args): def __rand__(self, *args):
return self._getSpecialAttr('__rand__')(*args) return self._getSpecialAttr('__rand__')(*args)

View File

@ -190,10 +190,15 @@ class ScatterPlotWidget(QtGui.QSplitter):
for ax in [0,1]: for ax in [0,1]:
if not enum[ax]: if not enum[ax]:
continue continue
for i in range(int(xy[ax].max())+1): imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0
for i in range(imax+1):
keymask = xy[ax] == i keymask = xy[ax] == i
scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True) scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True)
scatter *= 0.2 / np.abs(scatter).max() if len(scatter) == 0:
continue
smax = np.abs(scatter).max()
if smax != 0:
scatter *= 0.2 / smax
xy[ax][keymask] += scatter xy[ax][keymask] += scatter
if self.scatterPlot is not None: if self.scatterPlot is not None: