Fixed click signal propagation for PlotDataItem

This commit is contained in:
Luke Campagnola 2012-04-21 15:55:27 -04:00
parent 59ad54c55e
commit 33bc81a121
3 changed files with 149 additions and 45 deletions

View File

@ -12,17 +12,50 @@ __all__ = ['PlotCurveItem']
class PlotCurveItem(GraphicsObject):
"""Class representing a single plot curve. Provides:
"""
Class representing a single plot curve. Instances of this class are created
automatically as part of PlotDataItem; these rarely need to be instantiated
directly.
Features:
- Fast data update
- FFT display mode
- shadow pen
- mouse interaction
- FFT display mode (accessed via PlotItem context menu)
- Fill under curve
- Mouse interaction
==================== ===============================================
**Signals:**
sigPlotChanged(self) Emitted when the data being plotted has changed
sigClicked(self) Emitted when the curve is clicked
==================== ===============================================
"""
sigPlotChanged = QtCore.Signal(object)
sigClicked = QtCore.Signal(object)
def __init__(self, y=None, x=None, fillLevel=None, copy=False, pen=None, shadowPen=None, brush=None, parent=None, clickable=False):
"""
============== =======================================================
**Arguments:**
x, y (numpy arrays) Data to show
pen Pen to use when drawing. Any single argument accepted by
:func:`mkPen <pyqtgraph.mkPen>` is allowed.
shadowPen Pen for drawing behind the primary pen. Usually this
is used to emphasize the curve by providing a
high-contrast border. Any single argument accepted by
:func:`mkPen <pyqtgraph.mkPen>` is allowed.
fillLevel (float or None) Fill the area 'under' the curve to
*fillLevel*
brush QBrush to use when filling. Any single argument accepted
by :func:`mkBrush <pyqtgraph.mkBrush>` is allowed.
clickable If True, the item will emit sigClicked when it is
clicked on.
============== =======================================================
"""
GraphicsObject.__init__(self, parent)
self.clear()
self.path = None
@ -62,6 +95,7 @@ class PlotCurveItem(GraphicsObject):
return interface in ints
def setClickable(self, s):
"""Sets whether the item responds to mouse clicks."""
self.clickable = s
@ -127,18 +161,25 @@ class PlotCurveItem(GraphicsObject):
#return self.metaData
def setPen(self, *args, **kargs):
"""Set the pen used to draw the curve."""
self.opts['pen'] = fn.mkPen(*args, **kargs)
self.update()
def setShadowPen(self, *args, **kargs):
"""Set the shadow pen used to draw behind tyhe primary pen.
This pen must have a larger width than the primary
pen to be visible.
"""
self.opts['shadowPen'] = fn.mkPen(*args, **kargs)
self.update()
def setBrush(self, *args, **kargs):
"""Set the brush used when filling the area under the curve"""
self.opts['brush'] = fn.mkBrush(*args, **kargs)
self.update()
def setFillLevel(self, level):
"""Set the level filled to when filling under the curve"""
self.opts['fillLevel'] = level
self.fillPath = None
self.update()
@ -177,7 +218,9 @@ class PlotCurveItem(GraphicsObject):
#self.update()
def setData(self, *args, **kargs):
"""Same as updateData()"""
"""
Accepts most of the same arguments as __init__.
"""
self.updateData(*args, **kargs)
def updateData(self, *args, **kargs):

View File

@ -24,15 +24,18 @@ class PlotDataItem(GraphicsObject):
usually created by plot() methods such as :func:`pyqtgraph.plot` and
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`.
===================== ==============================================
============================== ==============================================
**Signals:**
sigPlotChanged(self) Emitted when the data in this item is updated.
sigClicked(self) Emitted when the item is clicked.
===================== ==============================================
sigPointsClicked(self, points) Emitted when a plot point is clicked
Sends the list of points under the mouse.
============================== ==============================================
"""
sigPlotChanged = QtCore.Signal(object)
sigClicked = QtCore.Signal(object)
sigPointsClicked = QtCore.Signal(object, object)
def __init__(self, *args, **kargs):
"""
@ -109,6 +112,10 @@ class PlotDataItem(GraphicsObject):
self.curve.setParentItem(self)
self.scatter.setParentItem(self)
self.curve.sigClicked.connect(self.curveClicked)
self.scatter.sigClicked.connect(self.scatterClicked)
#self.clear()
self.opts = {
'fftMode': False,
@ -127,6 +134,8 @@ class PlotDataItem(GraphicsObject):
'symbolPen': (200,200,200),
'symbolBrush': (50, 50, 150),
'identical': False,
'data': None,
}
self.setData(*args, **kargs)
@ -150,8 +159,8 @@ class PlotDataItem(GraphicsObject):
self.xDisp = self.yDisp = None
self.updateItems()
def setLogMode(self, mode):
self.opts['logMode'] = mode
def setLogMode(self, xMode, yMode):
self.opts['logMode'] = (xMode, yMode)
self.xDisp = self.yDisp = None
self.updateItems()
@ -244,7 +253,7 @@ class PlotDataItem(GraphicsObject):
data = args[0]
dt = dataType(data)
if dt == 'empty':
return
pass
elif dt == 'listOfValues':
y = np.array(data)
elif dt == 'Nx2array':
@ -260,6 +269,8 @@ class PlotDataItem(GraphicsObject):
x = np.array([d.get('x',None) for d in data])
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]
elif dt == 'MetaArray':
y = data.view(np.ndarray)
x = data.xvals(0).view(np.ndarray)
@ -349,7 +360,8 @@ class PlotDataItem(GraphicsObject):
curveArgs[v] = self.opts[k]
scatterArgs = {}
for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size')]:
for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data')]:
if k in self.opts:
scatterArgs[v] = self.opts[k]
x,y = self.getData()
@ -398,6 +410,11 @@ class PlotDataItem(GraphicsObject):
x = np.log10(x)
if self.opts['logMode'][1]:
y = np.log10(y)
if any(self.opts['logMode']): ## re-check for NANs after log
nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y)
if any(nanMask):
x = x[~nanMask]
y = y[~nanMask]
self.xDisp = x
self.yDisp = y
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
@ -438,6 +455,13 @@ class PlotDataItem(GraphicsObject):
def appendData(self, *args, **kargs):
pass
def curveClicked(self):
self.sigClicked.emit(self)
def scatterClicked(self, plt, points):
self.sigClicked.emit(self)
self.sigPointsClicked.emit(self, points)
def dataType(obj):
if hasattr(obj, '__len__') and len(obj) == 0:

View File

@ -7,7 +7,23 @@ import scipy.stats
__all__ = ['ScatterPlotItem', 'SpotItem']
class ScatterPlotItem(GraphicsObject):
"""
Displays a set of x/y points. Instances of this class are created
automatically as part of PlotDataItem; these rarely need to be instantiated
directly.
The size, shape, pen, and fill brush may be set for each point individually
or for all points.
======================== ===============================================
**Signals:**
sigPlotChanged(self) Emitted when the data being plotted has changed
sigClicked(self, points) Emitted when the curve is clicked. Sends a list
of all the points under the mouse pointer.
======================== ===============================================
"""
#sigPointClicked = QtCore.Signal(object, object)
sigClicked = QtCore.Signal(object, object) ## self, points
sigPlotChanged = QtCore.Signal(object)
@ -37,35 +53,37 @@ class ScatterPlotItem(GraphicsObject):
def setData(self, *args, **kargs):
"""
Ordered Arguments:
If there is only one unnamed argument, it will be interpreted like the 'spots' argument.
**Ordered Arguments:**
If there are two unnamed arguments, they will be interpreted as sequences of x and y values.
* If there is only one unnamed argument, it will be interpreted like the 'spots' argument.
* If there are two unnamed arguments, they will be interpreted as sequences of x and y values.
Keyword Arguments:
*spots*: Optional list of dicts. Each dict specifies parameters for a single spot:
====================== =================================================
**Keyword Arguments:**
*spots* Optional list of dicts. Each dict specifies parameters for a single spot:
{'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method
of passing in data for the corresponding arguments.
*x*,*y*: 1D arrays of x,y values.
*pos*: 2D structure of x,y pairs (such as Nx2 array or list of tuples)
*pxMode*: If True, spots are always the same size regardless of scaling, and size is given in px.
*x*,*y* 1D arrays of x,y values.
*pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples)
*pxMode* If True, spots are always the same size regardless of scaling, and size is given in px.
Otherwise, size is in scene coordinates and the spots scale with the view.
Default is True
*identical*: If True, all spots are forced to look identical.
*identical* If True, all spots are forced to look identical.
This can result in performance enhancement.
Default is False
*symbol* can be one (or a list) of:
'o' circle (default)
's' square
't' triangle
'd' diamond
'+' plus
*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,
* 'o' circle (default)
* 's' square
* 't' triangle
* 'd' diamond
* '+' plus
*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,
it is in the item's local coordinate system.
*data*: a list of python objects used to uniquely identify each spot.
*data* a list of python objects used to uniquely identify each spot.
====================== =================================================
"""
self.clear()
@ -149,6 +167,9 @@ class ScatterPlotItem(GraphicsObject):
setMethod = getattr(self, 'set' + k[0].upper() + k[1:])
setMethod(kargs[k])
if 'data' in kargs:
self.setPointData(kargs['data'])
self.updateSpots()
@ -183,7 +204,7 @@ class ScatterPlotItem(GraphicsObject):
#self.data[k].append(v)
def setPoints(self, *args, **kargs):
"""Deprecated; use setData"""
##Deprecated; use setData
return self.setData(*args, **kargs)
#def setPoints(self, spots=None, x=None, y=None, data=None):
@ -259,6 +280,16 @@ class ScatterPlotItem(GraphicsObject):
self.opts['size'] = size
self.updateSpots()
def setPointData(self, data):
if isinstance(data, np.ndarray) or isinstance(data, list):
if self.data is None:
raise Exception("Must set xy data before setting meta data.")
if len(data) != len(self.data):
raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(self.data)))
self.data['data'] = data
self.updateSpots()
def setIdentical(self, ident):
self.opts['identical'] = ident
self.updateSpots()
@ -354,6 +385,12 @@ class ScatterPlotItem(GraphicsObject):
symbol = self.data['symbol'].copy()
symbol[symbol==''] = self.opts['symbol']
data = self.data['data'].copy()
if 'data' in self.opts:
data[data==None] = self.opts['data']
for i in xrange(len(self.data)):
s = self.data[i]
pos = Point(s['x'], s['y'])
@ -373,7 +410,7 @@ class ScatterPlotItem(GraphicsObject):
#ymn = min(ymn, pos[1]-psize)
#ymx = max(ymx, pos[1]+psize)
item = self.mkSpot(pos, size[i], self.opts['pxMode'], brush[i], pen[i], s['data'], symbol=symbol[i], index=len(self.spots))
item = self.mkSpot(pos, size[i], self.opts['pxMode'], brush[i], pen[i], data[i], symbol=symbol[i], index=len(self.spots))
self.spots.append(item)
self.data[i]['spot'] = item
#if self.optimize: