diff --git a/exporters/Exporter.py b/exporters/Exporter.py index 81930670..f5a93088 100644 --- a/exporters/Exporter.py +++ b/exporters/Exporter.py @@ -66,7 +66,7 @@ class Exporter(object): if selectedExt is not None: selectedExt = selectedExt.groups()[0].lower() if ext != selectedExt: - fileName = fileName + selectedExt + fileName = fileName + '.' + selectedExt.lstrip('.') self.export(fileName=fileName, **self.fileDialog.opts) diff --git a/graphicsItems/ErrorBarItem.py b/graphicsItems/ErrorBarItem.py index ccb38774..656b9e2e 100644 --- a/graphicsItems/ErrorBarItem.py +++ b/graphicsItems/ErrorBarItem.py @@ -127,7 +127,7 @@ class ErrorBarItem(GraphicsObject): def boundingRect(self): if self.path is None: - return QtCore.QRectF() + self.drawPath() return self.path.boundingRect() \ No newline at end of file diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py index 7c204479..93653869 100644 --- a/graphicsItems/ScatterPlotItem.py +++ b/graphicsItems/ScatterPlotItem.py @@ -554,7 +554,6 @@ class ScatterPlotItem(GraphicsObject): #rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec)) if invalidate: self.invalidate() - self.informViewBoundsChanged() def getSpotOpts(self, recs, scale=1.0): if recs.ndim == 0: @@ -632,23 +631,26 @@ class ScatterPlotItem(GraphicsObject): 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) + #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) return self.bounds[ax] elif frac <= 0.0: raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) else: 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 @@ -665,14 +667,26 @@ class ScatterPlotItem(GraphicsObject): if ymn is None or ymx is None: ymn = 0 ymx = 0 - return QtCore.QRectF(xmn, ymn, xmx-xmn, ymx-ymn) + + px = py = 0.0 + if self._maxSpotPxWidth > 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 + + # return bounds expanded by pixel size + px *= self._maxSpotPxWidth + py *= self._maxSpotPxWidth + px += self._maxSpotWidth * 0.5 + py += self._maxSpotWidth * 0.5 + return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) def viewTransformChanged(self): self.prepareGeometryChange() GraphicsObject.viewTransformChanged(self) self.bounds = [None, None] self.fragments = None - self.informViewBoundsChanged() def generateFragments(self): tr = self.deviceTransform() diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py index 44f98e77..ce1d61f9 100644 --- a/graphicsItems/ViewBox/ViewBox.py +++ b/graphicsItems/ViewBox/ViewBox.py @@ -297,12 +297,11 @@ class ViewBox(GraphicsWidget): def resizeEvent(self, ev): #self.setRange(self.range, padding=0) - #self.updateAutoRange() - self._itemBoundsCache.clear() + self.updateAutoRange() self.updateMatrix() self.sigStateChanged.emit(self) self.background.setRect(self.rect()) - + #self._itemBoundsCache.clear() #self.linkedXChanged() #self.linkedYChanged() @@ -730,8 +729,7 @@ class ViewBox(GraphicsWidget): def itemBoundsChanged(self, item): self._itemBoundsCache.pop(item, None) - if item in self.addedItems: - self.updateAutoRange() + self.updateAutoRange() def invertY(self, b=True): """ @@ -1003,63 +1001,71 @@ class ViewBox(GraphicsWidget): Values may be None if there are no specific bounds for an axis. """ prof = debug.Profiler('updateAutoRange', disabled=True) - - - #items = self.allChildren() items = self.addedItems - #if item is None: - ##print "children bounding rect:" - #item = self.childGroup - - range = [None, None] - + ## measure pixel dimensions in view box + px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] + + ## First collect all boundary information + itemBounds = [] for item in items: if not item.isVisible(): continue useX = True useY = True + if hasattr(item, 'dataBounds'): - bounds = self._itemBoundsCache.get(item, None) - if bounds is None: - if frac is None: - frac = (1.0, 1.0) - xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) - yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) - if xr is None or xr == (None, None): - useX = False - xr = (0,0) - if yr is None or yr == (None, None): - useY = False - yr = (0,0) + #bounds = self._itemBoundsCache.get(item, None) + #if bounds is None: + if frac is None: + frac = (1.0, 1.0) + xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) + yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) + pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() + if xr is None or xr == (None, None): + useX = False + xr = (0,0) + if yr is None or yr == (None, None): + useY = False + yr = (0,0) - bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) - bounds = self.mapFromItemToView(item, bounds).boundingRect() - self._itemBoundsCache[item] = (bounds, useX, useY) - else: - bounds, useX, useY = bounds + bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) + bounds = self.mapFromItemToView(item, bounds).boundingRect() + + if not any([useX, useY]): + continue + + ## If we are ignoring only one axis, we need to check for rotations + if useX != useY: ## != means xor + ang = round(item.transformAngle()) + if ang == 0 or ang == 180: + pass + elif ang == 90 or ang == 270: + useX, useY = useY, useX + else: + ## Item is rotated at non-orthogonal angle, ignore bounds entirely. + ## Not really sure what is the expected behavior in this case. + continue ## need to check for item rotations and decide how best to apply this boundary. + + + itemBounds.append((bounds, useX, useY, pxPad)) + #self._itemBoundsCache[item] = (bounds, useX, useY) + #else: + #bounds, useX, useY = bounds else: if int(item.flags() & item.ItemHasNoContents) > 0: continue else: bounds = item.boundingRect() bounds = self.mapFromItemToView(item, bounds).boundingRect() - - prof.mark('1') - - if not any([useX, useY]): - continue - - if useX != useY: ## != means xor - ang = item.transformAngle() - if ang == 0 or ang == 180: - pass - elif ang == 90 or ang == 270: - useX, useY = useY, useX - else: - continue ## need to check for item rotations and decide how best to apply this boundary. - + itemBounds.append((bounds, True, True, 0)) + + #print itemBounds + + ## determine tentative new range + range = [None, None] + for bounds, useX, useY, px in itemBounds: if useY: if range[1] is not None: range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] @@ -1071,7 +1077,32 @@ class ViewBox(GraphicsWidget): else: range[0] = [bounds.left(), bounds.right()] prof.mark('2') - + + #print "range", range + + ## Now expand any bounds that have a pixel margin + ## This must be done _after_ we have a good estimate of the new range + ## to ensure that the pixel size is roughly accurate. + w = self.width() + h = self.height() + #print "w:", w, "h:", h + if w > 0 and range[0] is not None: + pxSize = (range[0][1] - range[0][0]) / w + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useX: + continue + range[0][0] = min(range[0][0], bounds.left() - px*pxSize) + range[0][1] = max(range[0][1], bounds.right() + px*pxSize) + if h > 0 and range[1] is not None: + pxSize = (range[1][1] - range[1][0]) / h + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useY: + continue + range[1][0] = min(range[1][0], bounds.top() - px*pxSize) + range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) + + #print "final range", range + prof.finish() return range @@ -1089,6 +1120,8 @@ class ViewBox(GraphicsWidget): def updateMatrix(self, changed=None): + ## Make the childGroup's transform match the requested range. + if changed is None: changed = [False, False] changed = list(changed)