- 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):
|
def __init__(self, register=True):
|
||||||
self._viewWidget = None
|
self._viewWidget = None
|
||||||
self._viewBox = None
|
self._viewBox = None
|
||||||
|
self._connectedView = None
|
||||||
if register:
|
if register:
|
||||||
GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
|
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
|
return vt.map(QtCore.QPointF(1, 0))-orig, vt.map(QtCore.QPointF(0, 1))-orig
|
||||||
|
|
||||||
def pixelLength(self, direction):
|
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()
|
dt = self.deviceTransform()
|
||||||
if dt is None:
|
if dt is None:
|
||||||
return None
|
return None
|
||||||
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
|
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]
|
dti = dt.inverted()[0]
|
||||||
return Point(dti.map(norm)-dti.map(Point(0,0))).length()
|
return Point(dti.map(norm)-dti.map(Point(0,0))).length()
|
||||||
|
|
||||||
|
@ -235,23 +243,6 @@ class GraphicsItem(object):
|
||||||
def viewPos(self):
|
def viewPos(self):
|
||||||
return self.mapToView(self.mapFromParent(self.pos()))
|
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):
|
def parentItem(self):
|
||||||
## PyQt bug -- some items are returned incorrectly.
|
## PyQt bug -- some items are returned incorrectly.
|
||||||
return GraphicsScene.translateGraphicsItem(QtGui.QGraphicsObject.parentItem(self))
|
return GraphicsScene.translateGraphicsItem(QtGui.QGraphicsObject.parentItem(self))
|
||||||
|
@ -287,6 +278,57 @@ class GraphicsItem(object):
|
||||||
return Point(vec).angle(Point(1,0))
|
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)
|
QtGui.QGraphicsObject.__init__(self, *args)
|
||||||
GraphicsItem.__init__(self)
|
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)
|
GraphicsItem.__init__(self)
|
||||||
GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items()
|
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):
|
#def getMenu(self):
|
||||||
#pass
|
#pass
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,8 @@ class GridItem(UIGraphicsItem):
|
||||||
self.picture = None
|
self.picture = None
|
||||||
|
|
||||||
|
|
||||||
def viewChangedEvent(self):
|
def viewRangeChanged(self):
|
||||||
|
GraphicsObject.viewRangeChanged(self)
|
||||||
self.picture = None
|
self.picture = None
|
||||||
#UIGraphicsItem.viewRangeChanged(self)
|
#UIGraphicsItem.viewRangeChanged(self)
|
||||||
#self.update()
|
#self.update()
|
||||||
|
|
|
@ -1068,7 +1068,8 @@ class Handle(UIGraphicsItem):
|
||||||
return dti.map(tr.map(self.path))
|
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._shape = None ## invalidate shape, recompute later if requested.
|
||||||
#self.updateShape()
|
#self.updateShape()
|
||||||
|
|
||||||
|
|
|
@ -35,11 +35,12 @@ for k, c in coords.items():
|
||||||
|
|
||||||
def makeSymbolPixmap(size, pen, brush, symbol):
|
def makeSymbolPixmap(size, pen, brush, symbol):
|
||||||
## Render a spot with the given parameters to a pixmap
|
## 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)
|
image.fill(0)
|
||||||
p = QtGui.QPainter(image)
|
p = QtGui.QPainter(image)
|
||||||
p.setRenderHint(p.Antialiasing)
|
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.scale(size, size)
|
||||||
p.setPen(pen)
|
p.setPen(pen)
|
||||||
p.setBrush(brush)
|
p.setBrush(brush)
|
||||||
|
@ -79,19 +80,16 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
GraphicsObject.__init__(self)
|
GraphicsObject.__init__(self)
|
||||||
self.setFlag(self.ItemHasNoContents, True)
|
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.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', 'S1'), ('pen', object), ('brush', object), ('item', object), ('data', object)])
|
||||||
#self.spots = []
|
self.bounds = [None, None] ## caches data bounds
|
||||||
#self.fragments = None
|
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
|
||||||
self.bounds = [None, None]
|
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
|
||||||
self.opts = {'pxMode': True}
|
|
||||||
#self.spotsValid = False
|
|
||||||
#self.itemsValid = False
|
|
||||||
self._spotPixmap = None
|
self._spotPixmap = None
|
||||||
|
self.opts = {'pxMode': True}
|
||||||
|
|
||||||
self.setPen(200,200,200, update=False)
|
self.setPen(200,200,200, update=False)
|
||||||
self.setBrush(100,100,150, update=False)
|
self.setBrush(100,100,150, update=False)
|
||||||
self.setSymbol('o', update=False)
|
self.setSymbol('o', update=False)
|
||||||
self.setSize(7, update=False)
|
self.setSize(7, update=False)
|
||||||
#self.setIdentical(False, update=False)
|
|
||||||
prof.mark('1')
|
prof.mark('1')
|
||||||
self.setData(*args, **kargs)
|
self.setData(*args, **kargs)
|
||||||
prof.mark('setData')
|
prof.mark('setData')
|
||||||
|
@ -228,6 +226,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
self.setPointData(kargs['data'], dataSet=newData)
|
self.setPointData(kargs['data'], dataSet=newData)
|
||||||
|
|
||||||
#self.updateSpots()
|
#self.updateSpots()
|
||||||
|
self.bounds = [None, None]
|
||||||
self.generateSpotItems()
|
self.generateSpotItems()
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
|
||||||
|
@ -345,8 +344,29 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
def updateSpots(self, dataSet=None):
|
def updateSpots(self, dataSet=None):
|
||||||
if dataSet is None:
|
if dataSet is None:
|
||||||
dataSet = self.data
|
dataSet = self.data
|
||||||
|
self._maxSpotWidth = 0
|
||||||
|
self._maxSpotPxWidth = 0
|
||||||
for spot in dataSet['item']:
|
for spot in dataSet['item']:
|
||||||
spot.updateItem()
|
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):
|
def clear(self):
|
||||||
"""Remove all spots from the scatter plot"""
|
"""Remove all spots from the scatter plot"""
|
||||||
|
@ -384,14 +404,16 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
d2 = d2[mask]
|
d2 = d2[mask]
|
||||||
|
|
||||||
if frac >= 1.0:
|
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)
|
minIndex = np.argmin(d)
|
||||||
maxIndex = np.argmax(d)
|
maxIndex = np.argmax(d)
|
||||||
minVal = d[minIndex]
|
minVal = d[minIndex]
|
||||||
maxVal = d[maxIndex]
|
maxVal = d[maxIndex]
|
||||||
if not self.opts['pxMode']:
|
spotSize = 0.5 * (self._maxSpotWidth + px * self._maxSpotPxWidth)
|
||||||
minVal -= self.data[minIndex]['size']
|
self.bounds[ax] = (minVal-spotSize, maxVal+spotSize)
|
||||||
maxVal += self.data[maxIndex]['size']
|
|
||||||
self.bounds[ax] = (minVal, maxVal)
|
|
||||||
return self.bounds[ax]
|
return self.bounds[ax]
|
||||||
elif frac <= 0.0:
|
elif frac <= 0.0:
|
||||||
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
|
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:
|
for rec in self.data:
|
||||||
if rec['item'] is None:
|
if rec['item'] is None:
|
||||||
rec['item'] = PathSpotItem(rec, self)
|
rec['item'] = PathSpotItem(rec, self)
|
||||||
|
self.measureSpotSizes(self.data)
|
||||||
self.sigPlotChanged.emit(self)
|
self.sigPlotChanged.emit(self)
|
||||||
|
|
||||||
def defaultSpotPixmap(self):
|
def defaultSpotPixmap(self):
|
||||||
|
@ -431,6 +454,17 @@ class ScatterPlotItem(GraphicsObject):
|
||||||
ymx = 0
|
ymx = 0
|
||||||
return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn)
|
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):
|
def points(self):
|
||||||
return self.data['item']
|
return self.data['item']
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ class UIGraphicsItem(GraphicsObject):
|
||||||
"""
|
"""
|
||||||
GraphicsObject.__init__(self, parent)
|
GraphicsObject.__init__(self, parent)
|
||||||
self.setFlag(self.ItemSendsScenePositionChanges)
|
self.setFlag(self.ItemSendsScenePositionChanges)
|
||||||
self._connectedView = None
|
|
||||||
|
|
||||||
if bounds is None:
|
if bounds is None:
|
||||||
self._bounds = QtCore.QRectF(0, 0, 1, 1)
|
self._bounds = QtCore.QRectF(0, 0, 1, 1)
|
||||||
|
@ -36,7 +35,7 @@ class UIGraphicsItem(GraphicsObject):
|
||||||
self._bounds = bounds
|
self._bounds = bounds
|
||||||
|
|
||||||
self._boundingRect = None
|
self._boundingRect = None
|
||||||
self.updateView()
|
self._updateView()
|
||||||
|
|
||||||
def paint(self, *args):
|
def paint(self, *args):
|
||||||
## check for a new view object every time we paint.
|
## check for a new view object every time we paint.
|
||||||
|
@ -45,38 +44,38 @@ class UIGraphicsItem(GraphicsObject):
|
||||||
|
|
||||||
def itemChange(self, change, value):
|
def itemChange(self, change, value):
|
||||||
ret = GraphicsObject.itemChange(self, change, value)
|
ret = GraphicsObject.itemChange(self, change, value)
|
||||||
if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged:
|
#if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: ## handled by GraphicsItem now.
|
||||||
#print "caught parent/scene change:", self.parentItem(), self.scene()
|
##print "caught parent/scene change:", self.parentItem(), self.scene()
|
||||||
self.updateView()
|
#self.updateView()
|
||||||
elif change == self.ItemScenePositionHasChanged:
|
if change == self.ItemScenePositionHasChanged:
|
||||||
self.setNewBounds()
|
self.setNewBounds()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def updateView(self):
|
#def updateView(self):
|
||||||
## called to see whether this item has a new view to connect to
|
### called to see whether this item has a new view to connect to
|
||||||
|
|
||||||
## check for this item's current viewbox or view widget
|
### check for this item's current viewbox or view widget
|
||||||
view = self.getViewBox()
|
#view = self.getViewBox()
|
||||||
if view is None:
|
#if view is None:
|
||||||
#print " no view"
|
##print " no view"
|
||||||
return
|
#return
|
||||||
|
|
||||||
if self._connectedView is not None and view is self._connectedView():
|
#if self._connectedView is not None and view is self._connectedView():
|
||||||
#print " already have view", view
|
##print " already have view", view
|
||||||
return
|
#return
|
||||||
|
|
||||||
## disconnect from previous view
|
### disconnect from previous view
|
||||||
if self._connectedView is not None:
|
#if self._connectedView is not None:
|
||||||
cv = self._connectedView()
|
#cv = self._connectedView()
|
||||||
if cv is not None:
|
#if cv is not None:
|
||||||
#print "disconnect:", self
|
##print "disconnect:", self
|
||||||
cv.sigRangeChanged.disconnect(self.viewRangeChanged)
|
#cv.sigRangeChanged.disconnect(self.viewRangeChanged)
|
||||||
|
|
||||||
## connect to new view
|
### connect to new view
|
||||||
#print "connect:", self
|
##print "connect:", self
|
||||||
view.sigRangeChanged.connect(self.viewRangeChanged)
|
#view.sigRangeChanged.connect(self.viewRangeChanged)
|
||||||
self._connectedView = weakref.ref(view)
|
#self._connectedView = weakref.ref(view)
|
||||||
self.setNewBounds()
|
#self.setNewBounds()
|
||||||
|
|
||||||
def boundingRect(self):
|
def boundingRect(self):
|
||||||
if self._boundingRect is None:
|
if self._boundingRect is None:
|
||||||
|
@ -101,15 +100,6 @@ class UIGraphicsItem(GraphicsObject):
|
||||||
"""Update the item's bounding rect to match the viewport"""
|
"""Update the item's bounding rect to match the viewport"""
|
||||||
self._boundingRect = None ## invalidate bounding rect, regenerate later if needed.
|
self._boundingRect = None ## invalidate bounding rect, regenerate later if needed.
|
||||||
self.prepareGeometryChange()
|
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):
|
def setPos(self, *args):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user