- Fixes to ScatterPlotItem bounding rect calculation
- Moved some functionality from UIGraphicsItem upstream to GraphicsItem
This commit is contained in:
parent
8a9557cff1
commit
841006b79c
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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']
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user