dynamic range limiting in PlotDataItem (#1140)
* dynamic range limiting in PlotDataItem * revised version of cynamic range limiting * replaced == with is operator * removed unicode +- character, converted to ascii * code/docstring cleanup * clean state with changes * silenced numpy all-NaN warnings * reverted PlotWidget.py to original * reverted PlotWidget.py to original * reverted PlotWidget.py to original * rewrapped/reformated setDynamicRangeLimits docstring Co-authored-by: Ogi Moore <ognyan.moore@gmail.com>
This commit is contained in:
parent
39f9c6a6aa
commit
65e90faec5
@ -6,6 +6,7 @@ try:
|
||||
except:
|
||||
HAVE_OPENGL = False
|
||||
|
||||
import warnings
|
||||
import numpy as np
|
||||
from .GraphicsObject import GraphicsObject
|
||||
from .. import functions as fn
|
||||
@ -148,6 +149,9 @@ class PlotCurveItem(GraphicsObject):
|
||||
if frac >= 1.0:
|
||||
# include complete data range
|
||||
# first try faster nanmin/max function, then cut out infs if needed.
|
||||
with warnings.catch_warnings():
|
||||
# All-NaN data is acceptable; Explicit numpy warning is not needed.
|
||||
warnings.simplefilter("ignore")
|
||||
b = (np.nanmin(d), np.nanmax(d))
|
||||
if any(np.isinf(b)):
|
||||
mask = np.isfinite(d)
|
||||
|
@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import warnings
|
||||
import numpy as np
|
||||
from .. import metaarray as metaarray
|
||||
from ..Qt import QtCore
|
||||
@ -52,6 +53,7 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
**Data initialization arguments:** (x,y data AND may include spot style)
|
||||
|
||||
|
||||
============================ =========================================
|
||||
PlotDataItem(recarray) numpy array with ``dtype=[('x', float),
|
||||
('y', float), ...]``
|
||||
@ -73,6 +75,7 @@ class PlotDataItem(GraphicsObject):
|
||||
shadowPen Pen for secondary line to draw behind the primary line. disabled by default.
|
||||
May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>`
|
||||
fillLevel Fill the area between the curve and fillLevel
|
||||
|
||||
fillOutline (bool) If True, an outline surrounding the *fillLevel* area is drawn.
|
||||
fillBrush Fill to use when fillLevel is specified.
|
||||
May be any single argument accepted by :func:`mkBrush() <pyqtgraph.mkBrush>`
|
||||
@ -88,6 +91,7 @@ class PlotDataItem(GraphicsObject):
|
||||
step mode is not enabled.
|
||||
Passing True is a deprecated equivalent to "center".
|
||||
(added in version 0.9.9)
|
||||
|
||||
============ ==============================================================================
|
||||
|
||||
**Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() <pyqtgraph.ScatterPlotItem.setData>` for more information)
|
||||
@ -110,7 +114,7 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
**Optimization keyword arguments:**
|
||||
|
||||
================ =====================================================================
|
||||
================= =====================================================================
|
||||
antialias (bool) By default, antialiasing is disabled to improve performance.
|
||||
Note that in some cases (in particluar, when pxMode=True), points
|
||||
will be rendered antialiased even if this is set to False.
|
||||
@ -130,8 +134,10 @@ class PlotDataItem(GraphicsObject):
|
||||
the containing ViewBox. This can improve performance when plotting
|
||||
very large data sets where only a fraction of the data is visible
|
||||
at any time.
|
||||
dynamicRangeLimit (float or None) Limit off-screen positions of data points at large
|
||||
magnification to avoids display errors. Disabled if None.
|
||||
identical *deprecated*
|
||||
================ =====================================================================
|
||||
================= =====================================================================
|
||||
|
||||
**Meta-info keyword arguments:**
|
||||
|
||||
@ -156,7 +162,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.curve.sigClicked.connect(self.curveClicked)
|
||||
self.scatter.sigClicked.connect(self.scatterClicked)
|
||||
|
||||
|
||||
self._dataRect = None
|
||||
#self.clear()
|
||||
self.opts = {
|
||||
'connect': 'all',
|
||||
@ -189,6 +195,7 @@ class PlotDataItem(GraphicsObject):
|
||||
'downsampleMethod': 'peak',
|
||||
'autoDownsampleFactor': 5., # draw ~5 samples per pixel
|
||||
'clipToView': False,
|
||||
'dynamicRangeLimit': 1e6,
|
||||
|
||||
'data': None,
|
||||
}
|
||||
@ -232,6 +239,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.updateItems()
|
||||
self.informViewBoundsChanged()
|
||||
|
||||
|
||||
def setDerivativeMode(self, mode):
|
||||
if self.opts['derivativeMode'] == mode:
|
||||
return
|
||||
@ -315,8 +323,6 @@ class PlotDataItem(GraphicsObject):
|
||||
#self.scatter.setSymbolPen(pen)
|
||||
self.updateItems()
|
||||
|
||||
|
||||
|
||||
def setSymbolBrush(self, *args, **kargs):
|
||||
brush = fn.mkBrush(*args, **kargs)
|
||||
if self.opts['symbolBrush'] == brush:
|
||||
@ -377,6 +383,24 @@ class PlotDataItem(GraphicsObject):
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
def setDynamicRangeLimit(self, limit):
|
||||
"""
|
||||
Limit the off-screen positions of data points at large magnification
|
||||
This avoids errors with plots not displaying because their visibility is incorrectly determined. The default setting repositions far-off points to be within +-1E+06 times the viewport height.
|
||||
|
||||
=============== ================================================================
|
||||
**Arguments:**
|
||||
limit (float or None) Maximum allowed vertical distance of plotted
|
||||
points in units of viewport height.
|
||||
'None' disables the check for a minimal increase in performance.
|
||||
Default is 1E+06.
|
||||
=============== ================================================================
|
||||
"""
|
||||
if limit == self.opts['dynamicRangeLimit']:
|
||||
return # avoid update if there is no change
|
||||
self.opts['dynamicRangeLimit'] = limit # can be None
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
def setData(self, *args, **kargs):
|
||||
"""
|
||||
@ -497,6 +521,7 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by
|
||||
self.yData = y.view(np.ndarray)
|
||||
self._dataRect = None
|
||||
self.xClean = self.yClean = None
|
||||
self.xDisp = None
|
||||
self.yDisp = None
|
||||
@ -534,6 +559,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.curve.hide()
|
||||
|
||||
if scatterArgs['symbol'] is not None:
|
||||
|
||||
## check against `True` too for backwards compatibility
|
||||
if self.opts.get('stepMode', False) in ("center", True):
|
||||
x = 0.5 * (x[:-1] + x[1:])
|
||||
@ -557,6 +583,7 @@ class PlotDataItem(GraphicsObject):
|
||||
if self.opts['logMode'][0]:
|
||||
x=x[1:]
|
||||
y=y[1:]
|
||||
|
||||
if self.opts['derivativeMode']: # plot dV/dt
|
||||
y = np.diff(self.yData)/np.diff(self.xData)
|
||||
x = x[:-1]
|
||||
@ -609,7 +636,6 @@ class PlotDataItem(GraphicsObject):
|
||||
if x[x1] < range.right():
|
||||
x1 = np.searchsorted(x, range.right()) + 2*ds
|
||||
x1 = np.clip(x1, a_min=0, a_max=len(x))
|
||||
|
||||
x = x[x0:x1]
|
||||
y = y[x0:x1]
|
||||
|
||||
@ -632,11 +658,47 @@ class PlotDataItem(GraphicsObject):
|
||||
y1[:,1] = y2.min(axis=1)
|
||||
y = y1.reshape(n*2)
|
||||
|
||||
if self.opts['dynamicRangeLimit'] is not None:
|
||||
view_range = self.viewRect()
|
||||
if view_range is not None:
|
||||
data_range = self.dataRect()
|
||||
if data_range is not None:
|
||||
view_height = view_range.height()
|
||||
lim = self.opts['dynamicRangeLimit']
|
||||
if data_range.height() > lim * view_height:
|
||||
min_val = view_range.top() - lim * view_height
|
||||
max_val = view_range.bottom() + lim * view_height
|
||||
y = np.clip(y, a_min=min_val, a_max=max_val)
|
||||
|
||||
self.xDisp = x
|
||||
self.yDisp = y
|
||||
return self.xDisp, self.yDisp
|
||||
|
||||
def dataRect(self):
|
||||
"""
|
||||
Returns a bounding rectangle (as QRectF) for the full set of data.
|
||||
Will return None if there is no data or if all values (x or y) are NaN.
|
||||
"""
|
||||
if self._dataRect is not None:
|
||||
return self._dataRect
|
||||
if self.xData is None or self.yData is None:
|
||||
return None
|
||||
with warnings.catch_warnings():
|
||||
# All-NaN data is handled by returning None; Explicit numpy warning is not needed.
|
||||
warnings.simplefilter("ignore")
|
||||
ymin = np.nanmin(self.yData)
|
||||
if np.isnan( ymin ):
|
||||
return None # most likely case for all-NaN data
|
||||
xmin = np.nanmin(self.xData)
|
||||
if np.isnan( xmin ):
|
||||
return None # less likely case for all-NaN data
|
||||
ymax = np.nanmax(self.yData)
|
||||
xmax = np.nanmax(self.xData)
|
||||
self._dataRect = QtCore.QRectF(
|
||||
QtCore.QPointF(xmin,ymin),
|
||||
QtCore.QPointF(xmax,ymax) )
|
||||
return self._dataRect
|
||||
|
||||
def dataBounds(self, ax, frac=1.0, orthoRange=None):
|
||||
"""
|
||||
Returns the range occupied by the data (along a specific axis) in this item.
|
||||
@ -693,6 +755,7 @@ class PlotDataItem(GraphicsObject):
|
||||
#self.yClean = None
|
||||
self.xDisp = None
|
||||
self.yDisp = None
|
||||
self._dataRect = None
|
||||
self.curve.clear()
|
||||
self.scatter.clear()
|
||||
|
||||
@ -708,7 +771,10 @@ class PlotDataItem(GraphicsObject):
|
||||
|
||||
def viewRangeChanged(self):
|
||||
# view range has changed; re-plot if needed
|
||||
if self.opts['clipToView'] or self.opts['autoDownsample']:
|
||||
if( self.opts['clipToView']
|
||||
or self.opts['autoDownsample']
|
||||
or self.opts['dynamicRangeLimit'] is not None
|
||||
):
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user