More boundingRect / dataBounds bugfixes

This commit is contained in:
Luke Campagnola 2013-02-12 21:44:42 -05:00
parent 93a5753f5d
commit 9f55a27fdd
4 changed files with 149 additions and 93 deletions

View File

@ -70,7 +70,7 @@ s3 = pg.ScatterPlotItem(pxMode=False) ## Set pxMode=False to allow spots to tr
spots3 = []
for i in range(10):
for j in range(10):
spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 8}, 'brush':pg.intColor(i*10+j, 100)})
spots3.append({'pos': (1e-6*i, 1e-6*j), 'size': 1e-6, 'pen': {'color': 'w', 'width': 2}, 'brush':pg.intColor(i*10+j, 100)})
s3.addPoints(spots3)
w3.addItem(s3)
s3.sigClicked.connect(clicked)

View File

@ -93,7 +93,7 @@ class PlotCurveItem(GraphicsObject):
(x, y) = self.getData()
if x is None or len(x) == 0:
return (0, 0)
return (None, None)
if ax == 0:
d = x
@ -102,20 +102,106 @@ class PlotCurveItem(GraphicsObject):
d = y
d2 = x
## If an orthogonal range is specified, mask the data now
if orthoRange is not None:
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
d = d[mask]
d2 = d2[mask]
## Get min/max (or percentiles) of the requested data range
if frac >= 1.0:
b = (d.min(), d.max())
elif frac <= 0.0:
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
else:
b = (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
## adjust for fill level
if ax == 1 and self.opts['fillLevel'] is not None:
b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel']))
## Add pen width only if it is non-cosmetic.
pen = self.opts['pen']
spen = self.opts['shadowPen']
if not pen.isCosmetic():
b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072)
if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen:
b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072)
self._boundsCache[ax] = [(frac, orthoRange), b]
return b
def pixelPadding(self):
pen = self.opts['pen']
spen = self.opts['shadowPen']
w = 0
if pen.isCosmetic():
w += pen.widthF()*0.7072
if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen:
w = max(w, spen.widthF()*0.7072)
return w
def boundingRect(self):
if self._boundingRect is None:
(xmn, xmx) = self.dataBounds(ax=0)
(ymn, ymx) = self.dataBounds(ax=1)
if xmn is None:
return QtCore.QRectF()
px = py = 0.0
pxPad = self.pixelPadding()
if pxPad > 0:
# determine length of pixel in local x, y directions
px, py = self.pixelVectors()
px = 0 if px is None else px.length()
py = 0 if py is None else py.length()
# return bounds expanded by pixel size
px *= pxPad
py *= pxPad
#px += self._maxSpotWidth * 0.5
#py += self._maxSpotWidth * 0.5
self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn)
return self._boundingRect
def viewTransformChanged(self):
self.invalidateBounds()
self.prepareGeometryChange()
#def boundingRect(self):
#if self._boundingRect is None:
#(x, y) = self.getData()
#if x is None or y is None or len(x) == 0 or len(y) == 0:
#return QtCore.QRectF()
#if self.opts['shadowPen'] is not None:
#lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1)
#else:
#lineWidth = (self.opts['pen'].width()+1)
#pixels = self.pixelVectors()
#if pixels == (None, None):
#pixels = [Point(0,0), Point(0,0)]
#xmin = x.min()
#xmax = x.max()
#ymin = y.min()
#ymax = y.max()
#if self.opts['fillLevel'] is not None:
#ymin = min(ymin, self.opts['fillLevel'])
#ymax = max(ymax, self.opts['fillLevel'])
#xmin -= pixels[0].x() * lineWidth
#xmax += pixels[0].x() * lineWidth
#ymin -= abs(pixels[1].y()) * lineWidth
#ymax += abs(pixels[1].y()) * lineWidth
#self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
#return self._boundingRect
def invalidateBounds(self):
self._boundingRect = None
@ -280,40 +366,6 @@ class PlotCurveItem(GraphicsObject):
return QtGui.QPainterPath()
return self.path
def boundingRect(self):
if self._boundingRect is None:
(x, y) = self.getData()
if x is None or y is None or len(x) == 0 or len(y) == 0:
return QtCore.QRectF()
if self.opts['shadowPen'] is not None:
lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1)
else:
lineWidth = (self.opts['pen'].width()+1)
pixels = self.pixelVectors()
if pixels == (None, None):
pixels = [Point(0,0), Point(0,0)]
xmin = x.min()
xmax = x.max()
ymin = y.min()
ymax = y.max()
if self.opts['fillLevel'] is not None:
ymin = min(ymin, self.opts['fillLevel'])
ymax = max(ymax, self.opts['fillLevel'])
xmin -= pixels[0].x() * lineWidth
xmax += pixels[0].x() * lineWidth
ymin -= abs(pixels[1].y()) * lineWidth
ymax += abs(pixels[1].y()) * lineWidth
self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
return self._boundingRect
def paint(self, p, opt, widget):
prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
if self.xData is None:

View File

@ -471,33 +471,57 @@ class PlotDataItem(GraphicsObject):
and max)
=============== =============================================================
"""
if frac <= 0.0:
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
(x, y) = self.getData()
if x is None or len(x) == 0:
return None
range = [None, None]
if self.curve.isVisible():
range = self.curve.dataBounds(ax, frac, orthoRange)
elif self.scatter.isVisible():
r2 = self.scatter.dataBounds(ax, frac, orthoRange)
range = [
r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])),
r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1]))
]
return range
#if frac <= 0.0:
#raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
#(x, y) = self.getData()
#if x is None or len(x) == 0:
#return None
if ax == 0:
d = x
d2 = y
elif ax == 1:
d = y
d2 = x
#if ax == 0:
#d = x
#d2 = y
#elif ax == 1:
#d = y
#d2 = x
if orthoRange is not None:
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
d = d[mask]
#d2 = d2[mask]
#if orthoRange is not None:
#mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
#d = d[mask]
##d2 = d2[mask]
if len(d) > 0:
if frac >= 1.0:
return (np.min(d), np.max(d))
else:
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
else:
return None
#if len(d) > 0:
#if frac >= 1.0:
#return (np.min(d), np.max(d))
#else:
#return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
#else:
#return None
def pixelPadding(self):
"""
Return the size in pixels that this item may draw beyond the values returned by dataBounds().
This method is called by ViewBox when auto-scaling.
"""
pad = 0
if self.curve.isVisible():
pad = max(pad, self.curve.pixelPadding())
elif self.scatter.isVisible():
pad = max(pad, self.scatter.pixelPadding())
return pad
def clear(self):
#for i in self.curves+self.scatters:

View File

@ -60,7 +60,7 @@ def renderSymbol(symbol, size, pen, brush, device=None):
#return SymbolPixmapCache[key]
## Render a spot with the given parameters to a pixmap
penPxWidth = max(np.ceil(pen.width()), 1)
penPxWidth = max(np.ceil(pen.widthF()), 1)
image = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32)
image.fill(0)
p = QtGui.QPainter(image)
@ -115,7 +115,7 @@ class SymbolAtlas(object):
symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush']
pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen
brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush
key = (symbol, size, fn.colorTuple(pen.color()), pen.width(), pen.style(), fn.colorTuple(brush.color()))
key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color()))
if key not in self.symbolMap:
newCoords = SymbolAtlas.SymbolCoords()
self.symbolMap[key] = newCoords
@ -589,13 +589,13 @@ class ScatterPlotItem(GraphicsObject):
width = 0
pxWidth = 0
if self.opts['pxMode']:
pxWidth = size + pen.width()
pxWidth = size + pen.widthF()
else:
width = size
if pen.isCosmetic():
pxWidth += pen.width()
pxWidth += pen.widthF()
else:
width += pen.width()
width += pen.widthF()
self._maxSpotWidth = max(self._maxSpotWidth, width)
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
self.bounds = [None, None]
@ -629,20 +629,7 @@ 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
#px = self.pixelVectors()[ax]
#if px is None:
#px = 0
#else:
#px = px.length()
#minIndex = np.argmin(d)
#maxIndex = np.argmax(d)
#minVal = d[minIndex]
#maxVal = d[maxIndex]
#spotSize = 0.5 * (self._maxSpotWidth + px * self._maxSpotPxWidth)
#self.bounds[ax] = (minVal-spotSize, maxVal+spotSize)
self.bounds[ax] = (d.min() - 0.5*self._maxSpotWidth, d.max() + 0.5*self._maxSpotWidth)
self.bounds[ax] = (d.min() - self._maxSpotWidth*0.7072, d.max() + self._maxSpotWidth*0.7072)
return self.bounds[ax]
elif frac <= 0.0:
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
@ -650,13 +637,7 @@ class ScatterPlotItem(GraphicsObject):
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
def pixelPadding(self):
return self._maxSpotPxWidth
#def defaultSpotPixmap(self):
### Return the default spot pixmap
#if self._spotPixmap is None:
#self._spotPixmap = makeSymbolPixmap(size=self.opts['size'], brush=self.opts['brush'], pen=self.opts['pen'], symbol=self.opts['symbol'])
#return self._spotPixmap
return self._maxSpotPxWidth*0.7072
def boundingRect(self):
(xmn, xmx) = self.dataBounds(ax=0)
@ -669,17 +650,16 @@ class ScatterPlotItem(GraphicsObject):
ymx = 0
px = py = 0.0
if self._maxSpotPxWidth > 0:
pxPad = self.pixelPadding()
if pxPad > 0:
# determine length of pixel in local x, y directions
px, py = self.pixelVectors()
px = 0 if px is None else px.length() * 0.5
py = 0 if py is None else py.length() * 0.5
px = 0 if px is None else px.length()
py = 0 if py is None else py.length()
# return bounds expanded by pixel size
px *= self._maxSpotPxWidth
py *= self._maxSpotPxWidth
px += self._maxSpotWidth * 0.5
py += self._maxSpotWidth * 0.5
px *= pxPad
py *= pxPad
return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn)
def viewTransformChanged(self):