PlotCurveItem now has 'step' mode for drawing histograms
Added function for generating pseudo-scatter plots (points stack in a histogram-like manner)
This commit is contained in:
parent
16434272c2
commit
15d9c1b351
49
functions.py
49
functions.py
@ -1469,3 +1469,52 @@ def invertQTransform(tr):
|
||||
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
|
||||
|
||||
|
||||
def pseudoScatter(data, spacing=None, shuffle=True):
|
||||
"""
|
||||
Used for examining the distribution of values in a set.
|
||||
|
||||
Given a list of x-values, construct a set of y-values such that an x,y scatter-plot
|
||||
will not have overlapping points (it will look similar to a histogram).
|
||||
"""
|
||||
inds = np.arange(len(data))
|
||||
if shuffle:
|
||||
np.random.shuffle(inds)
|
||||
|
||||
data = data[inds]
|
||||
|
||||
if spacing is None:
|
||||
spacing = 2.*np.std(data)/len(data)**0.5
|
||||
s2 = spacing**2
|
||||
|
||||
yvals = np.empty(len(data))
|
||||
yvals[0] = 0
|
||||
for i in range(1,len(data)):
|
||||
x = data[i] # current x value to be placed
|
||||
x0 = data[:i] # all x values already placed
|
||||
y0 = yvals[:i] # all y values already placed
|
||||
y = 0
|
||||
|
||||
dx = (x0-x)**2 # x-distance to each previous point
|
||||
xmask = dx < s2 # exclude anything too far away
|
||||
|
||||
if xmask.sum() > 0:
|
||||
dx = dx[xmask]
|
||||
dy = (s2 - dx)**0.5
|
||||
limits = np.empty((2,len(dy))) # ranges of y-values to exclude
|
||||
limits[0] = y0[xmask] - dy
|
||||
limits[1] = y0[xmask] + dy
|
||||
|
||||
while True:
|
||||
# ignore anything below this y-value
|
||||
mask = limits[1] >= y
|
||||
limits = limits[:,mask]
|
||||
|
||||
# are we inside an excluded region?
|
||||
mask = (limits[0] < y) & (limits[1] > y)
|
||||
if mask.sum() == 0:
|
||||
break
|
||||
y = limits[:,mask].max()
|
||||
|
||||
yvals[i] = y
|
||||
|
||||
return yvals[np.argsort(inds)] ## un-shuffle values before returning
|
@ -64,6 +64,7 @@ class PlotCurveItem(GraphicsObject):
|
||||
'shadowPen': None,
|
||||
'fillLevel': None,
|
||||
'brush': None,
|
||||
'stepMode': False,
|
||||
}
|
||||
self.setClickable(kargs.get('clickable', False))
|
||||
self.setData(*args, **kargs)
|
||||
@ -223,7 +224,14 @@ class PlotCurveItem(GraphicsObject):
|
||||
|
||||
prof.mark('copy')
|
||||
|
||||
if self.xData.shape != self.yData.shape:
|
||||
if 'stepMode' in kargs:
|
||||
self.opts['stepMode'] = kargs['stepMode']
|
||||
|
||||
if self.opts['stepMode'] is True:
|
||||
if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots
|
||||
raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (str(x.shape), str(y.shape)))
|
||||
else:
|
||||
if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots
|
||||
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.shape)))
|
||||
|
||||
self.path = None
|
||||
@ -267,6 +275,29 @@ class PlotCurveItem(GraphicsObject):
|
||||
## 0(i4)
|
||||
##
|
||||
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
|
||||
|
||||
if self.opts['stepMode']:
|
||||
## each value in the x/y arrays generates 2 points.
|
||||
x2 = np.empty((len(x),2), dtype=x.dtype)
|
||||
x2[:] = x[:,np.newaxis]
|
||||
if self.opts['fillLevel'] is None:
|
||||
x = x2.reshape(x2.size)[1:-1]
|
||||
y2 = np.empty((len(y),2), dtype=y.dtype)
|
||||
y2[:] = y[:,np.newaxis]
|
||||
y = y2.reshape(y2.size)
|
||||
else:
|
||||
## If we have a fill level, add two extra points at either end
|
||||
x = x2.reshape(x2.size)
|
||||
y2 = np.empty((len(y)+2,2), dtype=y.dtype)
|
||||
y2[1:-1] = y[:,np.newaxis]
|
||||
y = y2.reshape(y2.size)[1:-1]
|
||||
y[0] = self.opts['fillLevel']
|
||||
y[-1] = self.opts['fillLevel']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if sys.version_info[0] == 2: ## So this is disabled for python 3... why??
|
||||
n = x.shape[0]
|
||||
# create empty array, pad with extra space on either end
|
||||
@ -324,11 +355,20 @@ class PlotCurveItem(GraphicsObject):
|
||||
pixels = self.pixelVectors()
|
||||
if pixels == (None, None):
|
||||
pixels = [Point(0,0), Point(0,0)]
|
||||
xmin = x.min() - pixels[0].x() * lineWidth
|
||||
xmax = x.max() + pixels[0].x() * lineWidth
|
||||
ymin = y.min() - abs(pixels[1].y()) * lineWidth
|
||||
ymax = y.max() + abs(pixels[1].y()) * lineWidth
|
||||
|
||||
xmin = x.min()
|
||||
xmax = x.max()
|
||||
ymin = y.min()
|
||||
ymax = y.max()
|
||||
|
||||
if self.opts['fillLevel'] is not None:
|
||||
ymin = min(ymin, self.opts['fillLevel'])
|
||||
ymax = max(ymax, self.opts['fillLevel'])
|
||||
|
||||
xmin -= pixels[0].x() * lineWidth
|
||||
xmax += pixels[0].x() * lineWidth
|
||||
ymin -= abs(pixels[1].y()) * lineWidth
|
||||
ymax += abs(pixels[1].y()) * lineWidth
|
||||
|
||||
return QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user