diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py
index ea35d119601752878aadad17e9a5799b7cb29f56..682f19f783153078e8f942fb5bc40a419bab7b5d 100644
--- a/pyqtgraph/Point.py
+++ b/pyqtgraph/Point.py
@@ -80,6 +80,12 @@ class Point(QtCore.QPointF):
     def __div__(self, 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):
         return self._math_('__rpow__', a)
     
diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py
index a861f9408778af43441c324a25f055b4fd9c5f1a..efb24f60b9dcf1a9b0114b70beea2834fa0c7ac8 100644
--- a/pyqtgraph/SRTTransform.py
+++ b/pyqtgraph/SRTTransform.py
@@ -130,11 +130,14 @@ class SRTTransform(QtGui.QTransform):
         self._state['angle'] = angle
         self.update()
 
-    def __div__(self, t):
+    def __truediv__(self, t):
         """A / B  ==  B^-1 * A"""
         dt = t.inverted()[0] * self
         return SRTTransform(dt)
         
+    def __div__(self, t):
+        return self.__truediv__(t)
+        
     def __mul__(self, t):
         return SRTTransform(QtGui.QTransform.__mul__(self, t))
 
diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py
index 77583b5a3fc3c0db5ce4e727780f62230873681d..7d87dcb86ccee8676156f4ccad6594d53a956b3a 100644
--- a/pyqtgraph/SRTTransform3D.py
+++ b/pyqtgraph/SRTTransform3D.py
@@ -123,7 +123,6 @@ class SRTTransform3D(pg.Transform3D):
         m = self.matrix().reshape(4,4)
         ## translation is 4th column
         self._state['pos'] = m[:3,3] 
-        
         ## scale is vector-length of first three columns
         scale = (m[:3,:3]**2).sum(axis=0)**0.5
         ## see whether there is an inversion
@@ -141,18 +140,30 @@ class SRTTransform3D(pg.Transform3D):
             print("Scale: %s" % str(scale))
             print("Original matrix: %s" % str(m))
             raise
-        eigIndex = np.argwhere(np.abs(evals-1) < 1e-7)
+        eigIndex = np.argwhere(np.abs(evals-1) < 1e-6)
         if len(eigIndex) < 1:
             print("eigenvalues: %s" % str(evals))
             print("eigenvectors: %s" % str(evecs))
             print("index: %s, %s" % (str(eigIndex), str(evals-1)))
             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
         self._state['axis'] = axis
         
         ## 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:
             self._state['axis'] = (0,0,1)
         
diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py
index ae2b21ac7670cd98af92aa4e546581774375e2b9..a175be9ca2964c98ddf813afacfa7fdffada9c92 100644
--- a/pyqtgraph/debug.py
+++ b/pyqtgraph/debug.py
@@ -28,6 +28,15 @@ def ftrace(func):
         return rv
     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='|  '):
     tb = traceback.format_exc()
     lines = []
diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py
index 412af5736de37571fa6abc79c5e5c88c448a838b..579d2cd2b7c7849f0b8c5eaf258cab1048a94385 100644
--- a/pyqtgraph/flowchart/library/Operators.py
+++ b/pyqtgraph/flowchart/library/Operators.py
@@ -24,7 +24,15 @@ class BinOpNode(Node):
         })
         
     def process(self, **args):
-        fn = getattr(args['A'], self.fn)
+        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)
         out = fn(args['B'])
         if out is NotImplemented:
             raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B']))))
@@ -60,5 +68,7 @@ class DivideNode(BinOpNode):
     """Returns A / B. Does not check input types."""
     nodeName = 'Divide'
     def __init__(self, name):
-        BinOpNode.__init__(self, name, '__div__')
+        # try truediv first, followed by div
+        BinOpNode.__init__(self, name, ('__truediv__', '__div__'))
+        
 
diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py
index a9cf26939d657cf5e5e9f1cf3f319fada3634015..5a78616d3c3096585a7d4d545aad09010556adf5 100644
--- a/pyqtgraph/functions.py
+++ b/pyqtgraph/functions.py
@@ -264,6 +264,7 @@ def mkPen(*args, **kargs):
     color = kargs.get('color', None)
     width = kargs.get('width', 1)
     style = kargs.get('style', None)
+    dash = kargs.get('dash', None)
     cosmetic = kargs.get('cosmetic', True)
     hsv = kargs.get('hsv', None)
     
@@ -291,6 +292,8 @@ def mkPen(*args, **kargs):
     pen.setCosmetic(cosmetic)
     if style is not None:
         pen.setStyle(style)
+    if dash is not None:
+        pen.setDashPattern(dash)
     return pen
 
 def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0):
@@ -1966,6 +1969,8 @@ def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
     s2 = spacing**2
     
     yvals = np.empty(len(data))
+    if len(data) == 0:
+        return yvals
     yvals[0] = 0
     for i in range(1,len(data)):
         x = data[i]     # current x value to be placed
diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py
index 846f48ac21066f2e393be483f97d128dcfccc8a6..d82f5d41239632ae971c591f9dfc7c2629e51fc6 100644
--- a/pyqtgraph/graphicsItems/AxisItem.py
+++ b/pyqtgraph/graphicsItems/AxisItem.py
@@ -42,12 +42,18 @@ class AxisItem(GraphicsWidget):
             self.label.rotate(-90)
             
         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
             'tickTextHeight': 18, 
             'autoExpandTextSpace': True,  ## automatically expand text space if needed
             'tickFont': None,
             '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 
@@ -209,14 +215,14 @@ class AxisItem(GraphicsWidget):
         ## to accomodate.
         if self.orientation in ['left', 'right']:
             mx = max(self.textWidth, x)
-            if mx > self.textWidth:
+            if mx > self.textWidth or mx < self.textWidth-10:
                 self.textWidth = mx
                 if self.style['autoExpandTextSpace'] is True:
                     self.setWidth()
                     #return True  ## size has changed
         else:
             mx = max(self.textHeight, x)
-            if mx > self.textHeight:
+            if mx > self.textHeight or mx < self.textHeight-10:
                 self.textHeight = mx
                 if self.style['autoExpandTextSpace'] is True:
                     self.setHeight()
@@ -236,7 +242,7 @@ class AxisItem(GraphicsWidget):
                 h = self.textHeight
             else:
                 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():
                 h += self.label.boundingRect().height() * 0.8
         self.setMaximumHeight(h)
@@ -252,7 +258,7 @@ class AxisItem(GraphicsWidget):
                 w = self.textWidth
             else:
                 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():
                 w += self.label.boundingRect().height() * 0.8  ## bounding rect is usually an overestimate
         self.setMaximumWidth(w)
@@ -431,7 +437,7 @@ class AxisItem(GraphicsWidget):
             return []
         
         ## 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)
         
         ## optimal minor tick spacing 
@@ -734,7 +740,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:
             #textWidth = self.textWidth
             #textHeight = self.textHeight
@@ -742,7 +748,7 @@ class AxisItem(GraphicsWidget):
             #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text
             #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text
             
-        
+        textSize2 = 0
         textRects = []
         textSpecs = []  ## list of draw
         textSize2 = 0
@@ -785,9 +791,16 @@ class AxisItem(GraphicsWidget):
                     textSize = np.sum([r.width() 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
-                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
             
             #spacing, values = tickLevels[best]
diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/pyqtgraph/graphicsItems/GraphicsItem.py
index 40ff6bc58ca669c701fc1b7bcc461fd7fdf5aaeb..a129436ea871093279e18ec83ff13fc93c6545f6 100644
--- a/pyqtgraph/graphicsItems/GraphicsItem.py
+++ b/pyqtgraph/graphicsItems/GraphicsItem.py
@@ -533,6 +533,7 @@ class GraphicsItem(object):
     def viewTransformChanged(self):
         """
         Called whenever the transformation matrix of the view has changed.
+        (eg, the view range has changed or the view was resized)
         """
         pass
     
diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py
index ebcd0d388225987ddc71d4cd2eef9a49a5e2464a..881dcf2d333c0114de4aa6c17ccbe111e876997f 100644
--- a/pyqtgraph/graphicsItems/PlotCurveItem.py
+++ b/pyqtgraph/graphicsItems/PlotCurveItem.py
@@ -375,6 +375,7 @@ class PlotCurveItem(GraphicsObject):
                 return QtGui.QPainterPath()
         return self.path
 
+    @pg.debug.warnOnException  ## raising an exception here causes crash
     def paint(self, p, opt, widget):
         prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
         if self.xData is None:
diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py
index 76b743592ba4c5c65f9ef788f37be827854bcf6a..1ae528ba2f4609085ca0d6c854a7bcb415c86e7c 100644
--- a/pyqtgraph/graphicsItems/PlotDataItem.py
+++ b/pyqtgraph/graphicsItems/PlotDataItem.py
@@ -84,24 +84,28 @@ class PlotDataItem(GraphicsObject):
         
         **Optimization keyword arguments:**
         
-            ============ =====================================================================
-            antialias    (bool) By default, antialiasing is disabled to improve performance.
-                         Note that in some cases (in particluar, when pxMode=True), points 
-                         will be rendered antialiased even if this is set to False.
-            decimate     (int) Sub-sample data by selecting every nth sample before plotting
-            onlyVisible  (bool) If True, only plot data that is visible within the X range of
-                         the containing ViewBox. This can improve performance when plotting
-                         very large data sets where only a fraction of the data is visible
-                         at any time.
-            autoResample (bool) If True, resample the data before plotting to avoid plotting
-                         multiple line segments per pixel. This can improve performance when
-                         viewing very high-density data, but increases the initial overhead 
-                         and memory usage.
-            sampleRate   (float) The sample rate of the data along the X axis (for data with
-                         a fixed sample rate). Providing this value improves performance of
-                         the *onlyVisible* and *autoResample* options.
-            identical    *deprecated*
-            ============ =====================================================================
+            ================ =====================================================================
+            antialias        (bool) By default, antialiasing is disabled to improve performance.
+                             Note that in some cases (in particluar, when pxMode=True), points 
+                             will be rendered antialiased even if this is set to False.
+            decimate         deprecated.
+            downsample       (int) Reduce the number of samples displayed by this value
+            downsampleMethod '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.
+            autoDownsample   (bool) If True, resample the data before plotting to avoid plotting
+                             multiple line segments per pixel. This can improve performance when
+                             viewing very high-density data, but increases the initial overhead 
+                             and memory usage.
+            clipToView       (bool) If True, only plot data that is visible within the X range of
+                             the containing ViewBox. This can improve performance when plotting
+                             very large data sets where only a fraction of the data is visible
+                             at any time.
+            identical        *deprecated*
+            ================ =====================================================================
         
         **Meta-info keyword arguments:**
         
@@ -131,7 +135,6 @@ class PlotDataItem(GraphicsObject):
         self.opts = {
             'fftMode': False,
             'logMode': [False, False],
-            'downsample': False,
             'alphaHint': 1.0,
             'alphaMode': False,
             
@@ -149,6 +152,11 @@ class PlotDataItem(GraphicsObject):
             'antialias': pg.getConfigOption('antialias'),
             'pointMode': None,
             
+            'downsample': 1,
+            'autoDownsample': False,
+            'downsampleMethod': 'peak',
+            'clipToView': False,
+            
             'data': None,
         }
         self.setData(*args, **kargs)
@@ -175,6 +183,7 @@ class PlotDataItem(GraphicsObject):
             return
         self.opts['fftMode'] = mode
         self.xDisp = self.yDisp = None
+        self.xClean = self.yClean = None
         self.updateItems()
         self.informViewBoundsChanged()
     
@@ -183,6 +192,7 @@ class PlotDataItem(GraphicsObject):
             return
         self.opts['logMode'] = [xMode, yMode]
         self.xDisp = self.yDisp = None
+        self.xClean = self.yClean = None
         self.updateItems()
         self.informViewBoundsChanged()
     
@@ -269,13 +279,51 @@ class PlotDataItem(GraphicsObject):
         #self.scatter.setSymbolSize(symbolSize)
         self.updateItems()
 
-    def setDownsampling(self, ds):
-        if self.opts['downsample'] == ds:
+    def setDownsampling(self, ds=None, auto=None, method=None):
+        """
+        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
+                
+        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.updateItems()
+        
+    def setClipToView(self, clip):
+        if self.opts['clipToView'] == clip:
             return
-        self.opts['downsample'] = ds
+        self.opts['clipToView'] = clip
         self.xDisp = self.yDisp = None
         self.updateItems()
         
+        
     def setData(self, *args, **kargs):
         """
         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))
             
         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:
                 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):
@@ -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.yData = y.view(np.ndarray)
+        self.xClean = self.yClean = None
         self.xDisp = None
         self.yDisp = None
         prof.mark('set data')
@@ -423,23 +472,28 @@ class PlotDataItem(GraphicsObject):
     def getData(self):
         if self.xData is 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)
             if any(nanMask):
                 self.dataMask = ~nanMask
-                x = self.xData[self.dataMask]
-                y = self.yData[self.dataMask]
+                self.xClean = self.xData[self.dataMask]
+                self.yClean = self.yData[self.dataMask]
             else:
                 self.dataMask = None
-                x = self.xData
-                y = self.yData
-                
+                self.xClean = self.xData
+                self.yClean = self.yData
             
-            ds = self.opts['downsample']
-            if ds > 1:
-                x = x[::ds]
-                #y = resample(y[:len(x)*ds], len(x))  ## scipy.signal.resample causes nasty ringing
-                y = y[::ds]
+        if self.xDisp is None:
+            x = self.xClean
+            y = self.yClean
+            
+            
+            #ds = self.opts['downsample']
+            #if isinstance(ds, int) and ds > 1:
+                #x = x[::ds]
+                ##y = resample(y[:len(x)*ds], len(x))  ## scipy.signal.resample causes nasty ringing
+                #y = y[::ds]
             if self.opts['fftMode']:
                 f = np.fft.fft(y) / len(y)
                 y = abs(f[1:len(f)/2])
@@ -457,6 +511,53 @@ class PlotDataItem(GraphicsObject):
                     y = y[self.dataMask]
                 else:
                     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.yDisp = y
         #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
@@ -542,6 +643,8 @@ class PlotDataItem(GraphicsObject):
         #self.scatters = []
         self.xData = None
         self.yData = None
+        self.xClean = None
+        self.yClean = None
         self.xDisp = None
         self.yDisp = None
         self.curve.setData([])
@@ -557,6 +660,14 @@ class PlotDataItem(GraphicsObject):
         self.sigClicked.emit(self)
         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):
     if hasattr(obj, '__len__') and len(obj) == 0:
diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
index 52a1429b527e4fbee1c3b9ec2679bfa2d8108df4..ec0960ba3ab8a6aeabb23fcb2c5c195a448b2c41 100644
--- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
+++ b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
@@ -256,6 +256,11 @@ class PlotItem(GraphicsWidget):
         c.logYCheck.toggled.connect(self.updateLogMode)
 
         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.averageGroup.toggled.connect(self.avgToggled)
@@ -528,7 +533,8 @@ class PlotItem(GraphicsWidget):
             (alpha, auto) = self.alphaState()
             item.setAlpha(alpha, auto)
             item.setFftMode(self.ctrl.fftCheck.isChecked())
-            item.setDownsampling(self.downsampleMode())
+            item.setDownsampling(*self.downsampleMode())
+            item.setClipToView(self.clipToViewMode())
             item.setPointMode(self.pointMode())
             
             ## Hide older plots if needed
@@ -570,8 +576,8 @@ class PlotItem(GraphicsWidget):
         :func:`InfiniteLine.__init__() <pyqtgraph.InfiniteLine.__init__>`.
         Returns the item created.
         """
-        angle = 0 if x is None else 90
-        pos = x if x is not None else y
+        pos = kwds.get('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)
         self.addItem(line)
         if z is not None:
@@ -943,23 +949,81 @@ class PlotItem(GraphicsWidget):
         self.enableAutoRange()
         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):
-        ds = self.downsampleMode()
+        ds, auto, method = self.downsampleMode()
+        clip = self.ctrl.clipToViewCheck.isChecked()
         for c in self.curves:
-            c.setDownsampling(ds)
+            c.setDownsampling(ds, auto, method)
+            c.setClipToView(clip)
         self.recomputeAverages()
         
-        
     def downsampleMode(self):
-        if self.ctrl.decimateGroup.isChecked():
-            if self.ctrl.manualDecimateRadio.isChecked():
-                ds = self.ctrl.downsampleSpin.value()
-            else:
-                ds = True
+        if self.ctrl.downsampleCheck.isChecked():
+            ds = self.ctrl.downsampleSpin.value()
         else:
-            ds = False
-        return ds
+            ds = 1
+            
+        auto = self.ctrl.downsampleCheck.isChecked() and self.ctrl.autoDownsampleCheck.isChecked()
+            
+        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):
         if self.ctrl.maxTracesCheck.isChecked():
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
index 516ec7217e82906481880d9fdb3b364d078628f0..dffc62d04f9c993a5b6b2393833c4dd18153ab24 100644
--- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
+++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>258</width>
-    <height>605</height>
+    <width>481</width>
+    <height>840</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -16,8 +16,8 @@
   <widget class="QGroupBox" name="averageGroup">
    <property name="geometry">
     <rect>
-     <x>10</x>
-     <y>200</y>
+     <x>0</x>
+     <y>640</y>
      <width>242</width>
      <height>182</height>
     </rect>
@@ -46,21 +46,15 @@
     </item>
    </layout>
   </widget>
-  <widget class="QGroupBox" name="decimateGroup">
+  <widget class="QFrame" name="decimateGroup">
    <property name="geometry">
     <rect>
-     <x>0</x>
-     <y>70</y>
-     <width>242</width>
-     <height>160</height>
+     <x>10</x>
+     <y>140</y>
+     <width>191</width>
+     <height>171</height>
     </rect>
    </property>
-   <property name="title">
-    <string>Downsample</string>
-   </property>
-   <property name="checkable">
-    <bool>true</bool>
-   </property>
    <layout class="QGridLayout" name="gridLayout_4">
     <property name="margin">
      <number>0</number>
@@ -68,57 +62,54 @@
     <property name="spacing">
      <number>0</number>
     </property>
-    <item row="0" column="0">
-     <widget class="QRadioButton" name="manualDecimateRadio">
-      <property name="text">
-       <string>Manual</string>
+    <item row="7" column="0" colspan="3">
+     <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="checked">
-       <bool>true</bool>
+      <property name="text">
+       <string>Clip to View</string>
       </property>
      </widget>
     </item>
-    <item row="0" column="1">
-     <widget class="QSpinBox" name="downsampleSpin">
-      <property name="minimum">
-       <number>1</number>
-      </property>
-      <property name="maximum">
-       <number>100000</number>
+    <item row="8" column="0" colspan="2">
+     <widget class="QCheckBox" name="maxTracesCheck">
+      <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>
       </property>
-      <property name="value">
-       <number>1</number>
+      <property name="text">
+       <string>Max Traces:</string>
       </property>
      </widget>
     </item>
-    <item row="1" column="0">
-     <widget class="QRadioButton" name="autoDecimateRadio">
+    <item row="0" column="0" colspan="3">
+     <widget class="QCheckBox" name="downsampleCheck">
       <property name="text">
-       <string>Auto</string>
-      </property>
-      <property name="checked">
-       <bool>false</bool>
+       <string>Downsample</string>
       </property>
      </widget>
     </item>
-    <item row="2" column="0">
-     <widget class="QCheckBox" name="maxTracesCheck">
+    <item row="6" column="1" colspan="2">
+     <widget class="QRadioButton" name="peakRadio">
       <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>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>Max Traces:</string>
+       <string>Peak</string>
+      </property>
+      <property name="checked">
+       <bool>true</bool>
       </property>
      </widget>
     </item>
-    <item row="2" column="1">
+    <item row="8" column="2">
      <widget class="QSpinBox" name="maxTracesSpin">
       <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>
       </property>
      </widget>
     </item>
-    <item row="3" column="0" colspan="2">
+    <item row="9" column="0" colspan="3">
      <widget class="QCheckBox" name="forgetTracesCheck">
       <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>
@@ -128,6 +119,74 @@
       </property>
      </widget>
     </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>
   </widget>
   <widget class="QFrame" name="transformGroup">
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
index d34cd2974fc45802e758c54a93dc770c81e61e86..5335ee76789af8690f3fa44741ab88691005abbe 100644
--- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
+++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
@@ -1,9 +1,9 @@
 # -*- 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
-#      by: PyQt4 UI code generator 4.9.1
+# Created: Mon Jul  1 23:21:08 2013
+#      by: PyQt4 UI code generator 4.9.3
 #
 # WARNING! All changes made in this file will be lost!
 
@@ -17,9 +17,9 @@ except AttributeError:
 class Ui_Form(object):
     def setupUi(self, Form):
         Form.setObjectName(_fromUtf8("Form"))
-        Form.resize(258, 605)
+        Form.resize(481, 840)
         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.setChecked(False)
         self.averageGroup.setObjectName(_fromUtf8("averageGroup"))
@@ -30,37 +30,50 @@ class Ui_Form(object):
         self.avgParamList = QtGui.QListWidget(self.averageGroup)
         self.avgParamList.setObjectName(_fromUtf8("avgParamList"))
         self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1)
-        self.decimateGroup = QtGui.QGroupBox(Form)
-        self.decimateGroup.setGeometry(QtCore.QRect(0, 70, 242, 160))
-        self.decimateGroup.setCheckable(True)
+        self.decimateGroup = QtGui.QFrame(Form)
+        self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171))
         self.decimateGroup.setObjectName(_fromUtf8("decimateGroup"))
         self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup)
         self.gridLayout_4.setMargin(0)
         self.gridLayout_4.setSpacing(0)
         self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4"))
-        self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup)
-        self.manualDecimateRadio.setChecked(True)
-        self.manualDecimateRadio.setObjectName(_fromUtf8("manualDecimateRadio"))
-        self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1)
-        self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup)
-        self.downsampleSpin.setMinimum(1)
-        self.downsampleSpin.setMaximum(100000)
-        self.downsampleSpin.setProperty("value", 1)
-        self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin"))
-        self.gridLayout_4.addWidget(self.downsampleSpin, 0, 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.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup)
+        self.clipToViewCheck.setObjectName(_fromUtf8("clipToViewCheck"))
+        self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3)
         self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
         self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck"))
-        self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1)
+        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, 2, 1, 1, 1)
+        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, 3, 0, 1, 2)
+        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.setMinimum(1)
+        self.downsampleSpin.setMaximum(100000)
+        self.downsampleSpin.setProperty("value", 1)
+        self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin"))
+        self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1)
         self.transformGroup = QtGui.QFrame(Form)
         self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79))
         self.transformGroup.setObjectName(_fromUtf8("transformGroup"))
@@ -129,14 +142,24 @@ class Ui_Form(object):
         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.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
-        self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8))
-        self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8))
-        self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", 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.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", 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.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.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.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.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))
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
index 85b563a70080bacda5f965e463c2a962d81286e9..b8e0b19e396c4afc0773ca1e84bdcd58a1876e91 100644
--- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
+++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
@@ -1,9 +1,9 @@
 # -*- 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
-#      by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Jul  1 23:21:08 2013
+#      by: pyside-uic 0.2.13 running on PySide 1.1.2
 #
 # WARNING! All changes made in this file will be lost!
 
@@ -12,9 +12,9 @@ from PySide import QtCore, QtGui
 class Ui_Form(object):
     def setupUi(self, Form):
         Form.setObjectName("Form")
-        Form.resize(258, 605)
+        Form.resize(481, 840)
         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.setChecked(False)
         self.averageGroup.setObjectName("averageGroup")
@@ -25,37 +25,50 @@ class Ui_Form(object):
         self.avgParamList = QtGui.QListWidget(self.averageGroup)
         self.avgParamList.setObjectName("avgParamList")
         self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1)
-        self.decimateGroup = QtGui.QGroupBox(Form)
-        self.decimateGroup.setGeometry(QtCore.QRect(0, 70, 242, 160))
-        self.decimateGroup.setCheckable(True)
+        self.decimateGroup = QtGui.QFrame(Form)
+        self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171))
         self.decimateGroup.setObjectName("decimateGroup")
         self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup)
         self.gridLayout_4.setContentsMargins(0, 0, 0, 0)
         self.gridLayout_4.setSpacing(0)
         self.gridLayout_4.setObjectName("gridLayout_4")
-        self.manualDecimateRadio = QtGui.QRadioButton(self.decimateGroup)
-        self.manualDecimateRadio.setChecked(True)
-        self.manualDecimateRadio.setObjectName("manualDecimateRadio")
-        self.gridLayout_4.addWidget(self.manualDecimateRadio, 0, 0, 1, 1)
-        self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup)
-        self.downsampleSpin.setMinimum(1)
-        self.downsampleSpin.setMaximum(100000)
-        self.downsampleSpin.setProperty("value", 1)
-        self.downsampleSpin.setObjectName("downsampleSpin")
-        self.gridLayout_4.addWidget(self.downsampleSpin, 0, 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.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup)
+        self.clipToViewCheck.setObjectName("clipToViewCheck")
+        self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3)
         self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup)
         self.maxTracesCheck.setObjectName("maxTracesCheck")
-        self.gridLayout_4.addWidget(self.maxTracesCheck, 2, 0, 1, 1)
+        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, 2, 1, 1, 1)
+        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, 3, 0, 1, 2)
+        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.setMinimum(1)
+        self.downsampleSpin.setMaximum(100000)
+        self.downsampleSpin.setProperty("value", 1)
+        self.downsampleSpin.setObjectName("downsampleSpin")
+        self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1)
         self.transformGroup = QtGui.QFrame(Form)
         self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79))
         self.transformGroup.setObjectName("transformGroup")
@@ -124,14 +137,24 @@ class Ui_Form(object):
         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.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
-        self.decimateGroup.setTitle(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8))
-        self.manualDecimateRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8))
-        self.autoDecimateRadio.setText(QtGui.QApplication.translate("Form", "Auto", 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.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", 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.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.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.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.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))
diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py
index c83249014f3eaadc4a4d9d377a9a3f6fa4f9287a..3070d15a22ce9e8db7f7f712404f7e4283fea987 100644
--- a/pyqtgraph/graphicsItems/ScatterPlotItem.py
+++ b/pyqtgraph/graphicsItems/ScatterPlotItem.py
@@ -15,7 +15,7 @@ __all__ = ['ScatterPlotItem', 'SpotItem']
 
 
 ## 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['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1))
 coords = {
@@ -32,6 +32,9 @@ for k, c in coords.items():
     for x,y in c[1:]:
         Symbols[k].lineTo(x, y)
     Symbols[k].closeSubpath()
+tr = QtGui.QTransform()
+tr.rotate(45)
+Symbols['x'] = tr.map(Symbols['+'])
 
     
 def drawSymbol(painter, symbol, size, pen, brush):
@@ -683,7 +686,8 @@ class ScatterPlotItem(GraphicsObject):
     def setExportMode(self, *args, **kwds):
         GraphicsObject.setExportMode(self, *args, **kwds)
         self.invalidate()
-            
+        
+    @pg.debug.warnOnException  ## raising an exception here causes crash
     def paint(self, p, *args):
 
         #p.setPen(fn.mkPen('r'))
diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
index 9edca06f9bf71059b643012294e22aef2bf7d60f..ea04bb16638b8bac8ff925a9115ed96ce8ece03b 100644
--- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
+++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
@@ -142,6 +142,12 @@ class ViewBox(GraphicsWidget):
         self.rbScaleBox.hide()
         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.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo"
         
@@ -276,6 +282,9 @@ class ViewBox(GraphicsWidget):
         """
         if item.zValue() < self.zValue():
             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)
         if not ignoreBounds:
             self.addedItems.append(item)
@@ -390,10 +399,28 @@ class ViewBox(GraphicsWidget):
             p = (mx-mn) * xpad
             mn -= p
             mx += p
-            
             if self.state['targetRange'][ax] != [mn, mx]:
                 self.state['targetRange'][ax] = [mn, mx]
                 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 all(changed):
@@ -407,6 +434,8 @@ class ViewBox(GraphicsWidget):
                 
         self.sigStateChanged.emit(self)
         
+        self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect()))
+        
         if update and (any(changed) or self.matrixNeedsUpdate):
             self.updateMatrix(changed)
         
@@ -498,7 +527,7 @@ class ViewBox(GraphicsWidget):
         scale = Point(scale)
             
         if self.state['aspectLocked'] is not False:
-            scale[0] = self.state['aspectLocked'] * scale[1]
+            scale[0] = scale[1]
 
         vr = self.targetRect()
         if center is None:
@@ -710,6 +739,7 @@ class ViewBox(GraphicsWidget):
             else:
                 if self.autoRangeEnabled()[axis] is False:
                     slot()
+        
             
         self.sigStateChanged.emit(self)
         
@@ -811,13 +841,17 @@ class ViewBox(GraphicsWidget):
         """
         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.
-        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:
             self.state['aspectLocked'] = False
         else:
+            rect = self.rect()
             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:
                 ratio = currentRatio
             self.state['aspectLocked'] = ratio
@@ -1096,10 +1130,10 @@ class ViewBox(GraphicsWidget):
                 xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
                 yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
                 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
                     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
                     yr = (0,0)
 
@@ -1198,32 +1232,41 @@ class ViewBox(GraphicsWidget):
         if changed is None:
             changed = [False, False]
         changed = list(changed)
-        #print "udpateMatrix:"
-        #print "  range:", self.range
         tr = self.targetRect()
-        bounds = self.rect() #boundingRect()
-        #print bounds
+        bounds = self.rect()
         
         ## 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][:]]
         else:
-            viewRatio = bounds.width() / bounds.height()
-            targetRatio = self.state['aspectLocked'] * tr.width() / tr.height()
+            ## aspect is (widget w/h) / (view range w/h)
+            
+            ## 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:  
-                ## target is wider than view
-                dy = 0.5 * (tr.width() / (self.state['aspectLocked'] * viewRatio) - tr.height())
+                ## view range needs to be taller than target
+                dy = 0.5 * (tr.width() / viewRatio - tr.height())
                 if dy != 0:
                     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:
-                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:
                     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()
-        #print "  bounds:", bounds
         if vr.height() == 0 or vr.width() == 0:
             return
         scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height())
@@ -1258,6 +1301,12 @@ class ViewBox(GraphicsWidget):
             p.setPen(self.border)
             #p.fillRect(bounds, QtGui.QColor(0, 0, 0))
             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):
         bg = self.state['background']
diff --git a/pyqtgraph/metaarray/MetaArray.py b/pyqtgraph/metaarray/MetaArray.py
index 0797c75e2bc4830ad73e0e260c39dcf0b418da10..f55c60dc2cd78ec8b3acb1884bb58537d0ec1363 100644
--- a/pyqtgraph/metaarray/MetaArray.py
+++ b/pyqtgraph/metaarray/MetaArray.py
@@ -328,6 +328,9 @@ class MetaArray(object):
     def __div__(self, b):
         return self._binop('__div__', b)
         
+    def __truediv__(self, b):
+        return self._binop('__truediv__', b)
+        
     def _binop(self, op, b):
         if isinstance(b, MetaArray):
             b = b.asarray()
diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py
index 974e1e95c47f9c5f582b2d23dd59dc725929f97f..f33ebc8308174e3fa2d6a5af72b972fc9ba7ebe4 100644
--- a/pyqtgraph/multiprocess/remoteproxy.py
+++ b/pyqtgraph/multiprocess/remoteproxy.py
@@ -887,6 +887,12 @@ class ObjectProxy(object):
     def __div__(self, *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):
         return self._getSpecialAttr('__mul__')(*args)
         
@@ -902,6 +908,12 @@ class ObjectProxy(object):
     def __idiv__(self, *args):
         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):
         return self._getSpecialAttr('__imul__')(*args, _callSync='off')
         
@@ -914,17 +926,11 @@ class ObjectProxy(object):
     def __lshift__(self, *args):
         return self._getSpecialAttr('__lshift__')(*args)
         
-    def __floordiv__(self, *args):
-        return self._getSpecialAttr('__pow__')(*args)
-        
     def __irshift__(self, *args):
-        return self._getSpecialAttr('__rshift__')(*args, _callSync='off')
+        return self._getSpecialAttr('__irshift__')(*args, _callSync='off')
         
     def __ilshift__(self, *args):
-        return self._getSpecialAttr('__lshift__')(*args, _callSync='off')
-        
-    def __ifloordiv__(self, *args):
-        return self._getSpecialAttr('__pow__')(*args, _callSync='off')
+        return self._getSpecialAttr('__ilshift__')(*args, _callSync='off')
         
     def __eq__(self, *args):
         return self._getSpecialAttr('__eq__')(*args)
@@ -974,6 +980,12 @@ class ObjectProxy(object):
     def __rdiv__(self, *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):
         return self._getSpecialAttr('__rmul__')(*args)
         
@@ -986,9 +998,6 @@ class ObjectProxy(object):
     def __rlshift__(self, *args):
         return self._getSpecialAttr('__rlshift__')(*args)
         
-    def __rfloordiv__(self, *args):
-        return self._getSpecialAttr('__rpow__')(*args)
-        
     def __rand__(self, *args):
         return self._getSpecialAttr('__rand__')(*args)
         
diff --git a/pyqtgraph/widgets/ScatterPlotWidget.py b/pyqtgraph/widgets/ScatterPlotWidget.py
index fe785e045867c55a9bef4e0fec04605c1109e0a6..e9e24dd79d29fcdab986d86ff774ecd15d435b42 100644
--- a/pyqtgraph/widgets/ScatterPlotWidget.py
+++ b/pyqtgraph/widgets/ScatterPlotWidget.py
@@ -190,10 +190,15 @@ class ScatterPlotWidget(QtGui.QSplitter):
             for ax in [0,1]:
                 if not enum[ax]:
                     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
                     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
         
         if self.scatterPlot is not None: