Merge tag 'pyqtgraph-0.9.7' into pyqtgraph-core

This commit is contained in:
Luke Campagnola 2013-12-23 11:15:17 -05:00
commit ef0ee7c60b
19 changed files with 146 additions and 79 deletions

View File

@ -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()

View File

@ -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

View File

@ -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)
os.remove(fn)

View File

@ -212,6 +212,19 @@ class Dock(QtGui.QWidget, DockDrop):
def __repr__(self):
return "<Dock %s %s>" % (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):

View File

@ -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)

View File

@ -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()

View File

@ -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:

View File

@ -90,7 +90,7 @@
<customwidget>
<class>FlowchartGraphicsView</class>
<extends>QGraphicsView</extends>
<header>FlowchartGraphicsView</header>
<header>pyqtgraph.flowchart.FlowchartGraphicsView</header>
</customwidget>
</customwidgets>
<resources/>

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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]

View File

@ -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
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

View File

@ -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)

View File

@ -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)))

View File

@ -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()]

View File

@ -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)

View File

@ -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):

View File

@ -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)