From a0b7e5a61c98e02a663ff19373f1f14c4cd5de4e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 19 Dec 2013 09:56:58 -0500 Subject: [PATCH] Corrected mouse clicking on PlotCurveItem - now uses stroke outline instead of path shape Added 'width' argument to PlotCurveItem.setClickable() --- examples/MouseSelection.py | 37 +++++++++++++ pyqtgraph/graphicsItems/PlotCurveItem.py | 67 +++++++++++++++++------- 2 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 examples/MouseSelection.py diff --git a/examples/MouseSelection.py b/examples/MouseSelection.py new file mode 100644 index 00000000..3a573751 --- /dev/null +++ b/examples/MouseSelection.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +""" +Demonstrates selecting plot curves by mouse click +""" +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 + +win = pg.plot() +win.setWindowTitle('pyqtgraph example: Plot data selection') + +curves = [ + pg.PlotCurveItem(y=np.sin(np.linspace(0, 20, 1000)), pen='r', clickable=True), + pg.PlotCurveItem(y=np.sin(np.linspace(1, 21, 1000)), pen='g', clickable=True), + pg.PlotCurveItem(y=np.sin(np.linspace(2, 22, 1000)), pen='b', clickable=True), + ] + +def plotClicked(curve): + global curves + for i,c in enumerate(curves): + if c is curve: + c.setPen('rgb'[i], width=3) + else: + c.setPen('rgb'[i], width=1) + + +for c in curves: + win.addItem(c) + c.sigClicked.connect(plotClicked) + +## 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_() diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index 7ee06338..d221bf74 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -53,9 +53,6 @@ class PlotCurveItem(GraphicsObject): """ GraphicsObject.__init__(self, kargs.get('parent', None)) self.clear() - self.path = None - self.fillPath = None - self._boundsCache = [None, None] ## this is disastrous for performance. #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) @@ -70,6 +67,7 @@ class PlotCurveItem(GraphicsObject): 'name': None, 'antialias': pg.getConfigOption('antialias'),\ 'connect': 'all', + 'mouseWidth': 8, # width of shape responding to mouse click } self.setClickable(kargs.get('clickable', False)) self.setData(*args, **kargs) @@ -80,9 +78,17 @@ class PlotCurveItem(GraphicsObject): return ints return interface in ints - def setClickable(self, s): - """Sets whether the item responds to mouse clicks.""" + def setClickable(self, s, width=None): + """Sets whether the item responds to mouse clicks. + + The *width* argument specifies the width in pixels orthogonal to the + curve that will respond to a mouse click. + """ self.clickable = s + if width is not None: + self.opts['mouseWidth'] = width + self._mouseShape = None + self._boundingRect = None def getData(self): @@ -148,6 +154,8 @@ class PlotCurveItem(GraphicsObject): w += pen.widthF()*0.7072 if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: w = max(w, spen.widthF()*0.7072) + if self.clickable: + w = max(w, self.opts['mouseWidth']//2 + 1) return w def boundingRect(self): @@ -171,6 +179,7 @@ class PlotCurveItem(GraphicsObject): #px += self._maxSpotWidth * 0.5 #py += self._maxSpotWidth * 0.5 self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) + return self._boundingRect def viewTransformChanged(self): @@ -328,6 +337,7 @@ class PlotCurveItem(GraphicsObject): self.path = None self.fillPath = None + self._mouseShape = None #self.xDisp = self.yDisp = None if 'name' in kargs: @@ -376,12 +386,14 @@ class PlotCurveItem(GraphicsObject): return path - def shape(self): + def getPath(self): if self.path is None: - try: - self.path = self.generatePath(*self.getData()) - except: + x,y = self.getData() + if x is None or len(x) == 0 or y is None or len(y) == 0: return QtGui.QPainterPath() + self.path = self.generatePath(*self.getData()) + self.fillPath = None + self._mouseShape = None return self.path @pg.debug.warnOnException ## raising an exception here causes crash @@ -396,14 +408,8 @@ class PlotCurveItem(GraphicsObject): x = None y = None - if self.path is None: - x,y = self.getData() - if x is None or len(x) == 0 or y is None or len(y) == 0: - return - self.path = self.generatePath(x,y) - self.fillPath = None - - path = self.path + path = self.getPath() + profiler('generate path') if self._exportOpts is not False: @@ -522,13 +528,36 @@ class PlotCurveItem(GraphicsObject): self.xDisp = None ## display values (after log / fft) self.yDisp = None self.path = None + self.fillPath = None + self._mouseShape = None + self._mouseBounds = None + self._boundsCache = [None, None] #del self.xData, self.yData, self.xDisp, self.yDisp, self.path + + def mouseShape(self): + """ + Return a QPainterPath representing the clickable shape of the curve + + """ + if self._mouseShape is None: + view = self.getViewBox() + if view is None: + return QtGui.QPainterPath() + stroker = QtGui.QPainterPathStroker() + path = self.getPath() + path = self.mapToItem(view, path) + stroker.setWidth(self.opts['mouseWidth']) + mousePath = stroker.createStroke(path) + self._mouseShape = self.mapFromItem(view, mousePath) + return self._mouseShape def mouseClickEvent(self, ev): if not self.clickable or ev.button() != QtCore.Qt.LeftButton: return - ev.accept() - self.sigClicked.emit(self) + if self.mouseShape().contains(ev.pos()): + ev.accept() + self.sigClicked.emit(self) + class ROIPlotItem(PlotCurveItem):