fixed scatterplotitem segfault
added graphitem
This commit is contained in:
parent
899663c6ca
commit
ee21e2d054
63
examples/GraphItem.py
Normal file
63
examples/GraphItem.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Simple example of GridItem use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import initExample ## Add path to library (just for examples; you do not need this)
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore, QtGui
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
w = pg.GraphicsWindow()
|
||||||
|
v = w.addViewBox()
|
||||||
|
v.setAspectLocked()
|
||||||
|
|
||||||
|
g = pg.GraphItem()
|
||||||
|
v.addItem(g)
|
||||||
|
|
||||||
|
## Define positions of nodes
|
||||||
|
pos = np.array([
|
||||||
|
[0,0],
|
||||||
|
[10,0],
|
||||||
|
[0,10],
|
||||||
|
[10,10],
|
||||||
|
[5,5],
|
||||||
|
[15,5]
|
||||||
|
])
|
||||||
|
|
||||||
|
## Define the set of connections in the graph
|
||||||
|
adj = np.array([
|
||||||
|
[0,1],
|
||||||
|
[1,3],
|
||||||
|
[3,2],
|
||||||
|
[2,0],
|
||||||
|
[1,5],
|
||||||
|
[3,5],
|
||||||
|
])
|
||||||
|
|
||||||
|
## Define the symbol to use for each node (this is optional)
|
||||||
|
symbols = ['o','o','o','o','t','+']
|
||||||
|
|
||||||
|
## Define the line style for each connection (this is optional)
|
||||||
|
lines = np.array([
|
||||||
|
(255,0,0,255,1),
|
||||||
|
(255,0,255,255,2),
|
||||||
|
(255,0,255,255,3),
|
||||||
|
(255,255,0,255,2),
|
||||||
|
(255,0,0,255,1),
|
||||||
|
(255,255,255,255,4),
|
||||||
|
], dtype=[('red',np.ubyte),('green',np.ubyte),('blue',np.ubyte),('alpha',np.ubyte),('width',float)])
|
||||||
|
|
||||||
|
## Update the graph
|
||||||
|
g.setData(pos=pos, adj=adj, pen=lines, size=1, symbol=symbols, pxMode=False)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||||
|
QtGui.QApplication.instance().exec_()
|
@ -27,6 +27,7 @@ from pyqtgraph import getConfigOption
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import decimal, re
|
import decimal, re
|
||||||
import ctypes
|
import ctypes
|
||||||
|
import sys, struct
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import scipy.ndimage
|
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):
|
#def isosurface(data, level):
|
||||||
#"""
|
#"""
|
||||||
#Generate isosurface from volumetric data using marching tetrahedra algorithm.
|
#Generate isosurface from volumetric data using marching tetrahedra algorithm.
|
||||||
|
109
pyqtgraph/graphicsItems/GraphItem.py
Normal file
109
pyqtgraph/graphicsItems/GraphItem.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
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()
|
prof.finish()
|
||||||
|
|
||||||
def generatePath(self, x, y):
|
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']:
|
if self.opts['stepMode']:
|
||||||
## each value in the x/y arrays generates 2 points.
|
## each value in the x/y arrays generates 2 points.
|
||||||
x2 = np.empty((len(x),2), dtype=x.dtype)
|
x2 = np.empty((len(x),2), dtype=x.dtype)
|
||||||
@ -287,40 +267,7 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
y[0] = self.opts['fillLevel']
|
y[0] = self.opts['fillLevel']
|
||||||
y[-1] = self.opts['fillLevel']
|
y[-1] = self.opts['fillLevel']
|
||||||
|
|
||||||
|
path = fn.arrayToQPath(x, y, connect='all')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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])
|
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
@ -1054,6 +1054,21 @@ class PlotItem(GraphicsWidget):
|
|||||||
"""
|
"""
|
||||||
self.getAxis(axis).setLabel(text=text, units=units, **args)
|
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):
|
def showLabel(self, axis, show=True):
|
||||||
"""
|
"""
|
||||||
Show or hide one of the plot's axis labels (the axis itself will be unaffected).
|
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[1] = self.data['y']
|
||||||
pts = fn.transformCoordinates(tr, pts)
|
pts = fn.transformCoordinates(tr, pts)
|
||||||
self.fragments = []
|
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)):
|
for i in xrange(len(self.data)):
|
||||||
rec = self.data[i]
|
rec = self.data[i]
|
||||||
pos = QtCore.QPointF(pts[0,i], pts[1,i])
|
pos = QtCore.QPointF(pts[0,i], pts[1,i])
|
||||||
@ -683,8 +685,10 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.invalidate()
|
self.invalidate()
|
||||||
|
|
||||||
def paint(self, p, *args):
|
def paint(self, p, *args):
|
||||||
|
|
||||||
#p.setPen(fn.mkPen('r'))
|
#p.setPen(fn.mkPen('r'))
|
||||||
#p.drawRect(self.boundingRect())
|
#p.drawRect(self.boundingRect())
|
||||||
|
|
||||||
if self._exportOpts is not False:
|
if self._exportOpts is not False:
|
||||||
aa = self._exportOpts.get('antialias', True)
|
aa = self._exportOpts.get('antialias', True)
|
||||||
scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed
|
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)
|
self.picture.play(p)
|
||||||
|
|
||||||
|
|
||||||
def points(self):
|
def points(self):
|
||||||
for rec in self.data:
|
for rec in self.data:
|
||||||
if rec['item'] is None:
|
if rec['item'] is None:
|
||||||
|
Loading…
Reference in New Issue
Block a user