diff --git a/GraphicsScene/exportDialog.py b/GraphicsScene/exportDialog.py index 73a8c83f9b..436d5e4229 100644 --- a/GraphicsScene/exportDialog.py +++ b/GraphicsScene/exportDialog.py @@ -34,8 +34,12 @@ def __init__(self, scene): 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 18531c1466..0bf13ca87c 100644 --- a/PlotData.py +++ b/PlotData.py @@ -22,7 +22,7 @@ def __init__(self): 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 db7dc732ae..f709c78660 100644 --- a/configfile.py +++ b/configfile.py @@ -10,7 +10,7 @@ """ 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 @@ def measureIndent(s): 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 35781535ec..19ebc76ebc 100644 --- a/dockarea/Dock.py +++ b/dockarea/Dock.py @@ -212,6 +212,19 @@ def containerChanged(self, c): 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 78d512f343..752cf3b6f2 100644 --- a/dockarea/DockArea.py +++ b/dockarea/DockArea.py @@ -33,12 +33,13 @@ def __init__(self, temporary=False, home=None): 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 @@ def addDock(self, dock, position='bottom', relativeTo=None): 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 @@ def addDock(self, dock, position='bottom', relativeTo=None): 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 @@ def apoptose(self): 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 629b278937..0439fc3593 100644 --- a/exporters/CSVExporter.py +++ b/exporters/CSVExporter.py @@ -14,6 +14,7 @@ def __init__(self, item): 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 @@ def export(self, fileName=None): 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 12d6a97c69..a68cf542a7 100644 --- a/flowchart/Flowchart.py +++ b/flowchart/Flowchart.py @@ -206,17 +206,12 @@ def addNode(self, node, name, pos=None): 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 @@ def removeNode(self, 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 e4530800c6..31b1359c46 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 2e9ea312ae..c07dd7347c 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 @@ def retranslateUi(self, Form): 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 d49d308306..c73f3c003c 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 @@ def retranslateUi(self, Form): 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 1c612e0836..cbef848ace 100644 --- a/flowchart/library/Data.py +++ b/flowchart/library/Data.py @@ -152,7 +152,7 @@ def process(self, data=None, display=True): #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 @@ def focusOutEvent(self, ev): 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 7979d7a78a..9068c0ec8c 100644 --- a/flowchart/library/Display.py +++ b/flowchart/library/Display.py @@ -21,7 +21,7 @@ def __init__(self, name): 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 0c6c07184d..dcede02a0b 100644 --- a/graphicsItems/ArrowItem.py +++ b/graphicsItems/ArrowItem.py @@ -84,8 +84,41 @@ def setStyle(self, **opts): 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 5439c731ae..955106d856 100644 --- a/graphicsItems/GradientEditorItem.py +++ b/graphicsItems/GradientEditorItem.py @@ -782,7 +782,8 @@ def setColorMap(self, cm): 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 @@ def __init__(self, view, pos, color, movable=True, scale=10, pen='w'): 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 18d9ebf316..a69131ef25 100644 --- a/graphicsItems/ScatterPlotItem.py +++ b/graphicsItems/ScatterPlotItem.py @@ -495,8 +495,8 @@ def setSize(self, size, update=True, dataSet=None, mask=None): 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 @@ def setSize(self, size, update=True, dataSet=None, mask=None): 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 ce1d61f933..87b687bd34 100644 --- a/graphicsItems/ViewBox/ViewBox.py +++ b/graphicsItems/ViewBox/ViewBox.py @@ -336,7 +336,7 @@ def targetRect(self): 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 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=Tru *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 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=Tru 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 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=Tru 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 @@ def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=Tru 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 @@ def updateAutoRange(self): 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 @@ def allChildren(self, item=None): - 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 5c6573e382..f0c13a6098 100644 --- a/imageview/ImageView.py +++ b/imageview/ImageView.py @@ -205,7 +205,12 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, *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 3e5938d1b3..170074b9c8 100644 --- a/opengl/MeshData.py +++ b/opengl/MeshData.py @@ -436,7 +436,7 @@ def save(self): 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 1e4cbf9c27..0000000000 --- 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)