Fixed click signal propagation for PlotDataItem
This commit is contained in:
parent
59ad54c55e
commit
33bc81a121
@ -12,17 +12,50 @@ __all__ = ['PlotCurveItem']
|
|||||||
class PlotCurveItem(GraphicsObject):
|
class PlotCurveItem(GraphicsObject):
|
||||||
|
|
||||||
|
|
||||||
"""Class representing a single plot curve. Provides:
|
"""
|
||||||
- Fast data update
|
Class representing a single plot curve. Instances of this class are created
|
||||||
- FFT display mode
|
automatically as part of PlotDataItem; these rarely need to be instantiated
|
||||||
- shadow pen
|
directly.
|
||||||
- mouse interaction
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- Fast data update
|
||||||
|
- 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)
|
sigPlotChanged = QtCore.Signal(object)
|
||||||
sigClicked = 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):
|
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)
|
GraphicsObject.__init__(self, parent)
|
||||||
self.clear()
|
self.clear()
|
||||||
self.path = None
|
self.path = None
|
||||||
@ -62,6 +95,7 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
return interface in ints
|
return interface in ints
|
||||||
|
|
||||||
def setClickable(self, s):
|
def setClickable(self, s):
|
||||||
|
"""Sets whether the item responds to mouse clicks."""
|
||||||
self.clickable = s
|
self.clickable = s
|
||||||
|
|
||||||
|
|
||||||
@ -127,18 +161,25 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
#return self.metaData
|
#return self.metaData
|
||||||
|
|
||||||
def setPen(self, *args, **kargs):
|
def setPen(self, *args, **kargs):
|
||||||
|
"""Set the pen used to draw the curve."""
|
||||||
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
self.opts['pen'] = fn.mkPen(*args, **kargs)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setShadowPen(self, *args, **kargs):
|
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.opts['shadowPen'] = fn.mkPen(*args, **kargs)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setBrush(self, *args, **kargs):
|
def setBrush(self, *args, **kargs):
|
||||||
|
"""Set the brush used when filling the area under the curve"""
|
||||||
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
self.opts['brush'] = fn.mkBrush(*args, **kargs)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def setFillLevel(self, level):
|
def setFillLevel(self, level):
|
||||||
|
"""Set the level filled to when filling under the curve"""
|
||||||
self.opts['fillLevel'] = level
|
self.opts['fillLevel'] = level
|
||||||
self.fillPath = None
|
self.fillPath = None
|
||||||
self.update()
|
self.update()
|
||||||
@ -177,7 +218,9 @@ class PlotCurveItem(GraphicsObject):
|
|||||||
#self.update()
|
#self.update()
|
||||||
|
|
||||||
def setData(self, *args, **kargs):
|
def setData(self, *args, **kargs):
|
||||||
"""Same as updateData()"""
|
"""
|
||||||
|
Accepts most of the same arguments as __init__.
|
||||||
|
"""
|
||||||
self.updateData(*args, **kargs)
|
self.updateData(*args, **kargs)
|
||||||
|
|
||||||
def updateData(self, *args, **kargs):
|
def updateData(self, *args, **kargs):
|
||||||
|
@ -24,15 +24,18 @@ class PlotDataItem(GraphicsObject):
|
|||||||
usually created by plot() methods such as :func:`pyqtgraph.plot` and
|
usually created by plot() methods such as :func:`pyqtgraph.plot` and
|
||||||
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`.
|
:func:`PlotItem.plot() <pyqtgraph.PlotItem.plot>`.
|
||||||
|
|
||||||
===================== ==============================================
|
============================== ==============================================
|
||||||
**Signals:**
|
**Signals:**
|
||||||
sigPlotChanged(self) Emitted when the data in this item is updated.
|
sigPlotChanged(self) Emitted when the data in this item is updated.
|
||||||
sigClicked(self) Emitted when the item is clicked.
|
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)
|
sigPlotChanged = QtCore.Signal(object)
|
||||||
sigClicked = QtCore.Signal(object)
|
sigClicked = QtCore.Signal(object)
|
||||||
|
sigPointsClicked = QtCore.Signal(object, object)
|
||||||
|
|
||||||
def __init__(self, *args, **kargs):
|
def __init__(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
@ -109,6 +112,10 @@ class PlotDataItem(GraphicsObject):
|
|||||||
self.curve.setParentItem(self)
|
self.curve.setParentItem(self)
|
||||||
self.scatter.setParentItem(self)
|
self.scatter.setParentItem(self)
|
||||||
|
|
||||||
|
self.curve.sigClicked.connect(self.curveClicked)
|
||||||
|
self.scatter.sigClicked.connect(self.scatterClicked)
|
||||||
|
|
||||||
|
|
||||||
#self.clear()
|
#self.clear()
|
||||||
self.opts = {
|
self.opts = {
|
||||||
'fftMode': False,
|
'fftMode': False,
|
||||||
@ -127,6 +134,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
'symbolPen': (200,200,200),
|
'symbolPen': (200,200,200),
|
||||||
'symbolBrush': (50, 50, 150),
|
'symbolBrush': (50, 50, 150),
|
||||||
'identical': False,
|
'identical': False,
|
||||||
|
|
||||||
|
'data': None,
|
||||||
}
|
}
|
||||||
self.setData(*args, **kargs)
|
self.setData(*args, **kargs)
|
||||||
|
|
||||||
@ -150,8 +159,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
self.xDisp = self.yDisp = None
|
self.xDisp = self.yDisp = None
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
def setLogMode(self, mode):
|
def setLogMode(self, xMode, yMode):
|
||||||
self.opts['logMode'] = mode
|
self.opts['logMode'] = (xMode, yMode)
|
||||||
self.xDisp = self.yDisp = None
|
self.xDisp = self.yDisp = None
|
||||||
self.updateItems()
|
self.updateItems()
|
||||||
|
|
||||||
@ -244,7 +253,7 @@ class PlotDataItem(GraphicsObject):
|
|||||||
data = args[0]
|
data = args[0]
|
||||||
dt = dataType(data)
|
dt = dataType(data)
|
||||||
if dt == 'empty':
|
if dt == 'empty':
|
||||||
return
|
pass
|
||||||
elif dt == 'listOfValues':
|
elif dt == 'listOfValues':
|
||||||
y = np.array(data)
|
y = np.array(data)
|
||||||
elif dt == 'Nx2array':
|
elif dt == 'Nx2array':
|
||||||
@ -260,6 +269,8 @@ class PlotDataItem(GraphicsObject):
|
|||||||
x = np.array([d.get('x',None) for d in data])
|
x = np.array([d.get('x',None) for d in data])
|
||||||
if 'y' in data[0]:
|
if 'y' in data[0]:
|
||||||
y = np.array([d.get('y',None) for d in data])
|
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':
|
elif dt == 'MetaArray':
|
||||||
y = data.view(np.ndarray)
|
y = data.view(np.ndarray)
|
||||||
x = data.xvals(0).view(np.ndarray)
|
x = data.xvals(0).view(np.ndarray)
|
||||||
@ -349,8 +360,9 @@ class PlotDataItem(GraphicsObject):
|
|||||||
curveArgs[v] = self.opts[k]
|
curveArgs[v] = self.opts[k]
|
||||||
|
|
||||||
scatterArgs = {}
|
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')]:
|
||||||
scatterArgs[v] = self.opts[k]
|
if k in self.opts:
|
||||||
|
scatterArgs[v] = self.opts[k]
|
||||||
|
|
||||||
x,y = self.getData()
|
x,y = self.getData()
|
||||||
|
|
||||||
@ -398,6 +410,11 @@ class PlotDataItem(GraphicsObject):
|
|||||||
x = np.log10(x)
|
x = np.log10(x)
|
||||||
if self.opts['logMode'][1]:
|
if self.opts['logMode'][1]:
|
||||||
y = np.log10(y)
|
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.xDisp = x
|
||||||
self.yDisp = y
|
self.yDisp = y
|
||||||
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
|
#print self.yDisp.shape, self.yDisp.min(), self.yDisp.max()
|
||||||
@ -438,6 +455,13 @@ class PlotDataItem(GraphicsObject):
|
|||||||
def appendData(self, *args, **kargs):
|
def appendData(self, *args, **kargs):
|
||||||
pass
|
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):
|
def dataType(obj):
|
||||||
if hasattr(obj, '__len__') and len(obj) == 0:
|
if hasattr(obj, '__len__') and len(obj) == 0:
|
||||||
|
@ -7,7 +7,23 @@ import scipy.stats
|
|||||||
|
|
||||||
__all__ = ['ScatterPlotItem', 'SpotItem']
|
__all__ = ['ScatterPlotItem', 'SpotItem']
|
||||||
class ScatterPlotItem(GraphicsObject):
|
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)
|
#sigPointClicked = QtCore.Signal(object, object)
|
||||||
sigClicked = QtCore.Signal(object, object) ## self, points
|
sigClicked = QtCore.Signal(object, object) ## self, points
|
||||||
sigPlotChanged = QtCore.Signal(object)
|
sigPlotChanged = QtCore.Signal(object)
|
||||||
@ -37,35 +53,37 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
|
|
||||||
def setData(self, *args, **kargs):
|
def setData(self, *args, **kargs):
|
||||||
"""
|
"""
|
||||||
Ordered Arguments:
|
**Ordered Arguments:**
|
||||||
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.
|
* 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:**
|
||||||
{'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method
|
*spots* Optional list of dicts. Each dict specifies parameters for a single spot:
|
||||||
of passing in data for the corresponding arguments.
|
{'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method
|
||||||
*x*,*y*: 1D arrays of x,y values.
|
of passing in data for the corresponding arguments.
|
||||||
*pos*: 2D structure of x,y pairs (such as Nx2 array or list of tuples)
|
*x*,*y* 1D arrays of x,y values.
|
||||||
*pxMode*: If True, spots are always the same size regardless of scaling, and size is given in px.
|
*pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples)
|
||||||
Otherwise, size is in scene coordinates and the spots scale with the view.
|
*pxMode* If True, spots are always the same size regardless of scaling, and size is given in px.
|
||||||
Default is True
|
Otherwise, size is in scene coordinates and the spots scale with the view.
|
||||||
*identical*: If True, all spots are forced to look identical.
|
Default is True
|
||||||
This can result in performance enhancement.
|
*identical* If True, all spots are forced to look identical.
|
||||||
Default is False
|
This can result in performance enhancement.
|
||||||
*symbol* can be one (or a list) of:
|
Default is False
|
||||||
'o' circle (default)
|
*symbol* can be one (or a list) of:
|
||||||
's' square
|
|
||||||
't' triangle
|
|
||||||
'd' diamond
|
|
||||||
'+' plus
|
|
||||||
|
|
||||||
*pen*: The pen (or list of pens) to use for drawing spot outlines.
|
* 'o' circle (default)
|
||||||
*brush*: The brush (or list of brushes) to use for filling spots.
|
* 's' square
|
||||||
*size*: The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise,
|
* 't' triangle
|
||||||
it is in the item's local coordinate system.
|
* 'd' diamond
|
||||||
*data*: a list of python objects used to uniquely identify each spot.
|
* '+' 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.
|
||||||
|
====================== =================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.clear()
|
self.clear()
|
||||||
@ -149,6 +167,9 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
setMethod = getattr(self, 'set' + k[0].upper() + k[1:])
|
setMethod = getattr(self, 'set' + k[0].upper() + k[1:])
|
||||||
setMethod(kargs[k])
|
setMethod(kargs[k])
|
||||||
|
|
||||||
|
if 'data' in kargs:
|
||||||
|
self.setPointData(kargs['data'])
|
||||||
|
|
||||||
self.updateSpots()
|
self.updateSpots()
|
||||||
|
|
||||||
|
|
||||||
@ -183,7 +204,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
#self.data[k].append(v)
|
#self.data[k].append(v)
|
||||||
|
|
||||||
def setPoints(self, *args, **kargs):
|
def setPoints(self, *args, **kargs):
|
||||||
"""Deprecated; use setData"""
|
##Deprecated; use setData
|
||||||
return self.setData(*args, **kargs)
|
return self.setData(*args, **kargs)
|
||||||
|
|
||||||
#def setPoints(self, spots=None, x=None, y=None, data=None):
|
#def setPoints(self, spots=None, x=None, y=None, data=None):
|
||||||
@ -259,6 +280,16 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
self.opts['size'] = size
|
self.opts['size'] = size
|
||||||
self.updateSpots()
|
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):
|
def setIdentical(self, ident):
|
||||||
self.opts['identical'] = ident
|
self.opts['identical'] = ident
|
||||||
self.updateSpots()
|
self.updateSpots()
|
||||||
@ -354,6 +385,12 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
symbol = self.data['symbol'].copy()
|
symbol = self.data['symbol'].copy()
|
||||||
symbol[symbol==''] = self.opts['symbol']
|
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)):
|
for i in xrange(len(self.data)):
|
||||||
s = self.data[i]
|
s = self.data[i]
|
||||||
pos = Point(s['x'], s['y'])
|
pos = Point(s['x'], s['y'])
|
||||||
@ -373,7 +410,7 @@ class ScatterPlotItem(GraphicsObject):
|
|||||||
#ymn = min(ymn, pos[1]-psize)
|
#ymn = min(ymn, pos[1]-psize)
|
||||||
#ymx = max(ymx, 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.spots.append(item)
|
||||||
self.data[i]['spot'] = item
|
self.data[i]['spot'] = item
|
||||||
#if self.optimize:
|
#if self.optimize:
|
||||||
|
Loading…
Reference in New Issue
Block a user