diff --git a/examples/GLScatterPlotItem.py b/examples/GLScatterPlotItem.py index c1c6e8f8..16033520 100644 --- a/examples/GLScatterPlotItem.py +++ b/examples/GLScatterPlotItem.py @@ -5,6 +5,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl +import numpy as np app = QtGui.QApplication([]) w = gl.GLViewWidget() @@ -14,18 +15,47 @@ w.show() g = gl.GLGridItem() w.addItem(g) -pts = [ - {'pos': (1,0,0), 'size':0.5, 'color':(1.0, 0.0, 0.0, 0.5)}, - {'pos': (0,1,0), 'size':0.2, 'color':(0.0, 0.0, 1.0, 0.5)}, - {'pos': (0,0,1), 'size':2./3., 'color':(0.0, 1.0, 0.0, 0.5)}, -] -z = 0.5 -d = 6.0 -for i in range(50): - pts.append({'pos': (0,0,z), 'size':2./d, 'color':(0.0, 1.0, 0.0, 0.5)}) - z *= 0.5 - d *= 2.0 -sp = gl.GLScatterPlotItem(pts) +#pos = np.empty((53, 3)) +#size = np.empty((53)) +#color = np.empty((53, 4)) +#pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5) +#pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5) +#pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5) + +#z = 0.5 +#d = 6.0 +#for i in range(3,53): + #pos[i] = (0,0,z) + #size[i] = 2./d + #color[i] = (0.0, 1.0, 0.0, 0.5) + #z *= 0.5 + #d *= 2.0 + +#sp = gl.GLScatterPlotItem(pos=pos, sizes=size, colors=color, pxMode=False) + + +pos = (np.random.random(size=(100000,3)) * 10) - 5 +color = np.ones((pos.shape[0], 4)) +d = (pos**2).sum(axis=1)**0.5 +color[:,3] = np.clip(-np.cos(d*2) * 0.2, 0, 1) +sp = gl.GLScatterPlotItem(pos=pos, color=color, size=5) +phase = 0. + +def update(): + global phase, color, sp, d + s = -np.cos(d*2+phase) + color[:,3] = np.clip(s * 0.2, 0, 1) + color[:,0] = np.clip(s * 3.0, 0, 1) + color[:,1] = np.clip(s * 1.0, 0, 1) + color[:,2] = np.clip(s ** 3, 0, 1) + + sp.setData(color=color) + phase -= 0.1 + +t = QtCore.QTimer() +t.timeout.connect(update) +t.start(50) + w.addItem(sp) ## Start Qt event loop unless running in interactive mode. diff --git a/opengl/items/GLScatterPlotItem.py b/opengl/items/GLScatterPlotItem.py index 765a807d..3ef3f11b 100644 --- a/opengl/items/GLScatterPlotItem.py +++ b/opengl/items/GLScatterPlotItem.py @@ -8,31 +8,47 @@ __all__ = ['GLScatterPlotItem'] class GLScatterPlotItem(GLGraphicsItem): """Draws points at a list of 3D positions.""" - def __init__(self, data=None): + def __init__(self, **kwds): GLGraphicsItem.__init__(self) - self.data = [] - if data is not None: - self.setData(data) + self.pos = [] + self.size = 10 + self.color = [1.0,1.0,1.0,0.5] + self.pxMode = True + self.setData(**kwds) - def setData(self, data): + def setData(self, **kwds): """ - Data may be either a list of dicts (one dict per point) or a numpy record array. + Update the data displayed by this item. All arguments are optional; + for example it is allowed to update spot positions while leaving + colors unchanged, etc. ==================== ================================================== - Allowed fields are: + Arguments: ------------------------------------------------------------------------ - pos (x,y,z) tuple of coordinate values or QVector3D - color (r,g,b,a) tuple of floats (0.0-1.0) or QColor - size (float) diameter of spot + pos (N,3) array of floats specifying point locations. + color (N,4) array of floats (0.0-1.0) specifying + spot colors OR a tuple of floats specifying + a single color for all spots. + size (N,) array of floats specifying spot sizes or + a single value to apply to all spots. + pxMode If True, spot sizes are expressed in pixels. + Otherwise, they are expressed in item coordinates. ==================== ================================================== """ - - - self.data = data + args = ['pos', 'color', 'size', 'pxMode'] + for k in kwds.keys(): + if k not in args: + raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + self.pos = kwds.get('pos', self.pos) + self.color = kwds.get('color', self.color) + self.size = kwds.get('size', self.size) + self.pxMode = kwds.get('pxMode', self.pxMode) self.update() def initializeGL(self): + + ## Generate texture for rendering points w = 64 def fn(x,y): r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5 @@ -73,28 +89,49 @@ class GLScatterPlotItem(GLGraphicsItem): glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - for pt in self.data: - pos = pt['pos'] - try: - color = pt['color'] - except KeyError: - color = (1,1,1,1) - try: - size = pt['size'] - except KeyError: - size = 10 - - if isinstance(color, QtGui.QColor): - color = fn.glColor(color) - - pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) + if self.pxMode: + glVertexPointerf(self.pos) + if isinstance(self.color, np.ndarray): + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) - glPointSize(size / pxSize) - glBegin( GL_POINTS ) - glColor4f(*color) # x is blue - #glNormal3f(size, 0, 0) - glVertex3f(*pos) - glEnd() + if isinstance(self.size, np.ndarray): + raise Exception('Array size not yet supported in pxMode (hopefully soon)') + + glPointSize(self.size) + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_COLOR_ARRAY) + glDrawArrays(GL_POINTS, 0, len(self.pos)) + else: + + + for i in range(len(self.pos)): + pos = self.pos[i] + + if isinstance(self.color, np.ndarray): + color = self.color[i] + else: + color = self.color + if isinstance(self.color, QtGui.QColor): + color = fn.glColor(self.color) + + if isinstance(self.size, np.ndarray): + size = self.size[i] + else: + size = self.size + + pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) + + glPointSize(size / pxSize) + glBegin( GL_POINTS ) + glColor4f(*color) # x is blue + #glNormal3f(size, 0, 0) + glVertex3f(*pos) + glEnd()