From 815746895dd2b7e54d8733f133a9b5be092ea89e Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 13 Feb 2013 17:11:47 -0500 Subject: [PATCH 1/5] Fixed GLSurfacePlot bug --- examples/GLSurfacePlot.py | 2 +- pyqtgraph/opengl/items/GLSurfacePlotItem.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/GLSurfacePlot.py b/examples/GLSurfacePlot.py index c901d51e..d2151c46 100644 --- a/examples/GLSurfacePlot.py +++ b/examples/GLSurfacePlot.py @@ -62,7 +62,7 @@ w.addItem(p3) ## Animated example ## compute surface vertex data -cols = 100 +cols = 90 rows = 100 x = np.linspace(-8, 8, cols+1).reshape(cols+1,1) y = np.linspace(-8, 8, rows+1).reshape(1,rows+1) diff --git a/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/pyqtgraph/opengl/items/GLSurfacePlotItem.py index 69080fad..46c54fc2 100644 --- a/pyqtgraph/opengl/items/GLSurfacePlotItem.py +++ b/pyqtgraph/opengl/items/GLSurfacePlotItem.py @@ -127,8 +127,8 @@ class GLSurfacePlotItem(GLMeshItem): def generateFaces(self): - cols = self._z.shape[0]-1 - rows = self._z.shape[1]-1 + cols = self._z.shape[1]-1 + rows = self._z.shape[0]-1 faces = np.empty((cols*rows*2, 3), dtype=np.uint) rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) From 9c70d948aab24231ebe4395a8148c304f4d7d352 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Thu, 14 Feb 2013 14:22:30 -0500 Subject: [PATCH 2/5] Fixed ArrowItem auto-range --- pyqtgraph/graphicsItems/ArrowItem.py | 35 +++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py index 0c6c0718..dcede02a 100644 --- a/pyqtgraph/graphicsItems/ArrowItem.py +++ b/pyqtgraph/graphicsItems/ArrowItem.py @@ -84,8 +84,41 @@ class ArrowItem(QtGui.QGraphicsPathItem): def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.Antialiasing) QtGui.QGraphicsPathItem.paint(self, p, *args) + + #p.setPen(fn.mkPen('r')) + #p.setBrush(fn.mkBrush(None)) + #p.drawRect(self.boundingRect()) def shape(self): #if not self.opts['pxMode']: #return QtGui.QGraphicsPathItem.shape(self) - return self.path \ No newline at end of file + return self.path + + ## dataBounds and pixelPadding methods are provided to ensure ViewBox can + ## properly auto-range + def dataBounds(self, ax, frac, orthoRange=None): + pw = 0 + pen = self.pen() + if not pen.isCosmetic(): + pw = pen.width() * 0.7072 + if self.opts['pxMode']: + return [0,0] + else: + br = self.boundingRect() + if ax == 0: + return [br.left()-pw, br.right()+pw] + else: + return [br.top()-pw, br.bottom()+pw] + + def pixelPadding(self): + pad = 0 + if self.opts['pxMode']: + br = self.boundingRect() + pad += (br.width()**2 + br.height()**2) ** 0.5 + pen = self.pen() + if pen.isCosmetic(): + pad += max(1, pen.width()) * 0.7072 + return pad + + + \ No newline at end of file From 783af1a9184dac2e8f15afd31b3ee8234e615407 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Feb 2013 11:01:03 -0500 Subject: [PATCH 3/5] fix for python 2.6 compatibility --- pyqtgraph/opengl/MeshData.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py index 3e5938d1..170074b9 100644 --- a/pyqtgraph/opengl/MeshData.py +++ b/pyqtgraph/opengl/MeshData.py @@ -436,7 +436,7 @@ class MeshData(object): elif self._faceColorsIndexedByFaces is not None: names.append('_faceColorsIndexedByFaces') - state = {n:getattr(self, n) for n in names} + state = dict([(n,getattr(self, n)) for n in names]) return pickle.dumps(state) def restore(self, state): From 86861b5a061b0733a56a23fcacf9624216f82098 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Fri, 15 Feb 2013 15:22:05 -0500 Subject: [PATCH 4/5] Fixed dock dragging on pyside Added imageview.setImage argument documentation --- pyqtgraph/dockarea/Dock.py | 14 ++++++++++++++ pyqtgraph/dockarea/DockArea.py | 14 ++++++++++++++ pyqtgraph/imageview/ImageView.py | 7 ++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py index 35781535..e07ae6a7 100644 --- a/pyqtgraph/dockarea/Dock.py +++ b/pyqtgraph/dockarea/Dock.py @@ -186,6 +186,7 @@ class Dock(QtGui.QWidget, DockDrop): def startDrag(self): + print('startDrag') self.drag = QtGui.QDrag(self) mime = QtCore.QMimeData() #mime.setPlainText("asd") @@ -212,6 +213,19 @@ class Dock(QtGui.QWidget, DockDrop): def __repr__(self): return "" % (self.name(), self.stretch()) + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) class DockLabel(VerticalLabel): diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py index 78d512f3..50769ce6 100644 --- a/pyqtgraph/dockarea/DockArea.py +++ b/pyqtgraph/dockarea/DockArea.py @@ -293,5 +293,19 @@ class DockArea(Container, QtGui.QWidget, DockDrop): self.home.removeTempArea(self) #self.close() + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) + \ No newline at end of file diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 5c6573e3..f0c13a60 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -205,7 +205,12 @@ class ImageView(QtGui.QWidget): *axes* Dictionary indicating the interpretation for each axis. This is only needed to override the default guess. Format is:: - {'t':0, 'x':1, 'y':2, 'c':3}; + {'t':0, 'x':1, 'y':2, 'c':3}; + + *pos* Change the position of the displayed image + *scale* Change the scale of the displayed image + *transform* Set the transform of the dispalyed image. This option overrides *pos* + and *scale*. ============== ======================================================================= """ prof = debug.Profiler('ImageView.setImage', disabled=True) From 412e1d2ec8c907430a1e2e21d4ceec1c26997213 Mon Sep 17 00:00:00 2001 From: Luke Campagnola Date: Wed, 20 Feb 2013 11:13:50 -0500 Subject: [PATCH 5/5] doc updates ViewBox: made padding more consistent for all auto-ranging methods, deprecated autoRange(item=) in favor of autoRange(items=) --- doc/source/how_to_use.rst | 2 +- doc/source/qtcrashcourse.rst | 10 +++- examples/ImageView.py | 16 +++++-- examples/PlotWidget.py | 1 + pyqtgraph/PlotData.py | 2 +- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 55 +++++++++++++++------- 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/doc/source/how_to_use.rst b/doc/source/how_to_use.rst index 53a3d2b0..0e00af59 100644 --- a/doc/source/how_to_use.rst +++ b/doc/source/how_to_use.rst @@ -17,7 +17,7 @@ Pyqtgraph makes it very easy to visualize data from the command line. Observe:: import pyqtgraph as pg pg.plot(data) # data can be a list of values or a numpy array -The example above would open a window displaying a line plot of the data given. The call to :func:`pg.plot ` returns a handle to the :class:`plot widget ` that is created, allowing more data to be added to the same window. +The example above would open a window displaying a line plot of the data given. The call to :func:`pg.plot ` returns a handle to the :class:`plot widget ` that is created, allowing more data to be added to the same window. **Note:** interactive plotting from the python prompt is only available with PyQt; PySide does not run the Qt event loop while the interactive prompt is running. If you wish to use pyqtgraph interactively with PySide, see the 'console' :ref:`example `. Further examples:: diff --git a/doc/source/qtcrashcourse.rst b/doc/source/qtcrashcourse.rst index ca2da797..23a561b9 100644 --- a/doc/source/qtcrashcourse.rst +++ b/doc/source/qtcrashcourse.rst @@ -66,6 +66,12 @@ Signals, Slots, and Events [ to be continued.. please post a request on the pyqtgraph forum if you'd like to read more ] +Qt detects and reacts to user interaction by executing its *event loop*. + + - what happens in the event loop? + - when do I need to use QApplication.exec_() ? + - what control do I have over event loop execution? (QApplication.processEvents) + GraphicsView and GraphicsItems ------------------------------ @@ -79,8 +85,8 @@ Mouse and Keyboard Input ------------------------ -QTimer, the Event Loop, and Multi-Threading -------------------------------------------- +QTimer, Multi-Threading +----------------------- Multi-threading vs Multi-processing in Qt diff --git a/examples/ImageView.py b/examples/ImageView.py index 5edae00b..f11ce0f7 100644 --- a/examples/ImageView.py +++ b/examples/ImageView.py @@ -1,4 +1,15 @@ # -*- coding: utf-8 -*- +""" +This example demonstrates the use of ImageView, which is a high-level widget for +displaying and analyzing 2D and 3D data. ImageView provides: + + 1. A zoomable region (ViewBox) for displaying the image + 2. A combination histogram and gradient editor (HistogramLUTItem) for + controlling the visual appearance of the image + 3. A timeline for selecting the currently displayed frame (for 3D data only). + 4. Tools for very basic analysis of image data (see ROI and Norm buttons) + +""" ## Add path to library (just for examples; you do not need this) import initExample @@ -22,9 +33,6 @@ img = img[np.newaxis,:,:] decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis] data = np.random.normal(size=(100, 200, 200)) data += img * decay - -#for i in range(data.shape[0]): - #data[i] += 10*exp(-(2.*i)/data.shape[0]) data += 2 ## Add time-varying signal @@ -37,7 +45,7 @@ sig = sig[:,np.newaxis,np.newaxis] * 3 data[:,50:60,50:60] += sig -## Display the data +## Display the data and assign each frame a time value from 1.0 to 3.0 imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0])) ## Start Qt event loop unless running in interactive mode. diff --git a/examples/PlotWidget.py b/examples/PlotWidget.py index 3cca8f7a..2aa118f2 100644 --- a/examples/PlotWidget.py +++ b/examples/PlotWidget.py @@ -66,6 +66,7 @@ for i in range(0, 5): ## Test large numbers curve = pw3.plot(np.random.normal(size=100)*1e0, clickable=True) +curve.curve.setClickable(True) curve.setPen('w') ## white pen curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True)) diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py index 18531c14..0bf13ca8 100644 --- a/pyqtgraph/PlotData.py +++ b/pyqtgraph/PlotData.py @@ -22,7 +22,7 @@ class PlotData(object): self.maxVals = {} ## cache for max/min self.minVals = {} - def addFields(self, fields): + def addFields(self, **fields): for f in fields: if f not in self.fields: self.fields[f] = None diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index ce1d61f9..b562132c 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -336,7 +336,7 @@ class ViewBox(GraphicsWidget): print("make qrectf failed:", self.state['targetRange']) raise - def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=True, disableAutoRange=True): + def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): """ Set the visible range of the ViewBox. Must specify at least one of *range*, *xRange*, or *yRange*. @@ -347,7 +347,8 @@ class ViewBox(GraphicsWidget): *xRange* (min,max) The range that should be visible along the x-axis. *yRange* (min,max) The range that should be visible along the y-axis. *padding* (float) Expand the view by a fraction of the requested range. - By default, this value is 0.02 (2%) + By default, this value is set between 0.02 and 0.1 depending on + the size of the ViewBox. ============= ===================================================================== """ @@ -367,6 +368,10 @@ class ViewBox(GraphicsWidget): changed = [False, False] for ax, range in changes.items(): + if padding is None: + xpad = self.suggestPadding(ax) + else: + xpad = padding mn = min(range) mx = max(range) if mn == mx: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. @@ -375,11 +380,11 @@ class ViewBox(GraphicsWidget): dy = 1 mn -= dy*0.5 mx += dy*0.5 - padding = 0.0 + xpad = 0.0 if any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])): raise Exception("Not setting range [%s, %s]" % (str(mn), str(mx))) - p = (mx-mn) * padding + p = (mx-mn) * xpad mn -= p mx += p @@ -412,34 +417,53 @@ class ViewBox(GraphicsWidget): elif changed[1] and self.state['autoVisibleOnly'][0]: self.updateAutoRange() - def setYRange(self, min, max, padding=0.02, update=True): + def setYRange(self, min, max, padding=None, update=True): """ Set the visible Y range of the view to [*min*, *max*]. The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) """ self.setRange(yRange=[min, max], update=update, padding=padding) - def setXRange(self, min, max, padding=0.02, update=True): + def setXRange(self, min, max, padding=None, update=True): """ Set the visible X range of the view to [*min*, *max*]. The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) """ self.setRange(xRange=[min, max], update=update, padding=padding) - def autoRange(self, padding=0.02, item=None): + def autoRange(self, padding=None, items=None, item=None): """ Set the range of the view box to make all children visible. Note that this is not the same as enableAutoRange, which causes the view to automatically auto-range whenever its contents are changed. + + =========== ============================================================ + Arguments + padding The fraction of the total data range to add on to the final + visible range. By default, this value is set between 0.02 + and 0.1 depending on the size of the ViewBox. + items If specified, this is a list of items to consider when + determining the visible range. + =========== ============================================================ """ if item is None: - bounds = self.childrenBoundingRect() + bounds = self.childrenBoundingRect(items=items) else: + print "Warning: ViewBox.autoRange(item=__) is deprecated. Use 'items' argument instead." bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() if bounds is not None: self.setRange(bounds, padding=padding) + def suggestPadding(self, axis): + l = self.width() if axis==0 else self.height() + if l > 0: + padding = np.clip(1./(l**0.5), 0.02, 0.1) + else: + padding = 0.02 + return padding def scaleBy(self, s, center=None): """ @@ -577,12 +601,10 @@ class ViewBox(GraphicsWidget): w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. childRange[ax] = [x-w2, x+w2] else: - l = self.width() if ax==0 else self.height() - if l > 0: - padding = np.clip(1./(l**0.5), 0.02, 0.1) - wp = (xr[1] - xr[0]) * padding - childRange[ax][0] -= wp - childRange[ax][1] += wp + padding = self.suggestPadding(ax) + wp = (xr[1] - xr[0]) * padding + childRange[ax][0] -= wp + childRange[ax][1] += wp targetRect[ax] = childRange[ax] args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] if len(args) == 0: @@ -995,13 +1017,14 @@ class ViewBox(GraphicsWidget): - def childrenBounds(self, frac=None, orthoRange=(None,None)): + def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): """Return the bounding range of all children. [[xmin, xmax], [ymin, ymax]] Values may be None if there are no specific bounds for an axis. """ prof = debug.Profiler('updateAutoRange', disabled=True) - items = self.addedItems + if items is None: + items = self.addedItems ## measure pixel dimensions in view box px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()]