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:
Nils Nemitz 2020-10-19 14:34:41 +09:00 committed by GitHub
parent 39f9c6a6aa
commit 65e90faec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 210 additions and 140 deletions

View File

@ -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]

View File

@ -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()