- Removed inf/nan checking from PlotDataItem and PlotCurveItem; improved performance

- Added 'connect' option to PlotDataItem and PlotCurveItem to affect which line segments are drawn
- arrayToQPath() added 'finite' connection mode which omits non-finite values from connections
This commit is contained in:
Luke Campagnola 2013-09-13 03:27:26 -04:00
parent 5ad39c2509
commit 58048a703c
4 changed files with 51 additions and 31 deletions

View File

@ -1133,7 +1133,9 @@ def arrayToQPath(x, y, connect='all'):
connect[:,0] = 1 connect[:,0] = 1
connect[:,1] = 0 connect[:,1] = 0
connect = connect.flatten() connect = connect.flatten()
if connect == 'finite':
connect = np.isfinite(x) & np.isfinite(y)
arr[1:-1]['c'] = connect
if connect == 'all': if connect == 'all':
arr[1:-1]['c'] = 1 arr[1:-1]['c'] = 1
elif isinstance(connect, np.ndarray): elif isinstance(connect, np.ndarray):

View File

@ -71,7 +71,8 @@ class PlotCurveItem(GraphicsObject):
'brush': None, 'brush': None,
'stepMode': False, 'stepMode': False,
'name': None, 'name': None,
'antialias': pg.getConfigOption('antialias'), 'antialias': pg.getConfigOption('antialias'),\
'connect': 'all',
} }
self.setClickable(kargs.get('clickable', False)) self.setClickable(kargs.get('clickable', False))
self.setData(*args, **kargs) self.setData(*args, **kargs)
@ -119,10 +120,12 @@ class PlotCurveItem(GraphicsObject):
## Get min/max (or percentiles) of the requested data range ## Get min/max (or percentiles) of the requested data range
if frac >= 1.0: if frac >= 1.0:
b = (d.min(), d.max()) b = (np.nanmin(d), np.nanmax(d))
elif frac <= 0.0: elif frac <= 0.0:
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
else: else:
mask = np.isfinite(d)
d = d[mask]
b = (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50))) b = (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
## adjust for fill level ## adjust for fill level
@ -264,6 +267,12 @@ class PlotCurveItem(GraphicsObject):
stepMode If True, two orthogonal lines are drawn for each sample stepMode If True, two orthogonal lines are drawn for each sample
as steps. This is commonly used when drawing histograms. as steps. This is commonly used when drawing histograms.
Note that in this case, len(x) == len(y) + 1 Note that in this case, len(x) == len(y) + 1
connect Argument specifying how vertexes should be connected
by line segments. Default is "all", indicating full
connection. "pairs" causes only even-numbered segments
to be drawn. "finite" causes segments to be omitted if
they are attached to nan or inf values. For any other
connectivity, specify an array of boolean values.
============== ======================================================== ============== ========================================================
If non-keyword arguments are used, they will be interpreted as If non-keyword arguments are used, they will be interpreted as
@ -326,7 +335,8 @@ class PlotCurveItem(GraphicsObject):
if 'name' in kargs: if 'name' in kargs:
self.opts['name'] = kargs['name'] self.opts['name'] = kargs['name']
if 'connect' in kargs:
self.opts['connect'] = kargs['connect']
if 'pen' in kargs: if 'pen' in kargs:
self.setPen(kargs['pen']) self.setPen(kargs['pen'])
if 'shadowPen' in kargs: if 'shadowPen' in kargs:
@ -365,7 +375,7 @@ class PlotCurveItem(GraphicsObject):
y[0] = self.opts['fillLevel'] y[0] = self.opts['fillLevel']
y[-1] = self.opts['fillLevel'] y[-1] = self.opts['fillLevel']
path = fn.arrayToQPath(x, y, connect='all') path = fn.arrayToQPath(x, y, connect=self.opts['connect'])
return path return path

View File

@ -58,6 +58,8 @@ class PlotDataItem(GraphicsObject):
**Line style keyword arguments:** **Line style keyword arguments:**
========== ================================================ ========== ================================================
connect Specifies how / whether vertexes should be connected.
See :func:`arrayToQPath() <pyqtgraph.arrayToQPath>`
pen Pen to use for drawing line between points. pen Pen to use for drawing line between points.
Default is solid grey, 1px width. Use None to disable line drawing. Default is solid grey, 1px width. Use None to disable line drawing.
May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>` May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>`
@ -119,7 +121,7 @@ class PlotDataItem(GraphicsObject):
self.yData = None self.yData = None
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
self.dataMask = None #self.dataMask = None
#self.curves = [] #self.curves = []
#self.scatters = [] #self.scatters = []
self.curve = PlotCurveItem() self.curve = PlotCurveItem()
@ -133,6 +135,8 @@ class PlotDataItem(GraphicsObject):
#self.clear() #self.clear()
self.opts = { self.opts = {
'connect': 'all',
'fftMode': False, 'fftMode': False,
'logMode': [False, False], 'logMode': [False, False],
'alphaHint': 1.0, 'alphaHint': 1.0,
@ -386,6 +390,8 @@ class PlotDataItem(GraphicsObject):
if 'name' in kargs: if 'name' in kargs:
self.opts['name'] = kargs['name'] 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 pen/brush are given with no symbol, then assume symbol is 'o'
@ -445,7 +451,7 @@ class PlotDataItem(GraphicsObject):
def updateItems(self): def updateItems(self):
curveArgs = {} curveArgs = {}
for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias')]: for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect')]:
curveArgs[v] = self.opts[k] curveArgs[v] = self.opts[k]
scatterArgs = {} scatterArgs = {}
@ -454,7 +460,7 @@ class PlotDataItem(GraphicsObject):
scatterArgs[v] = self.opts[k] scatterArgs[v] = self.opts[k]
x,y = self.getData() x,y = self.getData()
scatterArgs['mask'] = self.dataMask #scatterArgs['mask'] = self.dataMask
if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): 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.setData(x=x, y=y, **curveArgs)
@ -473,20 +479,20 @@ class PlotDataItem(GraphicsObject):
if self.xData is None: if self.xData is None:
return (None, None) return (None, None)
if self.xClean is None: #if self.xClean is None:
nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) #nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData)
if nanMask.any(): #if nanMask.any():
self.dataMask = ~nanMask #self.dataMask = ~nanMask
self.xClean = self.xData[self.dataMask] #self.xClean = self.xData[self.dataMask]
self.yClean = self.yData[self.dataMask] #self.yClean = self.yData[self.dataMask]
else: #else:
self.dataMask = None #self.dataMask = None
self.xClean = self.xData #self.xClean = self.xData
self.yClean = self.yData #self.yClean = self.yData
if self.xDisp is None: if self.xDisp is None:
x = self.xClean x = self.xData
y = self.yClean y = self.yData
#ds = self.opts['downsample'] #ds = self.opts['downsample']
@ -500,14 +506,14 @@ class PlotDataItem(GraphicsObject):
x = np.log10(x) x = np.log10(x)
if self.opts['logMode'][1]: if self.opts['logMode'][1]:
y = np.log10(y) y = np.log10(y)
if any(self.opts['logMode']): ## re-check for NANs after log #if any(self.opts['logMode']): ## re-check for NANs after log
nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y) #nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y)
if any(nanMask): #if any(nanMask):
self.dataMask = ~nanMask #self.dataMask = ~nanMask
x = x[self.dataMask] #x = x[self.dataMask]
y = y[self.dataMask] #y = y[self.dataMask]
else: #else:
self.dataMask = None #self.dataMask = None
ds = self.opts['downsample'] ds = self.opts['downsample']
if not isinstance(ds, int): if not isinstance(ds, int):
@ -640,8 +646,8 @@ class PlotDataItem(GraphicsObject):
#self.scatters = [] #self.scatters = []
self.xData = None self.xData = None
self.yData = None self.yData = None
self.xClean = None #self.xClean = None
self.yClean = None #self.yClean = None
self.xDisp = None self.xDisp = None
self.yDisp = None self.yDisp = None
self.curve.setData([]) self.curve.setData([])

View File

@ -626,11 +626,13 @@ class ScatterPlotItem(GraphicsObject):
d2 = d2[mask] d2 = d2[mask]
if frac >= 1.0: if frac >= 1.0:
self.bounds[ax] = (d.min() - self._maxSpotWidth*0.7072, d.max() + self._maxSpotWidth*0.7072) self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072)
return self.bounds[ax] return self.bounds[ax]
elif frac <= 0.0: elif frac <= 0.0:
raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac))
else: else:
mask = np.isfinite(d)
d = d[mask]
return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50))) return (scipy.stats.scoreatpercentile(d, 50 - (frac * 50)), scipy.stats.scoreatpercentile(d, 50 + (frac * 50)))
def pixelPadding(self): def pixelPadding(self):