Commit 1b7cd44e by Luke Campagnola

### fixed scatterplotitem segfault

`added graphitem`
parent e05447de
 ... ... @@ -27,6 +27,7 @@ from pyqtgraph import getConfigOption import numpy as np import decimal, re import ctypes import sys, struct try: import scipy.ndimage ... ... @@ -1041,6 +1042,97 @@ def colorToAlpha(data, color): def arrayToQPath(x, y, connect='all'): """Convert an array of x,y coordinats to QPath as efficiently as possible. The *connect* argument may be 'all', indicating that each point should be connected to the next; 'pairs', indicating that each pair of points should be connected, or an array of int32 values (0 or 1) indicating connections. """ ## Create all vertices in path. The method used below creates a binary format so that all ## vertices can be read in at once. This binary format may change in future versions of Qt, ## so the original (slower) method is left here for emergencies: #path.moveTo(x[0], y[0]) #for i in range(1, y.shape[0]): # path.lineTo(x[i], y[i]) ## Speed this up using >> operator ## Format is: ## numVerts(i4) 0(i4) ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex ## ... ## 0(i4) ## ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') path = QtGui.QPainterPath() #prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) 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 arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) # write first two integers #prof.mark('allocate empty') arr.data[12:20] = struct.pack('>ii', n, 0) #prof.mark('pack header') # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y # decide which points are connected by lines if connect == 'pairs': connect = np.empty((n/2,2), dtype=np.int32) connect[:,0] = 1 connect[:,1] = 0 connect = connect.flatten() if connect == 'all': arr[1:-1]['c'] = 1 elif isinstance(connect, np.ndarray): arr[1:-1]['c'] = connect else: raise Exception('connect argument must be "all", "pairs", or array') #prof.mark('fill array') # write last 0 lastInd = 20*(n+1) arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) #prof.mark('footer') # create datastream object and stream into path buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here #prof.mark('create buffer') ds = QtCore.QDataStream(buf) #prof.mark('create datastream') ds >> path #prof.mark('load') #prof.finish() else: ## This does exactly the same as above, but less efficiently (and more simply). path.moveTo(x[0], y[0]) if connect == 'all': for i in range(1, y.shape[0]): path.lineTo(x[i], y[i]) elif connect == 'pairs': for i in range(1, y.shape[0]): if i%2 == 0: path.lineTo(x[i], y[i]) else: path.moveTo(x[i], y[i]) elif isinstance(connect, np.ndarray): for i in range(1, y.shape[0]): if connect[i] == 1: path.lineTo(x[i], y[i]) else: path.moveTo(x[i], y[i]) else: raise Exception('connect argument must be "all", "pairs", or array') return path #def isosurface(data, level): #""" #Generate isosurface from volumetric data using marching tetrahedra algorithm. ... ...
 from .. import functions as fn from GraphicsObject import GraphicsObject from ScatterPlotItem import ScatterPlotItem import pyqtgraph as pg import numpy as np __all__ = ['GraphItem'] class GraphItem(GraphicsObject): """A GraphItem displays graph information (as in 'graph theory', not 'graphics') as a set of nodes connected by lines. """ def __init__(self, **kwds): GraphicsObject.__init__(self) self.scatter = ScatterPlotItem() self.scatter.setParentItem(self) self.adjacency = None self.pos = None self.picture = None self.pen = 'default' self.setData(**kwds) def setData(self, **kwds): """ Change the data displayed by the graph. ============ ========================================================= Arguments pos (N,2) array of the positions of each node in the graph adj (M,2) array of connection data. Each row contains indexes of two nodes that are connected. pen The pen to use when drawing lines between connected nodes. May be one of: * QPen * a single argument to pass to pg.mkPen * a record array of length M with fields (red, green, blue, alpha, width). * None (to disable connection drawing) * 'default' to use the default foreground color. symbolPen The pen used for drawing nodes. **opts All other keyword arguments are given to ScatterPlotItem to affect the appearance of nodes (symbol, size, brush, etc.) ============ ========================================================= """ if 'adj' in kwds: self.adjacency = kwds.pop('adj') assert self.adjacency.dtype.kind in 'iu' self.picture = None if 'pos' in kwds: self.pos = kwds['pos'] self.picture = None if 'pen' in kwds: self.setPen(kwds.pop('pen')) self.picture = None if 'symbolPen' in kwds: kwds['pen'] = kwds.pop('symbolPen') self.scatter.setData(**kwds) self.informViewBoundsChanged() def setPen(self, pen): self.pen = pen self.picture = None def generatePicture(self): self.picture = pg.QtGui.QPicture() if self.pen is None or self.pos is None or self.adjacency is None: return p = pg.QtGui.QPainter(self.picture) try: pts = self.pos[self.adjacency] pen = self.pen if isinstance(pen, np.ndarray): lastPen = None for i in range(pts.shape[0]): pen = self.pen[i] if np.any(pen != lastPen): lastPen = pen if pen.dtype.fields is None: p.setPen(pg.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) else: p.setPen(pg.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) p.drawLine(pg.QtCore.QPointF(*pts[i][0]), pg.QtCore.QPointF(*pts[i][1])) else: if pen == 'default': pen = pg.getConfigOption('foreground') p.setPen(pg.mkPen(pen)) pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') p.drawPath(path) finally: p.end() def paint(self, p, *args): if self.picture == None: self.generatePicture() self.picture.play(p) def boundingRect(self): return self.scatter.boundingRect()
 ... ... @@ -249,26 +249,6 @@ class PlotCurveItem(GraphicsObject): prof.finish() def generatePath(self, x, y): prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) path = QtGui.QPainterPath() ## Create all vertices in path. The method used below creates a binary format so that all ## vertices can be read in at once. This binary format may change in future versions of Qt, ## so the original (slower) method is left here for emergencies: #path.moveTo(x[0], y[0]) #for i in range(1, y.shape[0]): # path.lineTo(x[i], y[i]) ## Speed this up using >> operator ## Format is: ## numVerts(i4) 0(i4) ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex ## ... ## 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) ... ... @@ -287,40 +267,7 @@ class PlotCurveItem(GraphicsObject): 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 arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) # write first two integers prof.mark('allocate empty') arr.data[12:20] = struct.pack('>ii', n, 0) prof.mark('pack header') # Fill array with vertex values arr[1:-1]['x'] = x arr[1:-1]['y'] = y arr[1:-1]['c'] = 1 prof.mark('fill array') # write last 0 lastInd = 20*(n+1) arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) prof.mark('footer') # create datastream object and stream into path buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here prof.mark('create buffer') ds = QtCore.QDataStream(buf) prof.mark('create datastream') ds >> path prof.mark('load') prof.finish() else: path.moveTo(x[0], y[0]) for i in range(1, y.shape[0]): path.lineTo(x[i], y[i]) path = fn.arrayToQPath(x, y, connect='all') return path ... ...
 ... ... @@ -1054,6 +1054,21 @@ class PlotItem(GraphicsWidget): """ self.getAxis(axis).setLabel(text=text, units=units, **args) def setLabels(self, **kwds): """ Convenience function allowing multiple labels and/or title to be set in one call. Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. Values may be strings or a tuple of arguments to pass to setLabel. """ for k,v in kwds.items(): if k == 'title': self.setTitle(v) else: if isinstance(v, basestring): v = (v,) self.setLabel(k, *v) def showLabel(self, axis, show=True): """ Show or hide one of the plot's axis labels (the axis itself will be unaffected). ... ...
 ... ... @@ -671,6 +671,8 @@ class ScatterPlotItem(GraphicsObject): pts[1] = self.data['y'] pts = fn.transformCoordinates(tr, pts) self.fragments = [] pts = np.clip(pts, -2**31, 2**31) ## prevent Qt segmentation fault. ## Still won't be able to render correctly, though. for i in xrange(len(self.data)): rec = self.data[i] pos = QtCore.QPointF(pts[0,i], pts[1,i]) ... ... @@ -683,8 +685,10 @@ class ScatterPlotItem(GraphicsObject): self.invalidate() def paint(self, p, *args): #p.setPen(fn.mkPen('r')) #p.drawRect(self.boundingRect()) if self._exportOpts is not False: aa = self._exportOpts.get('antialias', True) scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed ... ... @@ -732,7 +736,6 @@ class ScatterPlotItem(GraphicsObject): self.picture.play(p) def points(self): for rec in self.data: if rec['item'] is None: ... ...
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!