Added ViewBox auto-ranging options:
- auto pan (center of view changes, but total range does not) - scale only to visible data (data outside of viewbox is ignored when auto-ranging)
This commit is contained in:
parent
9107eed243
commit
f21c3986d5
@ -191,7 +191,7 @@ class InfiniteLine(UIGraphicsItem):
|
||||
p.drawLine(Point(br.right(), 0), Point(br.left(), 0))
|
||||
#p.drawRect(self.boundingRect())
|
||||
|
||||
def dataBounds(self, axis, frac=1.0):
|
||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||
if axis == 0:
|
||||
return None ## x axis should never be auto-scaled
|
||||
else:
|
||||
|
@ -146,7 +146,7 @@ class LinearRegionItem(UIGraphicsItem):
|
||||
p.drawRect(self.boundingRect())
|
||||
#prof.finish()
|
||||
|
||||
def dataBounds(self, axis, frac=1.0):
|
||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||
if axis == self.orientation:
|
||||
return self.getRegion()
|
||||
else:
|
||||
|
@ -82,15 +82,23 @@ class PlotCurveItem(GraphicsObject):
|
||||
def getData(self):
|
||||
return self.xData, self.yData
|
||||
|
||||
def dataBounds(self, ax, frac=1.0):
|
||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||
(x, y) = self.getData()
|
||||
if x is None or len(x) == 0:
|
||||
return (0, 0)
|
||||
|
||||
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 frac >= 1.0:
|
||||
return (d.min(), d.max())
|
||||
|
@ -421,15 +421,39 @@ class PlotDataItem(GraphicsObject):
|
||||
#print self.xDisp.shape, self.xDisp.min(), self.xDisp.max()
|
||||
return self.xDisp, self.yDisp
|
||||
|
||||
def dataBounds(self, ax, frac=1.0):
|
||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||
"""
|
||||
Returns the range occupied by the data (along a specific axis) in this item.
|
||||
Tis method is called by ViewBox when auto-scaling.
|
||||
=============== =============================================================
|
||||
**Arguments:**
|
||||
ax (0 or 1) the axis for which to return this item's data range
|
||||
frac (float 0.0-1.0) Specifies what fraction of the total data
|
||||
range to return. By default, the entire range is returned.
|
||||
This allows the ViewBox to ignore large spikes in the data
|
||||
when auto-scaling.
|
||||
orthoRange ([min,max] or None) Specifies that only the data within the
|
||||
given range (orthogonal to *ax*) should me measured when
|
||||
returning the data range. (For example, a ViewBox might ask
|
||||
what is the y-range of all data with x-values between min
|
||||
and max)
|
||||
=============== =============================================================
|
||||
"""
|
||||
(x, y) = self.getData()
|
||||
if x is None or len(x) == 0:
|
||||
return (0, 0)
|
||||
|
||||
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 frac >= 1.0:
|
||||
return (np.min(d), np.max(d))
|
||||
|
@ -70,6 +70,8 @@ class PlotItem(GraphicsWidget):
|
||||
:func:`autoRange <pyqtgraph.ViewBox.autoRange>`,
|
||||
:func:`setXLink <pyqtgraph.ViewBox.setXLink>`,
|
||||
:func:`setYLink <pyqtgraph.ViewBox.setYLink>`,
|
||||
:func:`setAutoPan <pyqtgraph.ViewBox.setAutoPan>`,
|
||||
:func:`setAutoVisible <pyqtgraph.ViewBox.setAutoVisible>`,
|
||||
:func:`viewRect <pyqtgraph.ViewBox.viewRect>`,
|
||||
:func:`viewRange <pyqtgraph.ViewBox.viewRange>`,
|
||||
:func:`setMouseEnabled <pyqtgraph.ViewBox.setMouseEnabled>`,
|
||||
@ -188,7 +190,7 @@ class PlotItem(GraphicsWidget):
|
||||
|
||||
## Wrap a few methods from viewBox
|
||||
for m in [
|
||||
'setXRange', 'setYRange', 'setXLink', 'setYLink',
|
||||
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
|
||||
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
|
||||
'enableAutoRange', 'disableAutoRange', 'setAspectLocked',
|
||||
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
|
||||
|
@ -341,7 +341,7 @@ class ScatterPlotItem(GraphicsObject):
|
||||
self.bounds = [None, None]
|
||||
|
||||
|
||||
def dataBounds(self, ax, frac=1.0):
|
||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||
if frac >= 1.0 and self.bounds[ax] is not None:
|
||||
return self.bounds[ax]
|
||||
|
||||
@ -350,8 +350,15 @@ class ScatterPlotItem(GraphicsObject):
|
||||
|
||||
if ax == 0:
|
||||
d = self.data['x']
|
||||
d2 = self.data['y']
|
||||
elif ax == 1:
|
||||
d = self.data['y']
|
||||
d2 = self.data['x']
|
||||
|
||||
if orthoRange is not None:
|
||||
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])
|
||||
d = d[mask]
|
||||
d2 = d2[mask]
|
||||
|
||||
if frac >= 1.0:
|
||||
minIndex = np.argmin(d)
|
||||
|
@ -85,7 +85,7 @@ class UIGraphicsItem(GraphicsObject):
|
||||
self._boundingRect = br
|
||||
return QtCore.QRectF(self._boundingRect)
|
||||
|
||||
def dataBounds(self, axis, frac=1.0):
|
||||
def dataBounds(self, axis, frac=1.0, orthoRange=None):
|
||||
"""Called by ViewBox for determining the auto-range bounds.
|
||||
By default, UIGraphicsItems are excluded from autoRange."""
|
||||
return None
|
||||
|
@ -97,6 +97,8 @@ class ViewBox(GraphicsWidget):
|
||||
'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio.
|
||||
'autoRange': [True, True], ## False if auto range is disabled,
|
||||
## otherwise float gives the fraction of data that is visible
|
||||
'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled
|
||||
'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot
|
||||
'linkedViews': [None, None],
|
||||
|
||||
'mouseEnabled': [enableMouse, enableMouse],
|
||||
@ -368,8 +370,11 @@ class ViewBox(GraphicsWidget):
|
||||
link = self.state['linkedViews'][ax]
|
||||
if link is not None:
|
||||
link.linkedViewChanged(self, ax)
|
||||
|
||||
|
||||
if changed[0] and self.state['autoVisibleOnly'][1]:
|
||||
self.updateAutoRange()
|
||||
elif changed[1] and self.state['autoVisibleOnly'][0]:
|
||||
self.updateAutoRange()
|
||||
|
||||
def setYRange(self, min, max, padding=0.02, update=True):
|
||||
"""
|
||||
@ -466,8 +471,29 @@ class ViewBox(GraphicsWidget):
|
||||
def autoRangeEnabled(self):
|
||||
return self.state['autoRange'][:]
|
||||
|
||||
def setAutoPan(self, x=None, y=None):
|
||||
if x is not None:
|
||||
self.state['autoPan'][0] = x
|
||||
if y is not None:
|
||||
self.state['autoPan'][1] = y
|
||||
if None not in [x,y]:
|
||||
self.updateAutoRange()
|
||||
|
||||
def setAutoVisible(self, x=None, y=None):
|
||||
if x is not None:
|
||||
self.state['autoVisibleOnly'][0] = x
|
||||
if x is True:
|
||||
self.state['autoVisibleOnly'][1] = False
|
||||
if y is not None:
|
||||
self.state['autoVisibleOnly'][1] = y
|
||||
if y is True:
|
||||
self.state['autoVisibleOnly'][0] = False
|
||||
|
||||
if x is not None or y is not None:
|
||||
self.updateAutoRange()
|
||||
|
||||
def updateAutoRange(self):
|
||||
tr = self.viewRect()
|
||||
targetRect = self.viewRange()
|
||||
if not any(self.state['autoRange']):
|
||||
return
|
||||
|
||||
@ -475,19 +501,53 @@ class ViewBox(GraphicsWidget):
|
||||
for i in [0,1]:
|
||||
if type(fractionVisible[i]) is bool:
|
||||
fractionVisible[i] = 1.0
|
||||
cr = self.childrenBoundingRect(frac=fractionVisible)
|
||||
wp = cr.width() * 0.02
|
||||
hp = cr.height() * 0.02
|
||||
cr = cr.adjusted(-wp, -hp, wp, hp)
|
||||
|
||||
if self.state['autoRange'][0] is not False:
|
||||
tr.setLeft(cr.left())
|
||||
tr.setRight(cr.right())
|
||||
if self.state['autoRange'][1] is not False:
|
||||
tr.setTop(cr.top())
|
||||
tr.setBottom(cr.bottom())
|
||||
|
||||
childRect = None
|
||||
|
||||
order = [0,1]
|
||||
if self.state['autoVisibleOnly'][0] is True:
|
||||
order = [1,0]
|
||||
|
||||
for ax in order:
|
||||
if self.state['autoRange'][ax] is False:
|
||||
continue
|
||||
if self.state['autoVisibleOnly'][ax]:
|
||||
oRange = [None, None]
|
||||
oRange[ax] = targetRect[1-ax]
|
||||
childRect = self.childrenBoundingRect(frac=fractionVisible, orthoRange=oRange)
|
||||
|
||||
else:
|
||||
if childRect is None:
|
||||
childRect = self.childrenBoundingRect(frac=fractionVisible)
|
||||
|
||||
if ax == 0:
|
||||
## Make corrections to X range
|
||||
if self.state['autoPan'][0]:
|
||||
x = childRect.center().x()
|
||||
w2 = (targetRect[0][1]-targetRect[0][0]) / 2.
|
||||
childRect.setLeft(x-w2)
|
||||
childRect.setRight(x+w2)
|
||||
else:
|
||||
wp = childRect.width() * 0.02
|
||||
childRect = childRect.adjusted(-wp, 0, wp, 0)
|
||||
|
||||
targetRect[0][0] = childRect.left()
|
||||
targetRect[0][1] = childRect.right()
|
||||
else:
|
||||
## Make corrections to Y range
|
||||
if self.state['autoPan'][1]:
|
||||
y = childRect.center().y()
|
||||
h2 = (targetRect[1][1]-targetRect[1][0]) / 2.
|
||||
childRect.setTop(y-h2)
|
||||
childRect.setBottom(y+h2)
|
||||
else:
|
||||
hp = childRect.height() * 0.02
|
||||
childRect = childRect.adjusted(0, -hp, 0, hp)
|
||||
|
||||
targetRect[1][0] = childRect.top()
|
||||
targetRect[1][1] = childRect.bottom()
|
||||
|
||||
self.setRange(tr, padding=0, disableAutoRange=False)
|
||||
self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
|
||||
|
||||
def setXLink(self, view):
|
||||
"""Link this view's X axis to another view. (see LinkView)"""
|
||||
@ -855,7 +915,7 @@ class ViewBox(GraphicsWidget):
|
||||
|
||||
|
||||
|
||||
def childrenBoundingRect(self, frac=None):
|
||||
def childrenBoundingRect(self, frac=None, orthoRange=(None,None)):
|
||||
"""Return the bounding range of all children.
|
||||
[[xmin, xmax], [ymin, ymax]]
|
||||
Values may be None if there are no specific bounds for an axis.
|
||||
@ -880,8 +940,8 @@ class ViewBox(GraphicsWidget):
|
||||
if hasattr(item, 'dataBounds'):
|
||||
if frac is None:
|
||||
frac = (1.0, 1.0)
|
||||
xr = item.dataBounds(0, frac=frac[0])
|
||||
yr = item.dataBounds(1, frac=frac[1])
|
||||
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)
|
||||
|
@ -110,6 +110,8 @@ class ViewBoxMenu(QtGui.QMenu):
|
||||
self.ctrl[i].maxText.setText("%0.5g" % tr[1])
|
||||
if state['autoRange'][i] is not False:
|
||||
self.ctrl[i].autoRadio.setChecked(True)
|
||||
if state['autoRange'][i] is not True:
|
||||
self.ctrl[i].autoPercentSpin.setValue(state['autoRange'][i]*100)
|
||||
else:
|
||||
self.ctrl[i].manualRadio.setChecked(True)
|
||||
self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i])
|
||||
@ -132,6 +134,8 @@ class ViewBoxMenu(QtGui.QMenu):
|
||||
finally:
|
||||
c.blockSignals(False)
|
||||
|
||||
self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i])
|
||||
self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i])
|
||||
|
||||
self.valid = True
|
||||
|
||||
@ -165,10 +169,10 @@ class ViewBoxMenu(QtGui.QMenu):
|
||||
self.view.setXLink(str(self.ctrl[0].linkCombo.currentText()))
|
||||
|
||||
def xAutoPanToggled(self, b):
|
||||
pass
|
||||
self.view.setAutoPan(x=b)
|
||||
|
||||
def xVisibleOnlyToggled(self, b):
|
||||
pass
|
||||
self.view.setAutoVisible(x=b)
|
||||
|
||||
|
||||
def yMouseToggled(self, b):
|
||||
@ -197,10 +201,10 @@ class ViewBoxMenu(QtGui.QMenu):
|
||||
self.view.setYLink(str(self.ctrl[1].linkCombo.currentText()))
|
||||
|
||||
def yAutoPanToggled(self, b):
|
||||
pass
|
||||
self.view.setAutoPan(y=b)
|
||||
|
||||
def yVisibleOnlyToggled(self, b):
|
||||
pass
|
||||
self.view.setAutoVisible(y=b)
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user