Commit 27c90c5d authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Many minor updates:

- added ability for ScatterPlotItem to use arbitrary symbol shapes
- added scatter plot speed test for evaluating new methods
- added butterworth notch filter to flowchart library
- fixed bugs with ViewBox trying to close itself after python has started cleaning up
- fixed python 2.6 compatibility bug in PlotCurveItem
- fixed support for list-of-dicts and dict-of-lists input for PlotDataItem
- check to ensure Qt version is >= 4.7
- workaround for numpy segmentation fault
- several other minor updates and documentation changes
parent 01deeb75
## Do all Qt imports from here to allow easier PyQt / PySide compatibility
import sys
import sys, re
## Automatically determine whether to use PyQt or PySide.
## This is done by first checking to see whether one of the libraries
......@@ -36,3 +36,13 @@ else:
QtCore.Signal = QtCore.pyqtSignal
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
## Make sure we have Qt >= 4.7
versionReq = [4, 7]
QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR
m = re.match(r'(\d+)\.(\d+).*', QtVersion)
if m is not None and map(int, m.groups()) < versionReq:
print map(int, m.groups())
raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq + (QtVersion,)))
......@@ -34,7 +34,11 @@ class SRTTransform(QtGui.QTransform):
def getScale(self):
return self._state['scale']
def getAngle(self):
def getAngle(self):
## deprecated; for backward compatibility
return self.getRotation()
def getRotation(self):
return self._state['angle']
def getTranslation(self):
......
This diff is collapsed.
......@@ -21,7 +21,9 @@ if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1]
## helpers for 2/3 compatibility
from . import python2_3
## install workarounds for numpy bugs
import numpy_fix
## in general openGL is poorly supported with Qt+GraphicsView.
## we only enable it where the performance benefit is critical.
## Note this only applies to 2D graphics; 3D graphics always use OpenGL.
......@@ -215,6 +217,21 @@ def image(*args, **kargs):
w.show()
return w
show = image ## for backward compatibility
def dbg():
"""
Create a console window and begin watching for exceptions.
"""
mkQApp()
import console
c = console.ConsoleWidget()
c.catchAllExceptions()
c.show()
global consoles
try:
consoles.append(c)
except NameError:
consoles = [c]
def mkQApp():
......
......@@ -184,7 +184,7 @@ class Canvas(QtGui.QWidget):
#if gi is None:
#return
try:
citem = item.canvasItem
citem = item.canvasItem()
except AttributeError:
return
if item.checkState(0) == QtCore.Qt.Checked:
......@@ -238,7 +238,7 @@ class Canvas(QtGui.QWidget):
"""
Return list of all selected canvasItems
"""
return [item.canvasItem for item in self.itemList.selectedItems() if item.canvasItem is not None]
return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None]
#def selectedItem(self):
#sel = self.itemList.selectedItems()
......@@ -371,7 +371,7 @@ class Canvas(QtGui.QWidget):
parent = parent.listItem
## set Z value above all other siblings if none was specified
siblings = [parent.child(i).canvasItem for i in range(parent.childCount())]
siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())]
z = citem.zValue()
if z is None:
zvals = [i.zValue() for i in siblings]
......@@ -382,7 +382,7 @@ class Canvas(QtGui.QWidget):
z = max(zvals)+10
else:
if len(zvals) == 0:
z = parent.canvasItem.zValue()
z = parent.canvasItem().zValue()
else:
z = max(zvals)+1
citem.setZValue(z)
......@@ -390,7 +390,7 @@ class Canvas(QtGui.QWidget):
## determine location to insert item relative to its siblings
for i in range(parent.childCount()):
ch = parent.child(i)
zval = ch.canvasItem.graphicsItem().zValue() ## should we use CanvasItem.zValue here?
zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here?
if zval < z:
insertLocation = i
break
......@@ -416,7 +416,7 @@ class Canvas(QtGui.QWidget):
citem.name = name
citem.listItem = node
node.canvasItem = citem
node.canvasItem = weakref.ref(citem)
self.items.append(citem)
ctrl = citem.ctrlWidget()
......@@ -465,10 +465,10 @@ class Canvas(QtGui.QWidget):
def treeItemMoved(self, item, parent, index):
##Item moved in tree; update Z values
if parent is self.itemList.invisibleRootItem():
item.canvasItem.setParentItem(self.view.childGroup)
item.canvasItem().setParentItem(self.view.childGroup)
else:
item.canvasItem.setParentItem(parent.canvasItem)
siblings = [parent.child(i).canvasItem for i in range(parent.childCount())]
item.canvasItem().setParentItem(parent.canvasItem())
siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())]
zvals = [i.zValue() for i in siblings]
zvals.sort(reverse=True)
......
......@@ -339,19 +339,31 @@ class ConsoleWidget(QtGui.QWidget):
if excType is GeneratorExit or excType is StopIteration:
return False
if excType is KeyError:
if filename.endswith('python2.7/weakref.py') and function == '__contains__':
if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'):
return False
if filename.endswith('python2.7/copy.py') and function == '_keep_alive':
return False
if excType is AttributeError:
if filename.endswith('python2.7/collections.py') and function == '__init__':
return False
if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose'):
if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'):
return False
if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'):
return False
if filename.endswith('MetaArray.py') and function == '__getattr__':
for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array
if name in exc:
return False
if filename.endswith('flowchart/eq.py'):
return False
if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage':
return False
if excType is TypeError:
if filename.endswith('numpy/lib/function_base.py') and function == 'iterable':
return False
if excType is ZeroDivisionError:
if filename.endswith('python2.7/traceback.py'):
return False
return True
\ No newline at end of file
Installation
============
Pyqtgraph currently does not have (or really require) any installation scripts. The only requirement is that the pyqtgraph folder to be placed someplace importable. Most people will prefer to simply place pyqtgraph within a larger project folder. If you want to make pyqtgraph available system-wide, copy pyqtgraph to one of the directories listed in python's sys.path list.
\ No newline at end of file
Pyqtgraph does not really require any installation scripts. All that is needed is for the pyqtgraph folder to be placed someplace importable. Most people will prefer to simply place this folder within a larger project folder. If you want to make pyqtgraph available system-wide, use one of the methods listed below:
* **Debian, Ubuntu, and similar Linux:** Download the .deb file linked at the top of the pyqtgraph web page or install using apt by putting "deb http://luke.campagnola.me/debian dev/" in your /etc/apt/sources.list file and install the python-pyqtgraph package.
* **Arch Linux:** Looks like someone has posted unofficial packages for Arch (thanks windel). (https://aur.archlinux.org/packages.php?ID=62577)
* **Windows:** Download and run the .exe installer file linked at the top of the pyqtgraph web page.
* **Everybody (including OSX):** Download the .tar.gz source package linked at the top of the pyqtgraph web page, extract its contents, and run "python setup.py install" from within the extracted directory.
......@@ -26,13 +26,18 @@ win.show()
p = ui.plot
data = np.random.normal(size=(50,500), scale=100)
sizeArray = np.random.random(500) * 20.
ptr = 0
lastTime = time()
fps = None
def update():
global curve, data, ptr, p, lastTime, fps
p.clear()
curve = pg.ScatterPlotItem(x=data[ptr%10], y=data[(ptr+1)%10], pen='w', brush='b', size=10, pxMode=ui.pixelModeCheck.isChecked())
if ui.randCheck.isChecked():
size = sizeArray
else:
size = ui.sizeSpin.value()
curve = pg.ScatterPlotItem(x=data[ptr%50], y=data[(ptr+1)%50], pen='w', brush='b', size=size, pxMode=ui.pixelModeCheck.isChecked())
p.addItem(curve)
ptr += 1
now = time()
......
......@@ -14,20 +14,34 @@
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="PlotWidget" name="plot"/>
<item row="1" column="1">
<widget class="QSpinBox" name="sizeSpin">
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="pixelModeCheck">
<property name="text">
<string>pixel mode</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="identicalCheck">
<widget class="QLabel" name="label">
<property name="text">
<string>Identical</string>
<string>Size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="pixelModeCheck">
<item row="0" column="0" colspan="4">
<widget class="PlotWidget" name="plot"/>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="randCheck">
<property name="text">
<string>pixel mode</string>
<string>Randomize</string>
</property>
</widget>
</item>
......
......@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/ScatterPlotSpeedTestTemplate.ui'
#
# Created: Sun Sep 9 14:41:31 2012
# Created: Fri Sep 21 15:39:09 2012
# by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!
......@@ -20,22 +20,30 @@ class Ui_Form(object):
Form.resize(400, 300)
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.plot = PlotWidget(Form)
self.plot.setObjectName(_fromUtf8("plot"))
self.gridLayout.addWidget(self.plot, 0, 0, 1, 2)
self.identicalCheck = QtGui.QCheckBox(Form)
self.identicalCheck.setObjectName(_fromUtf8("identicalCheck"))
self.gridLayout.addWidget(self.identicalCheck, 1, 0, 1, 1)
self.sizeSpin = QtGui.QSpinBox(Form)
self.sizeSpin.setProperty("value", 10)
self.sizeSpin.setObjectName(_fromUtf8("sizeSpin"))
self.gridLayout.addWidget(self.sizeSpin, 1, 1, 1, 1)
self.pixelModeCheck = QtGui.QCheckBox(Form)
self.pixelModeCheck.setObjectName(_fromUtf8("pixelModeCheck"))
self.gridLayout.addWidget(self.pixelModeCheck, 1, 1, 1, 1)
self.gridLayout.addWidget(self.pixelModeCheck, 1, 3, 1, 1)
self.label = QtGui.QLabel(Form)
self.label.setObjectName(_fromUtf8("label"))
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.plot = PlotWidget(Form)
self.plot.setObjectName(_fromUtf8("plot"))
self.gridLayout.addWidget(self.plot, 0, 0, 1, 4)
self.randCheck = QtGui.QCheckBox(Form)
self.randCheck.setObjectName(_fromUtf8("randCheck"))
self.gridLayout.addWidget(self.randCheck, 1, 2, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.identicalCheck.setText(QtGui.QApplication.translate("Form", "Identical", None, QtGui.QApplication.UnicodeUTF8))
self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import PlotWidget
......@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/ScatterPlotSpeedTestTemplate.ui'
#
# Created: Sun Sep 9 14:41:31 2012
# Created: Fri Sep 21 15:39:09 2012
# by: pyside-uic 0.2.13 running on PySide 1.1.0
#
# WARNING! All changes made in this file will be lost!
......@@ -15,22 +15,30 @@ class Ui_Form(object):
Form.resize(400, 300)
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setObjectName("gridLayout")
self.plot = PlotWidget(Form)
self.plot.setObjectName("plot")
self.gridLayout.addWidget(self.plot, 0, 0, 1, 2)
self.identicalCheck = QtGui.QCheckBox(Form)
self.identicalCheck.setObjectName("identicalCheck")
self.gridLayout.addWidget(self.identicalCheck, 1, 0, 1, 1)
self.sizeSpin = QtGui.QSpinBox(Form)
self.sizeSpin.setProperty("value", 10)
self.sizeSpin.setObjectName("sizeSpin")
self.gridLayout.addWidget(self.sizeSpin, 1, 1, 1, 1)
self.pixelModeCheck = QtGui.QCheckBox(Form)
self.pixelModeCheck.setObjectName("pixelModeCheck")
self.gridLayout.addWidget(self.pixelModeCheck, 1, 1, 1, 1)
self.gridLayout.addWidget(self.pixelModeCheck, 1, 3, 1, 1)
self.label = QtGui.QLabel(Form)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.plot = PlotWidget(Form)
self.plot.setObjectName("plot")
self.gridLayout.addWidget(self.plot, 0, 0, 1, 4)
self.randCheck = QtGui.QCheckBox(Form)
self.randCheck.setObjectName("randCheck")
self.gridLayout.addWidget(self.randCheck, 1, 2, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.identicalCheck.setText(QtGui.QApplication.translate("Form", "Identical", None, QtGui.QApplication.UnicodeUTF8))
self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import PlotWidget
......@@ -73,6 +73,29 @@ class Butterworth(CtrlNode):
ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop'])
return ret
class ButterworthNotch(CtrlNode):
"""Butterworth notch filter"""
nodeName = 'ButterworthNotchFilter'
uiTemplate = [
('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}),
('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}),
('bidir', 'check', {'checked': True})
]
def processData(self, data):
s = self.stateGroup.state()
low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop'])
high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop'])
return low + high
class Mean(CtrlNode):
"""Filters data by taking the mean of a sliding window"""
......@@ -204,6 +227,7 @@ class RemovePeriodic(CtrlNode):
#('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000})
('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}),
('harmonics', 'intSpin', {'value': 30, 'min': 0}),
('samples', 'intSpin', {'value': 1, 'min': 1}),
]
def processData(self, data):
......@@ -224,22 +248,16 @@ class RemovePeriodic(CtrlNode):
## determine index range to check for this frequency
ind1 = int(np.floor(f / df))
ind2 = int(np.ceil(f / df))
ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1)
if ind1 > len(ft)/2.:
break
print "--->", f
print ind1, ind2, abs(ft[ind1-2:ind2+2])
print ft[ind1-2:ind2+2]
mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5
print "new mag:", mag
for j in range(ind1, ind2+1):
phase = np.angle(ft[j])
phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts.
re = mag * np.cos(phase)
im = mag * np.sin(phase)
ft[j] = re + im*1j
ft[len(ft)-j] = re - im*1j
print abs(ft[ind1-2:ind2+2])
print ft[ind1-2:ind2+2]
data2 = np.fft.ifft(ft).real
......
......@@ -29,7 +29,7 @@ import decimal, re
try:
import scipy.weave
USE_WEAVE = True
except ImportError:
except:
USE_WEAVE = False
from . import debug
......@@ -715,7 +715,7 @@ def makeQImage(imgData, alpha):
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
try:
buf = imgData.data
except AttributeError:
except AttributeError: ## happens when image data is non-contiguous
imgData = np.ascontiguousarray(imgData)
buf = imgData.data
......
......@@ -10,10 +10,10 @@ class GradientLegend(UIGraphicsItem):
points along the gradient.
"""
def __init__(self, view, size, offset):
def __init__(self, size, offset):
self.size = size
self.offset = offset
UIGraphicsItem.__init__(self, view)
UIGraphicsItem.__init__(self)
self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.brush = QtGui.QBrush(QtGui.QColor(200,0,0))
self.pen = QtGui.QPen(QtGui.QColor(0,0,0))
......
......@@ -267,7 +267,7 @@ class PlotCurveItem(GraphicsObject):
## 0(i4)
##
## All values are big endian--pack using struct.pack('>d') or struct.pack('>i')
if sys.version_info.major == 2:
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')])
......
......@@ -57,36 +57,36 @@ class PlotDataItem(GraphicsObject):
**Line style keyword arguments:**
========== ================================================
pen pen to use for drawing line between points.
pen Pen to use for drawing line between points.
Default is solid grey, 1px width. Use None to disable line drawing.
May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>`
shadowPen pen for secondary line to draw behind the primary line. disabled by default.
shadowPen Pen for secondary line to draw behind the primary line. disabled by default.
May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>`
fillLevel fill the area between the curve and fillLevel
fillBrush fill to use when fillLevel is specified
fillLevel Fill the area between the curve and fillLevel
fillBrush Fill to use when fillLevel is specified.
May be any single argument accepted by :func:`mkBrush() <pyqtgraph.mkBrush>`
========== ================================================
**Point style keyword arguments:**
**Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() <pyqtgraph.ScatterPlotItem.setData>` for more information)
============ ================================================
symbol (str) symbol to use for drawing points OR list of symbols, one per point. Default is no symbol.
options are o, s, t, d, +
symbolPen outline pen for drawing points OR list of pens, one per point
symbol Symbol to use for drawing points OR list of symbols, one per point. Default is no symbol.
Options are o, s, t, d, +, or any QPainterPath
symbolPen Outline pen for drawing points OR list of pens, one per point.
May be any single argument accepted by :func:`mkPen() <pyqtgraph.mkPen>`
symbolBrush brush for filling points OR list of brushes, one per point
symbolBrush Brush for filling points OR list of brushes, one per point.
May be any single argument accepted by :func:`mkBrush() <pyqtgraph.mkBrush>`
symbolSize diameter of symbols OR list of diameters
symbolSize Diameter of symbols OR list of diameters.
pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is
specified in data coordinates.
============ ================================================
**Optimization keyword arguments:**
========== ================================================
========== =====================================================================
identical *deprecated*
decimate (int) decimate data
========== ================================================
decimate (int) sub-sample data by selecting every nth sample before plotting
========== =====================================================================
**Meta-info keyword arguments:**
......@@ -239,7 +239,6 @@ class PlotDataItem(GraphicsObject):
Clear any data displayed by this item and display new data.
See :func:`__init__() <pyqtgraph.PlotDataItem.__init__>` for details; it accepts the same arguments.
"""
#self.clear()
prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True)
y = None
......@@ -265,7 +264,8 @@ class PlotDataItem(GraphicsObject):
if 'y' in data[0]:
y = np.array([d.get('y',None) for d in data])
for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']:
kargs[k] = [d.get(k, None) for d in data]
if k in data:
kargs[k] = [d.get(k, None) for d in data]
elif dt == 'MetaArray':
y = data.view(np.ndarray)
x = data.xvals(0).view(np.ndarray)
......@@ -296,6 +296,7 @@ class PlotDataItem(GraphicsObject):
## if symbol pen/brush are given with no symbol, then assume symbol is 'o'
if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs):
kargs['symbol'] = 'o'
......@@ -502,11 +503,11 @@ def dataType(obj):
else:
raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape))
elif isinstance(first, dict):
return 'listOfDict'
return 'listOfDicts'
else:
return 'listOfValues'
elif isinstance(obj, dict):
return 'dict'
return 'dictOfLists'
def isSequence(obj):
......
......@@ -44,7 +44,9 @@ def makeSymbolPixmap(size, pen, brush, symbol):
p.scale(size, size)
p.setPen(pen)
p.setBrush(brush)
p.drawPath(Symbols[symbol])
if isinstance(symbol, basestring):
symbol = Symbols[symbol]
p.drawPath(symbol)
p.end()
return QtGui.QPixmap(image)
......@@ -112,12 +114,14 @@ class ScatterPlotItem(GraphicsObject):
Otherwise, size is in scene coordinates and the spots scale with the view.
Default is True
*symbol* can be one (or a list) of:
* 'o' circle (default)
* 's' square
* 't' triangle
* 'd' diamond
* '+' plus
* any QPainterPath to specify custom symbol shapes. To properly obey the position and size,
custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also
possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape.
*pen* The pen (or list of pens) to use for drawing spot outlines.
*brush* The brush (or list of brushes) to use for filling spots.
*size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise,
......
......@@ -163,7 +163,8 @@ class ViewBox(GraphicsWidget):
if name is not None:
ViewBox.NamedViews[name] = self
ViewBox.updateAllViewLists()
self.destroyed.connect(lambda: ViewBox.forgetView(id(self), self.name))
sid = id(self)
self.destroyed.connect(lambda: ViewBox.forgetView(sid, name))
#self.destroyed.connect(self.unregister)
def unregister(self):
......@@ -1173,6 +1174,11 @@ class ViewBox(GraphicsWidget):
def updateViewLists(self):
try:
self.window()
except RuntimeError: ## this view has already been deleted; it will probably be collected shortly.
return
def cmpViews(a, b):
wins = 100 * cmp(a.window() is self.window(), b.window() is self.window())
alpha = cmp(a.name, b.name)
......
try:
import numpy as np