diff --git a/GraphicsScene/exportDialog.py b/GraphicsScene/exportDialog.py index 73a8c83f..436d5e42 100644 --- a/GraphicsScene/exportDialog.py +++ b/GraphicsScene/exportDialog.py @@ -34,8 +34,12 @@ class ExportDialog(QtGui.QWidget): def show(self, item=None): if item is not None: + ## Select next exportable parent of the item originally clicked on while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None: item = item.parentItem() + ## if this is a ViewBox inside a PlotItem, select the parent instead. + if isinstance(item, pg.ViewBox) and isinstance(item.parentItem(), pg.PlotItem): + item = item.parentItem() self.updateItemList(select=item) self.setVisible(True) self.activateWindow() diff --git a/PlotData.py b/PlotData.py index 18531c14..0bf13ca8 100644 --- a/PlotData.py +++ b/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/configfile.py b/configfile.py index db7dc732..f709c786 100644 --- a/configfile.py +++ b/configfile.py @@ -10,7 +10,7 @@ as it can be converted to/from a string using repr and eval. """ import re, os, sys -from pgcollections import OrderedDict +from .pgcollections import OrderedDict GLOBAL_PATH = None # so not thread safe. from . import units from .python2_3 import asUnicode @@ -199,4 +199,4 @@ key2: ##comment print("============") data = readConfigFile(fn) print(data) - os.remove(fn) \ No newline at end of file + os.remove(fn) diff --git a/dockarea/Dock.py b/dockarea/Dock.py index 35781535..19ebc76e 100644 --- a/dockarea/Dock.py +++ b/dockarea/Dock.py @@ -212,6 +212,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/dockarea/DockArea.py b/dockarea/DockArea.py index 78d512f3..752cf3b6 100644 --- a/dockarea/DockArea.py +++ b/dockarea/DockArea.py @@ -33,12 +33,13 @@ class DockArea(Container, QtGui.QWidget, DockDrop): def type(self): return "top" - def addDock(self, dock, position='bottom', relativeTo=None): + def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): """Adds a dock to this area. =========== ================================================================= Arguments: - dock The new Dock object to add. + dock The new Dock object to add. If None, then a new Dock will be + created. position 'bottom', 'top', 'left', 'right', 'over', or 'under' relativeTo If relativeTo is None, then the new Dock is added to fill an entire edge of the window. If relativeTo is another Dock, then @@ -46,7 +47,12 @@ class DockArea(Container, QtGui.QWidget, DockDrop): configuration for 'over' and 'under'). =========== ================================================================= + All extra keyword arguments are passed to Dock.__init__() if *dock* is + None. """ + if dock is None: + dock = Dock(**kwds) + ## Determine the container to insert this dock into. ## If there is no neighbor, then the container is the top. @@ -100,6 +106,8 @@ class DockArea(Container, QtGui.QWidget, DockDrop): dock.area = self self.docks[dock.name()] = dock + return dock + def moveDock(self, dock, position, neighbor): """ Move an existing Dock to a new location. @@ -293,5 +301,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/exporters/CSVExporter.py b/exporters/CSVExporter.py index 629b2789..0439fc35 100644 --- a/exporters/CSVExporter.py +++ b/exporters/CSVExporter.py @@ -14,6 +14,7 @@ class CSVExporter(Exporter): Exporter.__init__(self, item) self.params = Parameter(name='params', type='group', children=[ {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, + {'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]}, ]) def parameters(self): @@ -42,18 +43,15 @@ class CSVExporter(Exporter): fd.write(sep.join(header) + '\n') i = 0 - while True: - done = True + numFormat = '%%0.%dg' % self.params['precision'] + numRows = reduce(max, [len(d[0]) for d in data]) + for i in range(numRows): for d in data: if i < len(d[0]): - fd.write('%g%s%g%s'%(d[0][i], sep, d[1][i], sep)) - done = False + fd.write(numFormat % d[0][i] + sep + numFormat % d[1][i] + sep) else: fd.write(' %s %s' % (sep, sep)) fd.write('\n') - if done: - break - i += 1 fd.close() diff --git a/flowchart/Flowchart.py b/flowchart/Flowchart.py index 12d6a97c..a68cf542 100644 --- a/flowchart/Flowchart.py +++ b/flowchart/Flowchart.py @@ -206,17 +206,12 @@ class Flowchart(Node): item = node.graphicsItem() item.setZValue(self.nextZVal*2) self.nextZVal += 1 - #item.setParentItem(self.chartGraphicsItem()) self.viewBox.addItem(item) - #item.setPos(pos2.x(), pos2.y()) item.moveBy(*pos) self._nodes[name] = node self.widget().addNode(node) - #QtCore.QObject.connect(node, QtCore.SIGNAL('closed'), self.nodeClosed) node.sigClosed.connect(self.nodeClosed) - #QtCore.QObject.connect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed) node.sigRenamed.connect(self.nodeRenamed) - #QtCore.QObject.connect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged) node.sigOutputChanged.connect(self.nodeOutputChanged) def removeNode(self, node): @@ -225,17 +220,14 @@ class Flowchart(Node): def nodeClosed(self, node): del self._nodes[node.name()] self.widget().removeNode(node) - #QtCore.QObject.disconnect(node, QtCore.SIGNAL('closed'), self.nodeClosed) try: node.sigClosed.disconnect(self.nodeClosed) except TypeError: pass - #QtCore.QObject.disconnect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed) try: node.sigRenamed.disconnect(self.nodeRenamed) except TypeError: pass - #QtCore.QObject.disconnect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged) try: node.sigOutputChanged.disconnect(self.nodeOutputChanged) except TypeError: diff --git a/flowchart/FlowchartTemplate.ui b/flowchart/FlowchartTemplate.ui index e4530800..31b1359c 100644 --- a/flowchart/FlowchartTemplate.ui +++ b/flowchart/FlowchartTemplate.ui @@ -90,7 +90,7 @@ FlowchartGraphicsView QGraphicsView -
FlowchartGraphicsView
+
pyqtgraph.flowchart.FlowchartGraphicsView
diff --git a/flowchart/FlowchartTemplate_pyqt.py b/flowchart/FlowchartTemplate_pyqt.py index 2e9ea312..c07dd734 100644 --- a/flowchart/FlowchartTemplate_pyqt.py +++ b/flowchart/FlowchartTemplate_pyqt.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' # -# Created: Sun Sep 9 14:41:29 2012 -# by: PyQt4 UI code generator 4.9.1 +# Created: Sun Feb 24 19:47:29 2013 +# by: PyQt4 UI code generator 4.9.3 # # WARNING! All changes made in this file will be lost! @@ -56,4 +56,4 @@ class Ui_Form(object): Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget -from FlowchartGraphicsView import FlowchartGraphicsView +from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/flowchart/FlowchartTemplate_pyside.py b/flowchart/FlowchartTemplate_pyside.py index d49d3083..c73f3c00 100644 --- a/flowchart/FlowchartTemplate_pyside.py +++ b/flowchart/FlowchartTemplate_pyside.py @@ -2,8 +2,8 @@ # Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' # -# Created: Sun Sep 9 14:41:30 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# Created: Sun Feb 24 19:47:30 2013 +# by: pyside-uic 0.2.13 running on PySide 1.1.1 # # WARNING! All changes made in this file will be lost! @@ -51,4 +51,4 @@ class Ui_Form(object): Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget -from FlowchartGraphicsView import FlowchartGraphicsView +from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/flowchart/library/Data.py b/flowchart/library/Data.py index 1c612e08..cbef848a 100644 --- a/flowchart/library/Data.py +++ b/flowchart/library/Data.py @@ -152,7 +152,7 @@ class RegionSelectNode(CtrlNode): #print " new rgn:", c, region #self.items[c].setYRange([0., 0.2], relative=True) - if self.selected.isConnected(): + if self['selected'].isConnected(): if data is None: sliced = None elif (hasattr(data, 'implements') and data.implements('MetaArray')): @@ -219,7 +219,6 @@ class EvalNode(Node): text = str(self.text.toPlainText()) if text != self.lastText: self.lastText = text - print("eval node update") self.update() return QtGui.QTextEdit.focusOutEvent(self.text, ev) diff --git a/flowchart/library/Display.py b/flowchart/library/Display.py index 7979d7a7..9068c0ec 100644 --- a/flowchart/library/Display.py +++ b/flowchart/library/Display.py @@ -21,7 +21,7 @@ class PlotWidgetNode(Node): self.items = {} def disconnected(self, localTerm, remoteTerm): - if localTerm is self.In and remoteTerm in self.items: + if localTerm is self['In'] and remoteTerm in self.items: self.plot.removeItem(self.items[remoteTerm]) del self.items[remoteTerm] diff --git a/graphicsItems/ArrowItem.py b/graphicsItems/ArrowItem.py index 0c6c0718..dcede02a 100644 --- a/graphicsItems/ArrowItem.py +++ b/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 diff --git a/graphicsItems/GradientEditorItem.py b/graphicsItems/GradientEditorItem.py index 5439c731..955106d8 100644 --- a/graphicsItems/GradientEditorItem.py +++ b/graphicsItems/GradientEditorItem.py @@ -782,7 +782,8 @@ class GradientEditorItem(TickSliderItem): self.sigGradientChangeFinished.emit(self) -class Tick(GraphicsObject): +class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsObject instead results in + ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 ## private class sigMoving = QtCore.Signal(object) @@ -802,7 +803,7 @@ class Tick(GraphicsObject): self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) self.pg.closeSubpath() - GraphicsObject.__init__(self) + QtGui.QGraphicsObject.__init__(self) self.setPos(pos[0], pos[1]) if self.movable: self.setZValue(1) diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py index 18d9ebf3..a69131ef 100644 --- a/graphicsItems/ScatterPlotItem.py +++ b/graphicsItems/ScatterPlotItem.py @@ -495,8 +495,8 @@ class ScatterPlotItem(GraphicsObject): if isinstance(size, np.ndarray) or isinstance(size, list): sizes = size - if kargs['mask'] is not None: - sizes = sizes[kargs['mask']] + if mask is not None: + sizes = sizes[mask] if len(sizes) != len(dataSet): raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) dataSet['size'] = sizes @@ -508,13 +508,13 @@ class ScatterPlotItem(GraphicsObject): if update: self.updateSpots(dataSet) - def setPointData(self, data, dataSet=None): + def setPointData(self, data, dataSet=None, mask=None): if dataSet is None: dataSet = self.data if isinstance(data, np.ndarray) or isinstance(data, list): - if kargs['mask'] is not None: - data = data[kargs['mask']] + if mask is not None: + data = data[mask] if len(data) != len(dataSet): raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py index ce1d61f9..87b687bd 100644 --- a/graphicsItems/ViewBox/ViewBox.py +++ b/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()] diff --git a/imageview/ImageView.py b/imageview/ImageView.py index 5c6573e3..f0c13a60 100644 --- a/imageview/ImageView.py +++ b/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) diff --git a/opengl/MeshData.py b/opengl/MeshData.py index 3e5938d1..170074b9 100644 --- a/opengl/MeshData.py +++ b/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): diff --git a/rebuildUi.py b/rebuildUi.py deleted file mode 100644 index 1e4cbf9c..00000000 --- a/rebuildUi.py +++ /dev/null @@ -1,23 +0,0 @@ -import os, sys -## Search the package tree for all .ui files, compile each to -## a .py for pyqt and pyside - -pyqtuic = 'pyuic4' -pysideuic = 'pyside-uic' - -for path, sd, files in os.walk('.'): - for f in files: - base, ext = os.path.splitext(f) - if ext != '.ui': - continue - ui = os.path.join(path, f) - - py = os.path.join(path, base + '_pyqt.py') - if not os.path.exists(py) or os.stat(ui).st_mtime > os.stat(py).st_mtime: - os.system('%s %s > %s' % (pyqtuic, ui, py)) - print(py) - - py = os.path.join(path, base + '_pyside.py') - if not os.path.exists(py) or os.stat(ui).st_mtime > os.stat(py).st_mtime: - os.system('%s %s > %s' % (pysideuic, ui, py)) - print(py)