merge from acq4
This commit is contained in:
commit
5bd3adfa7c
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 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.
|
||||
|
@ -58,13 +58,14 @@ class AxisItem(GraphicsWidget):
|
||||
self.labelUnitPrefix=''
|
||||
self.labelStyle = {}
|
||||
self.logMode = False
|
||||
self.tickFont = None
|
||||
|
||||
self.textHeight = 18
|
||||
self.tickLength = maxTickLength
|
||||
self._tickLevels = None ## used to override the automatic ticking system with explicit ticks
|
||||
self.scale = 1.0
|
||||
self.autoScale = True
|
||||
|
||||
|
||||
self.setRange(0, 1)
|
||||
|
||||
self.setPen(pen)
|
||||
@ -72,12 +73,12 @@ class AxisItem(GraphicsWidget):
|
||||
self._linkedView = None
|
||||
if linkView is not None:
|
||||
self.linkToView(linkView)
|
||||
|
||||
|
||||
self.showLabel(False)
|
||||
|
||||
self.grid = False
|
||||
#self.setCacheMode(self.DeviceCoordinateCache)
|
||||
|
||||
|
||||
def close(self):
|
||||
self.scene().removeItem(self.label)
|
||||
self.label = None
|
||||
@ -100,6 +101,14 @@ class AxisItem(GraphicsWidget):
|
||||
self.picture = None
|
||||
self.update()
|
||||
|
||||
def setTickFont(self, font):
|
||||
self.tickFont = font
|
||||
self.picture = None
|
||||
self.prepareGeometryChange()
|
||||
## Need to re-allocate space depending on font size?
|
||||
|
||||
self.update()
|
||||
|
||||
def resizeEvent(self, ev=None):
|
||||
#s = self.size()
|
||||
|
||||
@ -311,14 +320,21 @@ class AxisItem(GraphicsWidget):
|
||||
if linkedView is None or self.grid is False:
|
||||
rect = self.mapRectFromParent(self.geometry())
|
||||
## extend rect if ticks go in negative direction
|
||||
## also extend to account for text that flows past the edges
|
||||
if self.orientation == 'left':
|
||||
rect.setRight(rect.right() - min(0,self.tickLength))
|
||||
#rect.setRight(rect.right() - min(0,self.tickLength))
|
||||
#rect.setTop(rect.top() - 15)
|
||||
#rect.setBottom(rect.bottom() + 15)
|
||||
rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15)
|
||||
elif self.orientation == 'right':
|
||||
rect.setLeft(rect.left() + min(0,self.tickLength))
|
||||
#rect.setLeft(rect.left() + min(0,self.tickLength))
|
||||
rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15)
|
||||
elif self.orientation == 'top':
|
||||
rect.setBottom(rect.bottom() - min(0,self.tickLength))
|
||||
#rect.setBottom(rect.bottom() - min(0,self.tickLength))
|
||||
rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength))
|
||||
elif self.orientation == 'bottom':
|
||||
rect.setTop(rect.top() + min(0,self.tickLength))
|
||||
#rect.setTop(rect.top() + min(0,self.tickLength))
|
||||
rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0)
|
||||
return rect
|
||||
else:
|
||||
return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect())
|
||||
@ -647,6 +663,9 @@ class AxisItem(GraphicsWidget):
|
||||
prof.mark('draw ticks')
|
||||
|
||||
## Draw text until there is no more room (or no more text)
|
||||
if self.tickFont is not None:
|
||||
p.setFont(self.tickFont)
|
||||
|
||||
textRects = []
|
||||
for i in range(len(tickLevels)):
|
||||
## Get the list of strings to display for this level
|
||||
|
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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -202,7 +202,7 @@ class GraphicsItem(object):
|
||||
## check global cache
|
||||
key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
|
||||
pv = self._pixelVectorGlobalCache.get(key, None)
|
||||
if pv is not None:
|
||||
if direction is None and pv is not None:
|
||||
self._pixelVectorCache = [dt, pv]
|
||||
return tuple(map(Point,pv)) ## return a *copy*
|
||||
|
||||
|
@ -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)
|
||||
@ -286,41 +266,8 @@ class PlotCurveItem(GraphicsObject):
|
||||
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
|
||||
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).
|
||||
|
@ -620,9 +620,12 @@ class ScatterPlotItem(GraphicsObject):
|
||||
|
||||
if frac >= 1.0:
|
||||
## increase size of bounds based on spot size and pen width
|
||||
px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1)) ## determine length of pixel along this axis
|
||||
#px = self.pixelLength(Point(1, 0) if ax == 0 else Point(0, 1)) ## determine length of pixel along this axis
|
||||
px = self.pixelVectors()[ax]
|
||||
if px is None:
|
||||
px = 0
|
||||
else:
|
||||
px = px.length()
|
||||
minIndex = np.argmin(d)
|
||||
maxIndex = np.argmax(d)
|
||||
minVal = d[minIndex]
|
||||
@ -668,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])
|
||||
@ -680,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
|
||||
@ -728,7 +735,6 @@ class ScatterPlotItem(GraphicsObject):
|
||||
p2.end()
|
||||
|
||||
self.picture.play(p)
|
||||
|
||||
|
||||
def points(self):
|
||||
for rec in self.data:
|
||||
|
Loading…
Reference in New Issue
Block a user