diff --git a/graphicsItems/GraphicsItem.py b/graphicsItems/GraphicsItem.py
index 0b902c58d285e95695e9992aef58bef637fa7bd1..838fb76dd31491bac9f88488c4883d03c5962340 100644
--- a/graphicsItems/GraphicsItem.py
+++ b/graphicsItems/GraphicsItem.py
@@ -13,6 +13,7 @@ class GraphicsItem(object):
     def __init__(self, register=True):
         self._viewWidget = None
         self._viewBox = None
+        self._connectedView = None
         if register:
             GraphicsScene.registerObject(self)  ## workaround for pyqt bug in graphicsscene.items()
     
@@ -132,12 +133,19 @@ class GraphicsItem(object):
         return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig
         
     def pixelLength(self, direction):
-        """Return the length of one pixel in the direction indicated (in local coordinates)"""
+        """
+        Return the length of one pixel in the direction indicated (in local coordinates)
+        If the result would be infinite (this happens if the device transform is not properly configured yet),
+        then return None instead.
+        """
         dt = self.deviceTransform()
         if dt is None:
             return None
         viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
-        norm = viewDir.norm()
+        try:
+            norm = viewDir.norm()
+        except ZeroDivisionError:
+            return None
         dti = dt.inverted()[0]
         return Point(dti.map(norm)-dti.map(Point(0,0))).length()
         
@@ -235,23 +243,6 @@ class GraphicsItem(object):
     def viewPos(self):
         return self.mapToView(self.mapFromParent(self.pos()))
     
-    #def itemChange(self, change, value):
-        #ret = QtGui.QGraphicsObject.itemChange(self, change, value)
-        #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged:
-            #print "Item scene changed:", self
-            #self.setChildScene(self)  ## This is bizarre. 
-        #return ret
-    
-    #def setChildScene(self, ch):
-        #scene = self.scene()
-        #for ch2 in ch.childItems():
-            #if ch2.scene() is not scene:
-                #print "item", ch2, "has different scene:", ch2.scene(), scene
-                #scene.addItem(ch2)
-                #QtGui.QApplication.processEvents()
-                #print "   --> ", ch2.scene()
-            #self.setChildScene(ch2)
-            
     def parentItem(self):
         ## PyQt bug -- some items are returned incorrectly.
         return GraphicsScene.translateGraphicsItem(QtGui.QGraphicsObject.parentItem(self))
@@ -287,6 +278,57 @@ class GraphicsItem(object):
         return Point(vec).angle(Point(1,0))
         
         
+    #def itemChange(self, change, value):
+        #ret = QtGui.QGraphicsObject.itemChange(self, change, value)
+        #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged:
+            #print "Item scene changed:", self
+            #self.setChildScene(self)  ## This is bizarre.
+        #return ret
+
+    #def setChildScene(self, ch):
+        #scene = self.scene()
+        #for ch2 in ch.childItems():
+            #if ch2.scene() is not scene:
+                #print "item", ch2, "has different scene:", ch2.scene(), scene
+                #scene.addItem(ch2)
+                #QtGui.QApplication.processEvents()
+                #print "   --> ", ch2.scene()
+            #self.setChildScene(ch2)
+
+    def _updateView(self):
+        ## called to see whether this item has a new view to connect to
+        ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange.
+
+        ## It is possible this item has moved to a different ViewBox or widget;
+        ## clear out previously determined references to these.
+        self.forgetViewBox()
+        self.forgetViewWidget()
         
-        
-        
+        ## check for this item's current viewbox or view widget
+        view = self.getViewBox()
+        if view is None:
+            #print "  no view"
+            return
+
+        if self._connectedView is not None and view is self._connectedView():
+            #print "  already have view", view
+            return
+
+        ## disconnect from previous view
+        if self._connectedView is not None:
+            cv = self._connectedView()
+            if cv is not None:
+                #print "disconnect:", self
+                cv.sigRangeChanged.disconnect(self.viewRangeChanged)
+
+        ## connect to new view
+        #print "connect:", self
+        view.sigRangeChanged.connect(self.viewRangeChanged)
+        self._connectedView = weakref.ref(view)
+        self.viewRangeChanged()
+
+    def viewRangeChanged(self):
+        """
+        Called whenever the view coordinates of the ViewBox containing this item have changed.
+        """
+        pass
diff --git a/graphicsItems/GraphicsObject.py b/graphicsItems/GraphicsObject.py
index a738725de6e2df3202fe0f4c93e5f1201e42bea4..54df1156bccce450225d48507e3085269b2f9e9a 100644
--- a/graphicsItems/GraphicsObject.py
+++ b/graphicsItems/GraphicsObject.py
@@ -12,4 +12,10 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
         QtGui.QGraphicsObject.__init__(self, *args)
         GraphicsItem.__init__(self)
         
+    def itemChange(self, change, value):
+        ret = QtGui.QGraphicsObject.itemChange(self, change, value)
+        if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
+            self._updateView()
+        return ret
+
         
diff --git a/graphicsItems/GraphicsWidget.py b/graphicsItems/GraphicsWidget.py
index 6aef7dc973904b1127ccaa339a41f6a93a14f504..eb58f3a202ec2d0670c09be27e07750e26a1f20e 100644
--- a/graphicsItems/GraphicsWidget.py
+++ b/graphicsItems/GraphicsWidget.py
@@ -16,6 +16,12 @@ class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget):
         GraphicsItem.__init__(self)
         GraphicsScene.registerObject(self)  ## workaround for pyqt bug in graphicsscene.items()
 
+    def itemChange(self, change, value):
+        ret = QtGui.QGraphicsWidget.itemChange(self, change, value)
+        if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
+            self._updateView()
+        return ret
+
     #def getMenu(self):
         #pass
         
diff --git a/graphicsItems/GridItem.py b/graphicsItems/GridItem.py
index 6153f631d3cd8fc9a07dc3afbbccec0b95a4e442..cdb8ef430944e70b1ec9eb84aa9b9f9c0eade1d1 100644
--- a/graphicsItems/GridItem.py
+++ b/graphicsItems/GridItem.py
@@ -21,7 +21,8 @@ class GridItem(UIGraphicsItem):
         self.picture = None
         
         
-    def viewChangedEvent(self):
+    def viewRangeChanged(self):
+        GraphicsObject.viewRangeChanged(self)
         self.picture = None
         #UIGraphicsItem.viewRangeChanged(self)
         #self.update()
diff --git a/graphicsItems/ROI.py b/graphicsItems/ROI.py
index a6aa542ed388447e70a6801b8288f0c5b0200f42..dd0e199229efda5ab0f808118bc5f9a8e8b8c6ae 100755
--- a/graphicsItems/ROI.py
+++ b/graphicsItems/ROI.py
@@ -1068,7 +1068,8 @@ class Handle(UIGraphicsItem):
         return dti.map(tr.map(self.path))
         
         
-    def viewChangedEvent(self):
+    def viewRangeChanged(self):
+        GraphicsObject.viewRangeChanged(self)
         self._shape = None  ## invalidate shape, recompute later if requested.
         #self.updateShape()
         
diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py
index 2d7a168bd3694f99c1570857bb4c5c8ac7eedf41..ce5087555bb1b184c15438bfc47f495611583745 100644
--- a/graphicsItems/ScatterPlotItem.py
+++ b/graphicsItems/ScatterPlotItem.py
@@ -35,11 +35,12 @@ for k, c in coords.items():
 
 def makeSymbolPixmap(size, pen, brush, symbol):
     ## Render a spot with the given parameters to a pixmap
-    image = QtGui.QImage(size+2, size+2, QtGui.QImage.Format_ARGB32_Premultiplied)
+    penPxWidth = np.ceil(pen.width())
+    image = QtGui.QImage(size+penPxWidth, size+penPxWidth, QtGui.QImage.Format_ARGB32_Premultiplied)
     image.fill(0)
     p = QtGui.QPainter(image)
     p.setRenderHint(p.Antialiasing)
-    p.translate(size*0.5+1, size*0.5+1)
+    p.translate(image.width()*0.5, image.height()*0.5)
     p.scale(size, size)
     p.setPen(pen)
     p.setBrush(brush)
@@ -79,19 +80,16 @@ class ScatterPlotItem(GraphicsObject):
         GraphicsObject.__init__(self)
         self.setFlag(self.ItemHasNoContents, True)
         self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', 'S1'), ('pen', object), ('brush', object), ('item', object), ('data', object)])
-        #self.spots = []
-        #self.fragments = None
-        self.bounds = [None, None]
-        self.opts = {'pxMode': True}
-        #self.spotsValid = False
-        #self.itemsValid = False
+        self.bounds = [None, None]  ## caches data bounds
+        self._maxSpotWidth = 0      ## maximum size of the scale-variant portion of all spots
+        self._maxSpotPxWidth = 0    ## maximum size of the scale-invariant portion of all spots
         self._spotPixmap = None
+        self.opts = {'pxMode': True}
         
         self.setPen(200,200,200, update=False)
         self.setBrush(100,100,150, update=False)
         self.setSymbol('o', update=False)
         self.setSize(7, update=False)
-        #self.setIdentical(False, update=False)
         prof.mark('1')
         self.setData(*args, **kargs)
         prof.mark('setData')
@@ -228,6 +226,7 @@ class ScatterPlotItem(GraphicsObject):
             self.setPointData(kargs['data'], dataSet=newData)
         
         #self.updateSpots()
+        self.bounds = [None, None]
         self.generateSpotItems()
         self.sigPlotChanged.emit(self)
         
@@ -345,9 +344,30 @@ class ScatterPlotItem(GraphicsObject):
     def updateSpots(self, dataSet=None):
         if dataSet is None:
             dataSet = self.data
+        self._maxSpotWidth = 0
+        self._maxSpotPxWidth = 0
         for spot in dataSet['item']:
             spot.updateItem()
-        
+        self.measureSpotSizes(dataSet)
+
+    def measureSpotSizes(self, dataSet):
+        for spot in dataSet['item']:
+            ## keep track of the maximum spot size and pixel size
+            width = 0
+            pxWidth = 0
+            if self.opts['pxMode']:
+                pxWidth += spot.size()
+            else:
+                width += spot.size()
+            pen = spot.pen()
+            if pen.isCosmetic():
+                pxWidth += pen.width() * 2
+            else:
+                width += pen.width() * 2
+            self._maxSpotWidth = max(self._maxSpotWidth, width)
+            self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
+    
+    
     def clear(self):
         """Remove all spots from the scatter plot"""
         self.clearItems()
@@ -384,14 +404,16 @@ class ScatterPlotItem(GraphicsObject):
             d2 = d2[mask]
             
         if frac >= 1.0:
+            ## increase size of bounds based on spot size and pen width
+            px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1))  ## determine length of pixel along this axis
+            if px is None:
+                px = 0
             minIndex = np.argmin(d)
             maxIndex = np.argmax(d)
             minVal = d[minIndex]
             maxVal = d[maxIndex]
-            if not self.opts['pxMode']:
-                minVal -= self.data[minIndex]['size']
-                maxVal += self.data[maxIndex]['size']
-            self.bounds[ax] = (minVal, maxVal)
+            spotSize = 0.5 * (self._maxSpotWidth + px * self._maxSpotPxWidth)
+            self.bounds[ax] = (minVal-spotSize, maxVal+spotSize)
             return self.bounds[ax]
         elif frac <= 0.0:
             raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
@@ -412,6 +434,7 @@ class ScatterPlotItem(GraphicsObject):
             for rec in self.data:
                 if rec['item'] is None:
                     rec['item'] = PathSpotItem(rec, self)
+        self.measureSpotSizes(self.data)
         self.sigPlotChanged.emit(self)
 
     def defaultSpotPixmap(self):
@@ -430,6 +453,17 @@ class ScatterPlotItem(GraphicsObject):
             ymn = 0
             ymx = 0
         return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
+
+    def viewRangeChanged(self):
+        GraphicsObject.viewRangeChanged(self)
+        self.bounds = [None, None]
+        
+    def paint(self, p, *args):
+        ## NOTE: self.paint is disabled by this line in __init__:
+        ## self.setFlag(self.ItemHasNoContents, True)
+        p.setPen(fn.mkPen('r'))
+        p.drawRect(self.boundingRect())
+
         
     def points(self):
         return self.data['item']
diff --git a/graphicsItems/UIGraphicsItem.py b/graphicsItems/UIGraphicsItem.py
index cc1057512ed5c20b7afe9a9303250c52cfa65728..2bcdd66e998a36234ae78ef63365d16cdca4483e 100644
--- a/graphicsItems/UIGraphicsItem.py
+++ b/graphicsItems/UIGraphicsItem.py
@@ -28,7 +28,6 @@ class UIGraphicsItem(GraphicsObject):
         """
         GraphicsObject.__init__(self, parent)
         self.setFlag(self.ItemSendsScenePositionChanges)
-        self._connectedView = None
             
         if bounds is None:
             self._bounds = QtCore.QRectF(0, 0, 1, 1)
@@ -36,7 +35,7 @@ class UIGraphicsItem(GraphicsObject):
             self._bounds = bounds
             
         self._boundingRect = None
-        self.updateView()
+        self._updateView()
         
     def paint(self, *args):
         ## check for a new view object every time we paint.
@@ -45,39 +44,39 @@ class UIGraphicsItem(GraphicsObject):
     
     def itemChange(self, change, value):
         ret = GraphicsObject.itemChange(self, change, value)
-        if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged:
-            #print "caught parent/scene change:", self.parentItem(), self.scene()
-            self.updateView()
-        elif change == self.ItemScenePositionHasChanged:
+        #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged:  ## handled by GraphicsItem now.
+            ##print "caught parent/scene change:", self.parentItem(), self.scene()
+            #self.updateView()
+        if change == self.ItemScenePositionHasChanged:
             self.setNewBounds()
         return ret
     
-    def updateView(self):
-        ## called to see whether this item has a new view to connect to
+    #def updateView(self):
+        ### called to see whether this item has a new view to connect to
         
-        ## check for this item's current viewbox or view widget
-        view = self.getViewBox()
-        if view is None:
-            #print "  no view"
-            return
+        ### check for this item's current viewbox or view widget
+        #view = self.getViewBox()
+        #if view is None:
+            ##print "  no view"
+            #return
             
-        if self._connectedView is not None and view is self._connectedView():
-            #print "  already have view", view
-            return
+        #if self._connectedView is not None and view is self._connectedView():
+            ##print "  already have view", view
+            #return
             
-        ## disconnect from previous view
-        if self._connectedView is not None:
-            cv = self._connectedView()
-            if cv is not None:
-                #print "disconnect:", self
-                cv.sigRangeChanged.disconnect(self.viewRangeChanged)
+        ### disconnect from previous view
+        #if self._connectedView is not None:
+            #cv = self._connectedView()
+            #if cv is not None:
+                ##print "disconnect:", self
+                #cv.sigRangeChanged.disconnect(self.viewRangeChanged)
             
-        ## connect to new view
-        #print "connect:", self
-        view.sigRangeChanged.connect(self.viewRangeChanged)
-        self._connectedView = weakref.ref(view)
-        self.setNewBounds()
-        
+        ### connect to new view
+        ##print "connect:", self
+        #view.sigRangeChanged.connect(self.viewRangeChanged)
+        #self._connectedView = weakref.ref(view)
+        #self.setNewBounds()
+
     def boundingRect(self):
         if self._boundingRect is None:
             br = self.viewRect()
@@ -101,15 +100,6 @@ class UIGraphicsItem(GraphicsObject):
         """Update the item's bounding rect to match the viewport"""
         self._boundingRect = None  ## invalidate bounding rect, regenerate later if needed.
         self.prepareGeometryChange()
-        self.viewChangedEvent()
-
-
-    def viewChangedEvent(self):
-        """
-        Called whenever the view coordinates have changed.
-        This is a good method to override if you want to respond to change of coordinates.
-        """
-        pass
 
 
     def setPos(self, *args):