diff --git a/graphicsItems/GraphicsItem.py b/graphicsItems/GraphicsItem.py index 0b902c58..838fb76d 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 a738725d..54df1156 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 6aef7dc9..eb58f3a2 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 6153f631..cdb8ef43 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 a6aa542e..dd0e1992 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 2d7a168b..ce508755 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 cc105751..2bcdd66e 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):