Merge pull request #514 from campagnola/viewbox-fix

Viewbox fix
This commit is contained in:
Luke Campagnola 2017-07-28 16:01:15 -07:00 committed by GitHub
commit 518c0b1d5c

View File

@ -85,7 +85,6 @@ class ViewBox(GraphicsWidget):
sigXRangeChanged = QtCore.Signal(object, object) sigXRangeChanged = QtCore.Signal(object, object)
sigRangeChangedManually = QtCore.Signal(object) sigRangeChangedManually = QtCore.Signal(object)
sigRangeChanged = QtCore.Signal(object, object) sigRangeChanged = QtCore.Signal(object, object)
#sigActionPositionChanged = QtCore.Signal(object)
sigStateChanged = QtCore.Signal(object) sigStateChanged = QtCore.Signal(object)
sigTransformChanged = QtCore.Signal(object) sigTransformChanged = QtCore.Signal(object)
sigResized = QtCore.Signal(object) sigResized = QtCore.Signal(object)
@ -128,8 +127,6 @@ class ViewBox(GraphicsWidget):
self.name = None self.name = None
self.linksBlocked = False self.linksBlocked = False
self.addedItems = [] self.addedItems = []
#self.gView = view
#self.showGrid = showGrid
self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred
self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed.
@ -188,9 +185,6 @@ class ViewBox(GraphicsWidget):
self.background.setPen(fn.mkPen(None)) self.background.setPen(fn.mkPen(None))
self.updateBackground() self.updateBackground()
#self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan
# this also enables capture of keyPressEvents.
## Make scale box that is shown when dragging on the view ## Make scale box that is shown when dragging on the view
self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1)
self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1))
@ -239,7 +233,6 @@ class ViewBox(GraphicsWidget):
ViewBox.updateAllViewLists() ViewBox.updateAllViewLists()
sid = id(self) sid = id(self)
self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None)
#self.destroyed.connect(self.unregister)
def unregister(self): def unregister(self):
""" """
@ -288,16 +281,12 @@ class ViewBox(GraphicsWidget):
self.prepareForPaint() self.prepareForPaint()
self._lastScene = scene self._lastScene = scene
def prepareForPaint(self): def prepareForPaint(self):
#autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False)
# don't check whether auto range is enabled here--only check when setting dirty flag. # don't check whether auto range is enabled here--only check when setting dirty flag.
if self._autoRangeNeedsUpdate: # and autoRangeEnabled: if self._autoRangeNeedsUpdate: # and autoRangeEnabled:
self.updateAutoRange() self.updateAutoRange()
if self._matrixNeedsUpdate: self.updateMatrix()
self.updateMatrix()
def getState(self, copy=True): def getState(self, copy=True):
"""Return the current state of the ViewBox. """Return the current state of the ViewBox.
@ -326,7 +315,6 @@ class ViewBox(GraphicsWidget):
del state['linkedViews'] del state['linkedViews']
self.state.update(state) self.state.update(state)
#self.updateMatrix()
self.updateViewRange() self.updateViewRange()
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
@ -353,12 +341,6 @@ class ViewBox(GraphicsWidget):
self.state['mouseMode'] = mode self.state['mouseMode'] = mode
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
#def toggleLeftAction(self, act): ## for backward compatibility
#if act.text() is 'pan':
#self.setLeftButtonAction('pan')
#elif act.text() is 'zoom':
#self.setLeftButtonAction('rect')
def setLeftButtonAction(self, mode='rect'): ## for backward compatibility def setLeftButtonAction(self, mode='rect'): ## for backward compatibility
if mode.lower() == 'rect': if mode.lower() == 'rect':
self.setMouseMode(ViewBox.RectMode) self.setMouseMode(ViewBox.RectMode)
@ -405,7 +387,6 @@ class ViewBox(GraphicsWidget):
if not ignoreBounds: if not ignoreBounds:
self.addedItems.append(item) self.addedItems.append(item)
self.updateAutoRange() self.updateAutoRange()
#print "addItem:", item, item.boundingRect()
def removeItem(self, item): def removeItem(self, item):
"""Remove an item from this view.""" """Remove an item from this view."""
@ -562,10 +543,6 @@ class ViewBox(GraphicsWidget):
# If nothing has changed, we are done. # If nothing has changed, we are done.
if any(changed): if any(changed):
#if update and self.matrixNeedsUpdate:
#self.updateMatrix(changed)
#return
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
# Update target rect for debugging # Update target rect for debugging
@ -581,21 +558,6 @@ class ViewBox(GraphicsWidget):
self._autoRangeNeedsUpdate = True self._autoRangeNeedsUpdate = True
#self.updateAutoRange() #self.updateAutoRange()
## Update view matrix only if requested
#if update:
#self.updateMatrix(changed)
## Otherwise, indicate that the matrix needs to be updated
#else:
#self.matrixNeedsUpdate = True
## Inform linked views that the range has changed <<This should be moved>>
#for ax, range in changes.items():
#link = self.linkedView(ax)
#if link is not None:
#link.linkedViewChanged(self, ax)
def setYRange(self, min, max, padding=None, update=True): def setYRange(self, min, max, padding=None, update=True):
""" """
Set the visible Y range of the view to [*min*, *max*]. Set the visible Y range of the view to [*min*, *max*].
@ -675,10 +637,6 @@ class ViewBox(GraphicsWidget):
for kwd in kwds: for kwd in kwds:
if kwd not in allowed: if kwd not in allowed:
raise ValueError("Invalid keyword argument '%s'." % kwd) raise ValueError("Invalid keyword argument '%s'." % kwd)
#for kwd in ['xLimits', 'yLimits', 'minRange', 'maxRange']:
#if kwd in kwds and self.state['limits'][kwd] != kwds[kwd]:
#self.state['limits'][kwd] = kwds[kwd]
#update = True
for axis in [0,1]: for axis in [0,1]:
for mnmx in [0,1]: for mnmx in [0,1]:
kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx] kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx]
@ -694,9 +652,6 @@ class ViewBox(GraphicsWidget):
if update: if update:
self.updateViewRange() self.updateViewRange()
def scaleBy(self, s=None, center=None, x=None, y=None): def scaleBy(self, s=None, center=None, x=None, y=None):
""" """
@ -762,8 +717,6 @@ class ViewBox(GraphicsWidget):
y = vr.top()+y, vr.bottom()+y y = vr.top()+y, vr.bottom()+y
if x is not None or y is not None: if x is not None or y is not None:
self.setRange(xRange=x, yRange=y, padding=0) self.setRange(xRange=x, yRange=y, padding=0)
def enableAutoRange(self, axis=None, enable=True, x=None, y=None): def enableAutoRange(self, axis=None, enable=True, x=None, y=None):
""" """
@ -773,11 +726,6 @@ class ViewBox(GraphicsWidget):
The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should
be visible (this only works with items implementing a dataRange method, such as PlotDataItem). be visible (this only works with items implementing a dataRange method, such as PlotDataItem).
""" """
#print "autorange:", axis, enable
#if not enable:
#import traceback
#traceback.print_stack()
# support simpler interface: # support simpler interface:
if x is not None or y is not None: if x is not None or y is not None:
if x is not None: if x is not None:
@ -813,10 +761,6 @@ class ViewBox(GraphicsWidget):
self.state['autoRange'][ax] = enable self.state['autoRange'][ax] = enable
self._autoRangeNeedsUpdate |= (enable is not False) self._autoRangeNeedsUpdate |= (enable is not False)
self.update() self.update()
#if needAutoRangeUpdate:
# self.updateAutoRange()
self.sigStateChanged.emit(self) self.sigStateChanged.emit(self)
@ -828,6 +772,8 @@ class ViewBox(GraphicsWidget):
return self.state['autoRange'][:] return self.state['autoRange'][:]
def setAutoPan(self, x=None, y=None): def setAutoPan(self, x=None, y=None):
"""Set whether automatic range will only pan (not scale) the view.
"""
if x is not None: if x is not None:
self.state['autoPan'][0] = x self.state['autoPan'][0] = x
if y is not None: if y is not None:
@ -836,6 +782,9 @@ class ViewBox(GraphicsWidget):
self.updateAutoRange() self.updateAutoRange()
def setAutoVisible(self, x=None, y=None): def setAutoVisible(self, x=None, y=None):
"""Set whether automatic range uses only visible data when determining
the range to show.
"""
if x is not None: if x is not None:
self.state['autoVisibleOnly'][0] = x self.state['autoVisibleOnly'][0] = x
if x is True: if x is True:
@ -924,7 +873,6 @@ class ViewBox(GraphicsWidget):
"""Link this view's Y axis to another view. (see LinkView)""" """Link this view's Y axis to another view. (see LinkView)"""
self.linkView(self.YAxis, view) self.linkView(self.YAxis, view)
def linkView(self, axis, view): def linkView(self, axis, view):
""" """
Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis.
@ -1118,7 +1066,6 @@ class ViewBox(GraphicsWidget):
return return
self.state['aspectLocked'] = ratio self.state['aspectLocked'] = ratio
if ratio != currentRatio: ## If this would change the current range, do that now if ratio != currentRatio: ## If this would change the current range, do that now
#self.setRange(0, self.state['viewRange'][0][0], self.state['viewRange'][0][1])
self.updateViewRange() self.updateViewRange()
self.updateAutoRange() self.updateAutoRange()
@ -1130,12 +1077,9 @@ class ViewBox(GraphicsWidget):
Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. Return the transform that maps from child(item in the childGroup) coordinates to local coordinates.
(This maps from inside the viewbox to outside) (This maps from inside the viewbox to outside)
""" """
if self._matrixNeedsUpdate: self.updateMatrix()
self.updateMatrix()
m = self.childGroup.transform() m = self.childGroup.transform()
#m1 = QtGui.QTransform() return m
#m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y())
return m #*m1
def mapToView(self, obj): def mapToView(self, obj):
"""Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox"""
@ -1163,7 +1107,6 @@ class ViewBox(GraphicsWidget):
def mapFromViewToItem(self, item, obj): def mapFromViewToItem(self, item, obj):
"""Maps *obj* from view coordinates to the local coordinate system of *item*.""" """Maps *obj* from view coordinates to the local coordinate system of *item*."""
return self.childGroup.mapToItem(item, obj) return self.childGroup.mapToItem(item, obj)
#return item.mapFromScene(self.mapViewToScene(obj))
def mapViewToDevice(self, obj): def mapViewToDevice(self, obj):
return self.mapToDevice(self.mapFromView(obj)) return self.mapToDevice(self.mapFromView(obj))
@ -1177,25 +1120,9 @@ class ViewBox(GraphicsWidget):
px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()]
return (px.length(), py.length()) return (px.length(), py.length())
def itemBoundingRect(self, item): def itemBoundingRect(self, item):
"""Return the bounding rect of the item in view coordinates""" """Return the bounding rect of the item in view coordinates"""
return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() return self.mapSceneToView(item.sceneBoundingRect()).boundingRect()
#def viewScale(self):
#vr = self.viewRect()
##print "viewScale:", self.range
#xd = vr.width()
#yd = vr.height()
#if xd == 0 or yd == 0:
#print "Warning: 0 range in view:", xd, yd
#return np.array([1,1])
##cs = self.canvas().size()
#cs = self.boundingRect()
#scale = np.array([cs.width() / xd, cs.height() / yd])
##print "view scale:", scale
#return scale
def wheelEvent(self, ev, axis=None): def wheelEvent(self, ev, axis=None):
mask = np.array(self.state['mouseEnabled'], dtype=np.float) mask = np.array(self.state['mouseEnabled'], dtype=np.float)
@ -1206,13 +1133,11 @@ class ViewBox(GraphicsWidget):
s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor
center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos()))
#center = ev.pos()
self._resetTarget() self._resetTarget()
self.scaleBy(s, center) self.scaleBy(s, center)
self.sigRangeChangedManually.emit(self.state['mouseEnabled']) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
ev.accept() ev.accept()
def mouseClickEvent(self, ev): def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): if ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
@ -1251,7 +1176,6 @@ class ViewBox(GraphicsWidget):
if ev.isFinish(): ## This is the final move in the drag; change the view scale now if ev.isFinish(): ## This is the final move in the drag; change the view scale now
#print "finish" #print "finish"
self.rbScaleBox.hide() self.rbScaleBox.hide()
#ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos))
ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos))
ax = self.childGroup.mapRectFromParent(ax) ax = self.childGroup.mapRectFromParent(ax)
self.showAxRect(ax) self.showAxRect(ax)
@ -1301,12 +1225,6 @@ class ViewBox(GraphicsWidget):
ctrl-- : moves backward in the zooming stack (if it exists) ctrl-- : moves backward in the zooming stack (if it exists)
""" """
#print ev.key()
#print 'I intercepted a key press, but did not accept it'
## not implemented yet ?
#self.keypress.sigkeyPressEvent.emit()
ev.accept() ev.accept()
if ev.text() == '-': if ev.text() == '-':
self.scaleHistory(-1) self.scaleHistory(-1)
@ -1324,7 +1242,6 @@ class ViewBox(GraphicsWidget):
if ptr != self.axHistoryPointer: if ptr != self.axHistoryPointer:
self.axHistoryPointer = ptr self.axHistoryPointer = ptr
self.showAxRect(self.axHistory[ptr]) self.showAxRect(self.axHistory[ptr])
def updateScaleBox(self, p1, p2): def updateScaleBox(self, p1, p2):
r = QtCore.QRectF(p1, p2) r = QtCore.QRectF(p1, p2)
@ -1338,14 +1255,6 @@ class ViewBox(GraphicsWidget):
self.setRange(ax.normalized()) # be sure w, h are correct coordinates self.setRange(ax.normalized()) # be sure w, h are correct coordinates
self.sigRangeChangedManually.emit(self.state['mouseEnabled']) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
#def mouseRect(self):
#vs = self.viewScale()
#vr = self.state['viewRange']
## Convert positions from screen (view) pixel coordinates to axis coordinates
#ax = QtCore.QRectF(self.pressPos[0]/vs[0]+vr[0][0], -(self.pressPos[1]/vs[1]-vr[1][1]),
#(self.mousePos[0]-self.pressPos[0])/vs[0], -(self.mousePos[1]-self.pressPos[1])/vs[1])
#return(ax)
def allChildren(self, item=None): def allChildren(self, item=None):
"""Return a list of all children and grandchildren of this ViewBox""" """Return a list of all children and grandchildren of this ViewBox"""
if item is None: if item is None:
@ -1356,8 +1265,6 @@ class ViewBox(GraphicsWidget):
children.extend(self.allChildren(ch)) children.extend(self.allChildren(ch))
return children return children
def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): def childrenBounds(self, frac=None, orthoRange=(None,None), items=None):
"""Return the bounding range of all children. """Return the bounding range of all children.
[[xmin, xmax], [ymin, ymax]] [[xmin, xmax], [ymin, ymax]]
@ -1380,8 +1287,6 @@ class ViewBox(GraphicsWidget):
useY = True useY = True
if hasattr(item, 'dataBounds'): if hasattr(item, 'dataBounds'):
#bounds = self._itemBoundsCache.get(item, None)
#if bounds is None:
if frac is None: if frac is None:
frac = (1.0, 1.0) frac = (1.0, 1.0)
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
@ -1414,9 +1319,6 @@ class ViewBox(GraphicsWidget):
itemBounds.append((bounds, useX, useY, pxPad)) itemBounds.append((bounds, useX, useY, pxPad))
#self._itemBoundsCache[item] = (bounds, useX, useY)
#else:
#bounds, useX, useY = bounds
else: else:
if int(item.flags() & item.ItemHasNoContents) > 0: if int(item.flags() & item.ItemHasNoContents) > 0:
continue continue
@ -1425,8 +1327,6 @@ class ViewBox(GraphicsWidget):
bounds = self.mapFromItemToView(item, bounds).boundingRect() bounds = self.mapFromItemToView(item, bounds).boundingRect()
itemBounds.append((bounds, True, True, 0)) itemBounds.append((bounds, True, True, 0))
#print itemBounds
## determine tentative new range ## determine tentative new range
range = [None, None] range = [None, None]
for bounds, useX, useY, px in itemBounds: for bounds, useX, useY, px in itemBounds:
@ -1442,14 +1342,11 @@ class ViewBox(GraphicsWidget):
range[0] = [bounds.left(), bounds.right()] range[0] = [bounds.left(), bounds.right()]
profiler() profiler()
#print "range", range
## Now expand any bounds that have a pixel margin ## Now expand any bounds that have a pixel margin
## This must be done _after_ we have a good estimate of the new range ## This must be done _after_ we have a good estimate of the new range
## to ensure that the pixel size is roughly accurate. ## to ensure that the pixel size is roughly accurate.
w = self.width() w = self.width()
h = self.height() h = self.height()
#print "w:", w, "h:", h
if w > 0 and range[0] is not None: if w > 0 and range[0] is not None:
pxSize = (range[0][1] - range[0][0]) / w pxSize = (range[0][1] - range[0][0]) / w
for bounds, useX, useY, px in itemBounds: for bounds, useX, useY, px in itemBounds:
@ -1585,9 +1482,9 @@ class ViewBox(GraphicsWidget):
self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
if any(changed): if any(changed):
self._matrixNeedsUpdate = True
self.sigRangeChanged.emit(self, self.state['viewRange']) self.sigRangeChanged.emit(self, self.state['viewRange'])
self.update() self.update()
self._matrixNeedsUpdate = True
# Inform linked views that the range has changed # Inform linked views that the range has changed
for ax in [0, 1]: for ax in [0, 1]:
@ -1598,6 +1495,9 @@ class ViewBox(GraphicsWidget):
link.linkedViewChanged(self, ax) link.linkedViewChanged(self, ax)
def updateMatrix(self, changed=None): def updateMatrix(self, changed=None):
if not self._matrixNeedsUpdate:
return
## Make the childGroup's transform match the requested viewRange. ## Make the childGroup's transform match the requested viewRange.
bounds = self.rect() bounds = self.rect()
@ -1648,7 +1548,6 @@ class ViewBox(GraphicsWidget):
self.background.show() self.background.show()
self.background.setBrush(fn.mkBrush(bg)) self.background.setBrush(fn.mkBrush(bg))
def updateViewLists(self): def updateViewLists(self):
try: try:
self.window() self.window()
@ -1662,7 +1561,6 @@ class ViewBox(GraphicsWidget):
## make a sorted list of all named views ## make a sorted list of all named views
nv = list(ViewBox.NamedViews.values()) nv = list(ViewBox.NamedViews.values())
#print "new view list:", nv
sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList
if self in nv: if self in nv:
@ -1676,16 +1574,11 @@ class ViewBox(GraphicsWidget):
for v in nv: for v in nv:
if link == v.name: if link == v.name:
self.linkView(ax, v) self.linkView(ax, v)
#print "New view list:", nv
#print "linked views:", self.state['linkedViews']
@staticmethod @staticmethod
def updateAllViewLists(): def updateAllViewLists():
#print "Update:", ViewBox.AllViews.keys()
#print "Update:", ViewBox.NamedViews.keys()
for v in ViewBox.AllViews: for v in ViewBox.AllViews:
v.updateViewLists() v.updateViewLists()
@staticmethod @staticmethod
def forgetView(vid, name): def forgetView(vid, name):
@ -1766,4 +1659,5 @@ class ViewBox(GraphicsWidget):
self.scene().removeItem(self.locateGroup) self.scene().removeItem(self.locateGroup)
self.locateGroup = None self.locateGroup = None
from .ViewBoxMenu import ViewBoxMenu from .ViewBoxMenu import ViewBoxMenu