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,7 +149,10 @@ class PlotCurveItem(GraphicsObject):
|
||||
if frac >= 1.0:
|
||||
# include complete data range
|
||||
# first try faster nanmin/max function, then cut out infs if needed.
|
||||
b = (np.nanmin(d), np.nanmax(d))
|
||||
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)
|
||||
d = d[mask]
|
||||
|
@ -1,4 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import warnings
|
||||
import numpy as np
|
||||
from .. import metaarray as metaarray
|
||||
from ..Qt import QtCore
|
||||
@ -13,33 +14,33 @@ from .. import getConfigOption
|
||||
class PlotDataItem(GraphicsObject):
|
||||
"""
|
||||
**Bases:** :class:`GraphicsObject <pyqtgraph.GraphicsObject>`
|
||||
|
||||
GraphicsItem for displaying plot curves, scatter plots, or both.
|
||||
|
||||
GraphicsItem for displaying plot curves, scatter plots, or both.
|
||||
While it is possible to use :class:`PlotCurveItem <pyqtgraph.PlotCurveItem>` or
|
||||
:class:`ScatterPlotItem <pyqtgraph.ScatterPlotItem>` individually, this class
|
||||
provides a unified interface to both. Instances of :class:`PlotDataItem` are
|
||||
provides a unified interface to both. Instances of :class:`PlotDataItem` are
|
||||
usually created by plot() methods such as :func:`pyqtgraph.plot` and
|
||||
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`.
|
||||
|
||||
|
||||
============================== ==============================================
|
||||
**Signals:**
|
||||
sigPlotChanged(self) Emitted when the data in this item is updated.
|
||||
sigPlotChanged(self) Emitted when the data in this item is updated.
|
||||
sigClicked(self) Emitted when the item is clicked.
|
||||
sigPointsClicked(self, points) Emitted when a plot point is clicked
|
||||
Sends the list of points under the mouse.
|
||||
============================== ==============================================
|
||||
"""
|
||||
|
||||
|
||||
sigPlotChanged = QtCore.Signal(object)
|
||||
sigClicked = QtCore.Signal(object)
|
||||
sigPointsClicked = QtCore.Signal(object, object)
|
||||
|
||||
|
||||
def __init__(self, *args, **kargs):
|
||||
"""
|
||||
There are many different ways to create a PlotDataItem:
|
||||
|
||||
|
||||
**Data initialization arguments:** (x,y data only)
|
||||
|
||||
|
||||
=================================== ======================================
|
||||
PlotDataItem(xValues, yValues) x and y values may be any sequence
|
||||
(including ndarray) of real numbers
|
||||
@ -49,8 +50,9 @@ class PlotDataItem(GraphicsObject):
|
||||
PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where
|
||||
``x=data[:,0]`` and ``y=data[:,1]``
|
||||
=================================== ======================================
|
||||
|
||||
|
||||
**Data initialization arguments:** (x,y data AND may include spot style)
|
||||
|
||||
|
||||
============================ =========================================
|
||||
PlotDataItem(recarray) numpy array with ``dtype=[('x', 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,53 +91,56 @@ 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)
|
||||
|
||||
|
||||
============ =====================================================
|
||||
symbol Symbol to use for drawing points OR list of symbols,
|
||||
symbol Symbol to use for drawing points OR list of symbols,
|
||||
one per point. Default is no symbol.
|
||||
Options are o, s, t, d, +, or any QPainterPath
|
||||
symbolPen Outline pen for drawing points OR list of pens, one
|
||||
per point. May be any single argument accepted by
|
||||
symbolPen Outline pen for drawing points OR list of pens, one
|
||||
per point. May be any single argument accepted by
|
||||
:func:`mkPen() <pyqtgraph.mkPen>`
|
||||
symbolBrush Brush for filling points OR list of brushes, one per
|
||||
point. May be any single argument accepted by
|
||||
symbolBrush Brush for filling points OR list of brushes, one per
|
||||
point. May be any single argument accepted by
|
||||
:func:`mkBrush() <pyqtgraph.mkBrush>`
|
||||
symbolSize Diameter of symbols OR list of diameters.
|
||||
pxMode (bool) If True, then symbolSize is specified in
|
||||
pixels. If False, then symbolSize is
|
||||
pxMode (bool) If True, then symbolSize is specified in
|
||||
pixels. If False, then symbolSize is
|
||||
specified in data coordinates.
|
||||
============ =====================================================
|
||||
|
||||
|
||||
**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.
|
||||
decimate deprecated.
|
||||
downsample (int) Reduce the number of samples displayed by this value
|
||||
downsampleMethod 'subsample': Downsample by taking the first of N samples.
|
||||
This method is fastest and least accurate.
|
||||
'mean': Downsample by taking the mean of N samples.
|
||||
'peak': Downsample by drawing a saw wave that follows the min
|
||||
and max of the original data. This method produces the best
|
||||
visual representation of the data but is slower.
|
||||
autoDownsample (bool) If True, resample the data before plotting to avoid plotting
|
||||
multiple line segments per pixel. This can improve performance when
|
||||
viewing very high-density data, but increases the initial overhead
|
||||
and memory usage.
|
||||
clipToView (bool) If True, only plot data that is visible within the X range of
|
||||
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.
|
||||
identical *deprecated*
|
||||
================ =====================================================================
|
||||
|
||||
|
||||
================= =====================================================================
|
||||
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.
|
||||
decimate deprecated.
|
||||
downsample (int) Reduce the number of samples displayed by this value
|
||||
downsampleMethod 'subsample': Downsample by taking the first of N samples.
|
||||
This method is fastest and least accurate.
|
||||
'mean': Downsample by taking the mean of N samples.
|
||||
'peak': Downsample by drawing a saw wave that follows the min
|
||||
and max of the original data. This method produces the best
|
||||
visual representation of the data but is slower.
|
||||
autoDownsample (bool) If True, resample the data before plotting to avoid plotting
|
||||
multiple line segments per pixel. This can improve performance when
|
||||
viewing very high-density data, but increases the initial overhead
|
||||
and memory usage.
|
||||
clipToView (bool) If True, only plot data that is visible within the X range of
|
||||
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:**
|
||||
|
||||
|
||||
========== ================================================
|
||||
name name of dataset. This would appear in a legend
|
||||
========== ================================================
|
||||
@ -152,57 +158,58 @@ class PlotDataItem(GraphicsObject):
|
||||
self.scatter = ScatterPlotItem()
|
||||
self.curve.setParentItem(self)
|
||||
self.scatter.setParentItem(self)
|
||||
|
||||
|
||||
self.curve.sigClicked.connect(self.curveClicked)
|
||||
self.scatter.sigClicked.connect(self.scatterClicked)
|
||||
|
||||
|
||||
|
||||
self._dataRect = None
|
||||
#self.clear()
|
||||
self.opts = {
|
||||
'connect': 'all',
|
||||
|
||||
|
||||
'fftMode': False,
|
||||
'logMode': [False, False],
|
||||
'derivativeMode': False,
|
||||
'phasemapMode': False,
|
||||
'alphaHint': 1.0,
|
||||
'alphaMode': False,
|
||||
|
||||
|
||||
'pen': (200,200,200),
|
||||
'shadowPen': None,
|
||||
'fillLevel': None,
|
||||
'fillOutline': False,
|
||||
'fillBrush': None,
|
||||
'stepMode': None,
|
||||
|
||||
'stepMode': None,
|
||||
|
||||
'symbol': None,
|
||||
'symbolSize': 10,
|
||||
'symbolPen': (200,200,200),
|
||||
'symbolBrush': (50, 50, 150),
|
||||
'pxMode': True,
|
||||
|
||||
|
||||
'antialias': getConfigOption('antialias'),
|
||||
'pointMode': None,
|
||||
|
||||
|
||||
'downsample': 1,
|
||||
'autoDownsample': False,
|
||||
'downsampleMethod': 'peak',
|
||||
'autoDownsampleFactor': 5., # draw ~5 samples per pixel
|
||||
'clipToView': False,
|
||||
|
||||
'dynamicRangeLimit': 1e6,
|
||||
|
||||
'data': None,
|
||||
}
|
||||
self.setData(*args, **kargs)
|
||||
|
||||
|
||||
def implements(self, interface=None):
|
||||
ints = ['plotData']
|
||||
if interface is None:
|
||||
return ints
|
||||
return interface in ints
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.opts.get('name', None)
|
||||
|
||||
|
||||
def boundingRect(self):
|
||||
return QtCore.QRectF() ## let child items handle this
|
||||
|
||||
@ -213,7 +220,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.opts['alphaMode'] = auto
|
||||
self.setOpacity(alpha)
|
||||
#self.update()
|
||||
|
||||
|
||||
def setFftMode(self, mode):
|
||||
if self.opts['fftMode'] == mode:
|
||||
return
|
||||
@ -222,7 +229,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.xClean = self.yClean = None
|
||||
self.updateItems()
|
||||
self.informViewBoundsChanged()
|
||||
|
||||
|
||||
def setLogMode(self, xMode, yMode):
|
||||
if self.opts['logMode'] == [xMode, yMode]:
|
||||
return
|
||||
@ -232,6 +239,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.updateItems()
|
||||
self.informViewBoundsChanged()
|
||||
|
||||
|
||||
def setDerivativeMode(self, mode):
|
||||
if self.opts['derivativeMode'] == mode:
|
||||
return
|
||||
@ -255,7 +263,7 @@ class PlotDataItem(GraphicsObject):
|
||||
return
|
||||
self.opts['pointMode'] = mode
|
||||
self.update()
|
||||
|
||||
|
||||
def setPen(self, *args, **kargs):
|
||||
"""
|
||||
| Sets the pen used to draw lines between points.
|
||||
@ -268,11 +276,11 @@ class PlotDataItem(GraphicsObject):
|
||||
#c.setPen(pen)
|
||||
#self.update()
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setShadowPen(self, *args, **kargs):
|
||||
"""
|
||||
| Sets the shadow pen used to draw lines between points (this is for enhancing contrast or
|
||||
emphacizing data).
|
||||
| Sets the shadow pen used to draw lines between points (this is for enhancing contrast or
|
||||
emphacizing data).
|
||||
| This line is drawn behind the primary pen (see :func:`setPen() <pyqtgraph.PlotDataItem.setPen>`)
|
||||
and should generally be assigned greater width than the primary pen.
|
||||
| *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() <pyqtgraph.mkPen>`
|
||||
@ -283,17 +291,17 @@ class PlotDataItem(GraphicsObject):
|
||||
#c.setPen(pen)
|
||||
#self.update()
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setFillBrush(self, *args, **kargs):
|
||||
brush = fn.mkBrush(*args, **kargs)
|
||||
if self.opts['fillBrush'] == brush:
|
||||
return
|
||||
self.opts['fillBrush'] = brush
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setBrush(self, *args, **kargs):
|
||||
return self.setFillBrush(*args, **kargs)
|
||||
|
||||
|
||||
def setFillLevel(self, level):
|
||||
if self.opts['fillLevel'] == level:
|
||||
return
|
||||
@ -306,7 +314,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.opts['symbol'] = symbol
|
||||
#self.scatter.setSymbol(symbol)
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setSymbolPen(self, *args, **kargs):
|
||||
pen = fn.mkPen(*args, **kargs)
|
||||
if self.opts['symbolPen'] == pen:
|
||||
@ -314,9 +322,7 @@ class PlotDataItem(GraphicsObject):
|
||||
self.opts['symbolPen'] = pen
|
||||
#self.scatter.setSymbolPen(pen)
|
||||
self.updateItems()
|
||||
|
||||
|
||||
|
||||
|
||||
def setSymbolBrush(self, *args, **kargs):
|
||||
brush = fn.mkBrush(*args, **kargs)
|
||||
if self.opts['symbolBrush'] == brush:
|
||||
@ -324,8 +330,8 @@ class PlotDataItem(GraphicsObject):
|
||||
self.opts['symbolBrush'] = brush
|
||||
#self.scatter.setSymbolBrush(brush)
|
||||
self.updateItems()
|
||||
|
||||
|
||||
|
||||
|
||||
def setSymbolSize(self, size):
|
||||
if self.opts['symbolSize'] == size:
|
||||
return
|
||||
@ -336,8 +342,8 @@ class PlotDataItem(GraphicsObject):
|
||||
def setDownsampling(self, ds=None, auto=None, method=None):
|
||||
"""
|
||||
Set the downsampling mode of this item. Downsampling reduces the number
|
||||
of samples drawn to increase performance.
|
||||
|
||||
of samples drawn to increase performance.
|
||||
|
||||
============== =================================================================
|
||||
**Arguments:**
|
||||
ds (int) Reduce visible plot samples by this factor. To disable,
|
||||
@ -356,28 +362,46 @@ class PlotDataItem(GraphicsObject):
|
||||
if self.opts['downsample'] != ds:
|
||||
changed = True
|
||||
self.opts['downsample'] = ds
|
||||
|
||||
|
||||
if auto is not None and self.opts['autoDownsample'] != auto:
|
||||
self.opts['autoDownsample'] = auto
|
||||
changed = True
|
||||
|
||||
|
||||
if method is not None:
|
||||
if self.opts['downsampleMethod'] != method:
|
||||
changed = True
|
||||
self.opts['downsampleMethod'] = method
|
||||
|
||||
|
||||
if changed:
|
||||
self.xDisp = self.yDisp = None
|
||||
self.updateItems()
|
||||
|
||||
|
||||
def setClipToView(self, clip):
|
||||
if self.opts['clipToView'] == clip:
|
||||
return
|
||||
self.opts['clipToView'] = clip
|
||||
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):
|
||||
"""
|
||||
Clear any data displayed by this item and display new data.
|
||||
@ -421,7 +445,7 @@ class PlotDataItem(GraphicsObject):
|
||||
x = data.xvals(0).view(np.ndarray)
|
||||
else:
|
||||
raise Exception('Invalid data type %s' % type(data))
|
||||
|
||||
|
||||
elif len(args) == 2:
|
||||
seq = ('listOfValues', 'MetaArray', 'empty')
|
||||
dtyp = dataType(args[0]), dataType(args[1])
|
||||
@ -443,45 +467,45 @@ class PlotDataItem(GraphicsObject):
|
||||
y = np.array(args[1])
|
||||
else:
|
||||
y = args[1].view(np.ndarray)
|
||||
|
||||
|
||||
if 'x' in kargs:
|
||||
x = kargs['x']
|
||||
if 'y' in kargs:
|
||||
y = kargs['y']
|
||||
|
||||
profiler('interpret data')
|
||||
## pull in all style arguments.
|
||||
## pull in all style arguments.
|
||||
## Use self.opts to fill in anything not present in kargs.
|
||||
|
||||
|
||||
if 'name' in kargs:
|
||||
self.opts['name'] = kargs['name']
|
||||
if 'connect' in kargs:
|
||||
self.opts['connect'] = kargs['connect']
|
||||
|
||||
## if symbol pen/brush are given with no symbol, then assume symbol is 'o'
|
||||
|
||||
|
||||
if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs):
|
||||
kargs['symbol'] = 'o'
|
||||
|
||||
|
||||
if 'brush' in kargs:
|
||||
kargs['fillBrush'] = kargs['brush']
|
||||
|
||||
|
||||
for k in list(self.opts.keys()):
|
||||
if k in kargs:
|
||||
self.opts[k] = kargs[k]
|
||||
|
||||
|
||||
#curveArgs = {}
|
||||
#for k in ['pen', 'shadowPen', 'fillLevel', 'brush']:
|
||||
#if k in kargs:
|
||||
#self.opts[k] = kargs[k]
|
||||
#curveArgs[k] = self.opts[k]
|
||||
|
||||
|
||||
#scatterArgs = {}
|
||||
#for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]:
|
||||
#if k in kargs:
|
||||
#self.opts[k] = kargs[k]
|
||||
#scatterArgs[v] = self.opts[k]
|
||||
|
||||
|
||||
|
||||
if y is None:
|
||||
self.updateItems()
|
||||
@ -489,51 +513,53 @@ class PlotDataItem(GraphicsObject):
|
||||
return
|
||||
if y is not None and x is None:
|
||||
x = np.arange(len(y))
|
||||
|
||||
|
||||
if not isinstance(x, np.ndarray):
|
||||
x = np.array(x)
|
||||
if not isinstance(y, np.ndarray):
|
||||
y = np.array(y)
|
||||
|
||||
|
||||
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
|
||||
profiler('set data')
|
||||
|
||||
|
||||
self.updateItems()
|
||||
profiler('update items')
|
||||
|
||||
|
||||
self.informViewBoundsChanged()
|
||||
#view = self.getViewBox()
|
||||
#if view is not None:
|
||||
#view.itemBoundsChanged(self) ## inform view so it can update its range if it wants
|
||||
|
||||
|
||||
self.sigPlotChanged.emit(self)
|
||||
profiler('emit')
|
||||
|
||||
def updateItems(self):
|
||||
|
||||
|
||||
curveArgs = {}
|
||||
for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillOutline', 'fillOutline'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode')]:
|
||||
curveArgs[v] = self.opts[k]
|
||||
|
||||
|
||||
scatterArgs = {}
|
||||
for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]:
|
||||
if k in self.opts:
|
||||
scatterArgs[v] = self.opts[k]
|
||||
|
||||
|
||||
x,y = self.getData()
|
||||
#scatterArgs['mask'] = self.dataMask
|
||||
|
||||
|
||||
if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None):
|
||||
self.curve.setData(x=x, y=y, **curveArgs)
|
||||
self.curve.show()
|
||||
else:
|
||||
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:])
|
||||
@ -546,17 +572,18 @@ class PlotDataItem(GraphicsObject):
|
||||
def getData(self):
|
||||
if self.xData is None:
|
||||
return (None, None)
|
||||
|
||||
|
||||
if self.xDisp is None:
|
||||
x = self.xData
|
||||
y = self.yData
|
||||
|
||||
|
||||
if self.opts['fftMode']:
|
||||
x,y = self._fourierTransform(x, y)
|
||||
# Ignore the first bin for fft data if we have a logx scale
|
||||
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]
|
||||
@ -569,11 +596,11 @@ class PlotDataItem(GraphicsObject):
|
||||
x = np.log10(x)
|
||||
if self.opts['logMode'][1]:
|
||||
y = np.log10(y)
|
||||
|
||||
|
||||
ds = self.opts['downsample']
|
||||
if not isinstance(ds, int):
|
||||
ds = 1
|
||||
|
||||
|
||||
if self.opts['autoDownsample']:
|
||||
# this option presumes that x-values have uniform spacing
|
||||
range = self.viewRect()
|
||||
@ -586,7 +613,7 @@ class PlotDataItem(GraphicsObject):
|
||||
if width != 0.0:
|
||||
ds = int(max(1, int((x1-x0) / (width*self.opts['autoDownsampleFactor']))))
|
||||
## downsampling is expensive; delay until after clipping.
|
||||
|
||||
|
||||
if self.opts['clipToView']:
|
||||
view = self.getViewBox()
|
||||
if view is None or not view.autoRangeEnabled()[0]:
|
||||
@ -598,8 +625,8 @@ class PlotDataItem(GraphicsObject):
|
||||
dx = float(x[-1]-x[0]) / (len(x)-1)
|
||||
x0 = np.clip(int((range.left()-x[0])/dx) - 1*ds, 0, len(x)-1)
|
||||
x1 = np.clip(int((range.right()-x[0])/dx) + 2*ds, 0, len(x)-1)
|
||||
|
||||
# if data has been clipped too strongly (in case of non-uniform
|
||||
|
||||
# if data has been clipped too strongly (in case of non-uniform
|
||||
# spacing of x-values), refine the clipping region as required
|
||||
# worst case performance: O(log(n))
|
||||
# best case performance: O(1)
|
||||
@ -609,10 +636,9 @@ 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]
|
||||
|
||||
|
||||
if ds > 1:
|
||||
if self.opts['downsampleMethod'] == 'subsample':
|
||||
x = x[::ds]
|
||||
@ -631,12 +657,48 @@ class PlotDataItem(GraphicsObject):
|
||||
y1[:,0] = y2.max(axis=1)
|
||||
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.
|
||||
@ -645,18 +707,18 @@ class PlotDataItem(GraphicsObject):
|
||||
=============== =============================================================
|
||||
**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
|
||||
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
|
||||
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)
|
||||
=============== =============================================================
|
||||
"""
|
||||
|
||||
|
||||
range = [None, None]
|
||||
if self.curve.isVisible():
|
||||
range = self.curve.dataBounds(ax, frac, orthoRange)
|
||||
@ -667,7 +729,7 @@ class PlotDataItem(GraphicsObject):
|
||||
r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1]))
|
||||
]
|
||||
return range
|
||||
|
||||
|
||||
def pixelPadding(self):
|
||||
"""
|
||||
Return the size in pixels that this item may draw beyond the values returned by dataBounds().
|
||||
@ -679,7 +741,7 @@ class PlotDataItem(GraphicsObject):
|
||||
elif self.scatter.isVisible():
|
||||
pad = max(pad, self.scatter.pixelPadding())
|
||||
return pad
|
||||
|
||||
|
||||
|
||||
def clear(self):
|
||||
#for i in self.curves+self.scatters:
|
||||
@ -693,25 +755,29 @@ class PlotDataItem(GraphicsObject):
|
||||
#self.yClean = None
|
||||
self.xDisp = None
|
||||
self.yDisp = None
|
||||
self._dataRect = None
|
||||
self.curve.clear()
|
||||
self.scatter.clear()
|
||||
|
||||
def appendData(self, *args, **kargs):
|
||||
pass
|
||||
|
||||
|
||||
def curveClicked(self):
|
||||
self.sigClicked.emit(self)
|
||||
|
||||
|
||||
def scatterClicked(self, plt, points):
|
||||
self.sigClicked.emit(self)
|
||||
self.sigPointsClicked.emit(self, points)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
def _fourierTransform(self, x, y):
|
||||
## Perform fourier transform. If x values are not sampled uniformly,
|
||||
## then use np.interp to resample before taking fft.
|
||||
@ -727,7 +793,7 @@ class PlotDataItem(GraphicsObject):
|
||||
x = np.fft.rfftfreq(n, d)
|
||||
y = np.abs(f)
|
||||
return x, y
|
||||
|
||||
|
||||
def dataType(obj):
|
||||
if hasattr(obj, '__len__') and len(obj) == 0:
|
||||
return 'empty'
|
||||
@ -735,7 +801,7 @@ def dataType(obj):
|
||||
return 'dictOfLists'
|
||||
elif isSequence(obj):
|
||||
first = obj[0]
|
||||
|
||||
|
||||
if (hasattr(obj, 'implements') and obj.implements('MetaArray')):
|
||||
return 'MetaArray'
|
||||
elif isinstance(obj, np.ndarray):
|
||||
@ -752,13 +818,13 @@ def dataType(obj):
|
||||
return 'listOfDicts'
|
||||
else:
|
||||
return 'listOfValues'
|
||||
|
||||
|
||||
|
||||
|
||||
def isSequence(obj):
|
||||
return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray'))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#class TableData:
|
||||
#"""
|
||||
#Class for presenting multiple forms of tabular data through a consistent interface.
|
||||
@ -768,7 +834,7 @@ def isSequence(obj):
|
||||
#- dict-of-lists
|
||||
#- dict (single record)
|
||||
#Note: if all the values in this record are lists, it will be interpreted as multiple records
|
||||
|
||||
|
||||
#Data can be accessed and modified by column, by row, or by value
|
||||
#data[columnName]
|
||||
#data[rowId]
|
||||
@ -776,7 +842,7 @@ def isSequence(obj):
|
||||
#data[columnName] = [value, value, ...]
|
||||
#data[rowId] = {columnName: value, ...}
|
||||
#"""
|
||||
|
||||
|
||||
#def __init__(self, data):
|
||||
#self.data = data
|
||||
#if isinstance(data, np.ndarray):
|
||||
@ -797,13 +863,13 @@ def isSequence(obj):
|
||||
#self.mode = data.mode
|
||||
#else:
|
||||
#raise TypeError(type(data))
|
||||
|
||||
|
||||
#for fn in ['__getitem__', '__setitem__']:
|
||||
#setattr(self, fn, getattr(self, '_TableData'+fn+self.mode))
|
||||
|
||||
|
||||
#def originalData(self):
|
||||
#return self.data
|
||||
|
||||
|
||||
#def toArray(self):
|
||||
#if self.mode == 'array':
|
||||
#return self.data
|
||||
@ -818,13 +884,13 @@ def isSequence(obj):
|
||||
#for i in xrange(1, len(self)):
|
||||
#arr[i] = tuple(self[i].values())
|
||||
#return arr
|
||||
|
||||
|
||||
#def __getitem__array(self, arg):
|
||||
#if isinstance(arg, tuple):
|
||||
#return self.data[arg[0]][arg[1]]
|
||||
#else:
|
||||
#return self.data[arg]
|
||||
|
||||
|
||||
#def __getitem__list(self, arg):
|
||||
#if isinstance(arg, basestring):
|
||||
#return [d.get(arg, None) for d in self.data]
|
||||
@ -835,7 +901,7 @@ def isSequence(obj):
|
||||
#return self.data[arg[0]][arg[1]]
|
||||
#else:
|
||||
#raise TypeError(type(arg))
|
||||
|
||||
|
||||
#def __getitem__dict(self, arg):
|
||||
#if isinstance(arg, basestring):
|
||||
#return self.data[arg]
|
||||
@ -866,7 +932,7 @@ def isSequence(obj):
|
||||
#self.data[arg[0]][arg[1]] = val
|
||||
#else:
|
||||
#raise TypeError(type(arg))
|
||||
|
||||
|
||||
#def __setitem__dict(self, arg, val):
|
||||
#if isinstance(arg, basestring):
|
||||
#if len(val) != len(self.data[arg]):
|
||||
@ -887,7 +953,7 @@ def isSequence(obj):
|
||||
#return (args[1], args[0])
|
||||
#else:
|
||||
#return args
|
||||
|
||||
|
||||
#def __iter__(self):
|
||||
#for i in xrange(len(self)):
|
||||
#yield self[i]
|
||||
@ -909,6 +975,6 @@ def isSequence(obj):
|
||||
#return list(names)
|
||||
#elif self.mode == 'dict':
|
||||
#return self.data.keys()
|
||||
|
||||
|
||||
#def keys(self):
|
||||
#return self.columnNames()
|
||||
|
Loading…
Reference in New Issue
Block a user