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:
Luke Campagnola 2012-10-22 13:35:58 -04:00
parent 16434272c2
commit 15d9c1b351
2 changed files with 96 additions and 7 deletions

View File

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

View File

@ -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,8 +224,15 @@ class PlotCurveItem(GraphicsObject):
prof.mark('copy')
if self.xData.shape != self.yData.shape:
raise Exception("X and Y arrays must be the same shape--got %s and %s." % (str(x.shape), str(y.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
self.fillPath = 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,12 +355,21 @@ 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)
def paint(self, p, opt, widget):