Commit ef0ee7c6 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Merge tag 'pyqtgraph-0.9.7' into pyqtgraph-core

parents 9126b27c 03683a5e
...@@ -34,8 +34,12 @@ class ExportDialog(QtGui.QWidget): ...@@ -34,8 +34,12 @@ class ExportDialog(QtGui.QWidget):
def show(self, item=None): def show(self, item=None):
if item is not 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: while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None:
item = item.parentItem() 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.updateItemList(select=item)
self.setVisible(True) self.setVisible(True)
self.activateWindow() self.activateWindow()
......
...@@ -22,7 +22,7 @@ class PlotData(object): ...@@ -22,7 +22,7 @@ class PlotData(object):
self.maxVals = {} ## cache for max/min self.maxVals = {} ## cache for max/min
self.minVals = {} self.minVals = {}
def addFields(self, fields): def addFields(self, **fields):
for f in fields: for f in fields:
if f not in self.fields: if f not in self.fields:
self.fields[f] = None self.fields[f] = None
......
...@@ -10,7 +10,7 @@ as it can be converted to/from a string using repr and eval. ...@@ -10,7 +10,7 @@ as it can be converted to/from a string using repr and eval.
""" """
import re, os, sys import re, os, sys
from pgcollections import OrderedDict from .pgcollections import OrderedDict
GLOBAL_PATH = None # so not thread safe. GLOBAL_PATH = None # so not thread safe.
from . import units from . import units
from .python2_3 import asUnicode from .python2_3 import asUnicode
...@@ -199,4 +199,4 @@ key2: ##comment ...@@ -199,4 +199,4 @@ key2: ##comment
print("============") print("============")
data = readConfigFile(fn) data = readConfigFile(fn)
print(data) print(data)
os.remove(fn) os.remove(fn)
\ No newline at end of file
...@@ -212,6 +212,19 @@ class Dock(QtGui.QWidget, DockDrop): ...@@ -212,6 +212,19 @@ class Dock(QtGui.QWidget, DockDrop):
def __repr__(self): def __repr__(self):
return "<Dock %s %s>" % (self.name(), self.stretch()) 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): class DockLabel(VerticalLabel):
......
...@@ -33,12 +33,13 @@ class DockArea(Container, QtGui.QWidget, DockDrop): ...@@ -33,12 +33,13 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
def type(self): def type(self):
return "top" 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. """Adds a dock to this area.
=========== ================================================================= =========== =================================================================
Arguments: 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' position 'bottom', 'top', 'left', 'right', 'over', or 'under'
relativeTo If relativeTo is None, then the new Dock is added to fill an 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 entire edge of the window. If relativeTo is another Dock, then
...@@ -46,7 +47,12 @@ class DockArea(Container, QtGui.QWidget, DockDrop): ...@@ -46,7 +47,12 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
configuration for 'over' and 'under'). 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. ## Determine the container to insert this dock into.
## If there is no neighbor, then the container is the top. ## If there is no neighbor, then the container is the top.
...@@ -100,6 +106,8 @@ class DockArea(Container, QtGui.QWidget, DockDrop): ...@@ -100,6 +106,8 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
dock.area = self dock.area = self
self.docks[dock.name()] = dock self.docks[dock.name()] = dock
return dock
def moveDock(self, dock, position, neighbor): def moveDock(self, dock, position, neighbor):
""" """
Move an existing Dock to a new location. Move an existing Dock to a new location.
...@@ -293,5 +301,19 @@ class DockArea(Container, QtGui.QWidget, DockDrop): ...@@ -293,5 +301,19 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
self.home.removeTempArea(self) self.home.removeTempArea(self)
#self.close() #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
...@@ -14,6 +14,7 @@ class CSVExporter(Exporter): ...@@ -14,6 +14,7 @@ class CSVExporter(Exporter):
Exporter.__init__(self, item) Exporter.__init__(self, item)
self.params = Parameter(name='params', type='group', children=[ self.params = Parameter(name='params', type='group', children=[
{'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']},
{'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]},
]) ])
def parameters(self): def parameters(self):
...@@ -42,18 +43,15 @@ class CSVExporter(Exporter): ...@@ -42,18 +43,15 @@ class CSVExporter(Exporter):
fd.write(sep.join(header) + '\n') fd.write(sep.join(header) + '\n')
i = 0 i = 0
while True: numFormat = '%%0.%dg' % self.params['precision']
done = True numRows = reduce(max, [len(d[0]) for d in data])
for i in range(numRows):
for d in data: for d in data:
if i < len(d[0]): if i < len(d[0]):
fd.write('%g%s%g%s'%(d[0][i], sep, d[1][i], sep)) fd.write(numFormat % d[0][i] + sep + numFormat % d[1][i] + sep)
done = False
else: else:
fd.write(' %s %s' % (sep, sep)) fd.write(' %s %s' % (sep, sep))
fd.write('\n') fd.write('\n')
if done:
break
i += 1
fd.close() fd.close()
......
...@@ -206,17 +206,12 @@ class Flowchart(Node): ...@@ -206,17 +206,12 @@ class Flowchart(Node):
item = node.graphicsItem() item = node.graphicsItem()
item.setZValue(self.nextZVal*2) item.setZValue(self.nextZVal*2)
self.nextZVal += 1 self.nextZVal += 1
#item.setParentItem(self.chartGraphicsItem())
self.viewBox.addItem(item) self.viewBox.addItem(item)
#item.setPos(pos2.x(), pos2.y())
item.moveBy(*pos) item.moveBy(*pos)
self._nodes[name] = node self._nodes[name] = node
self.widget().addNode(node) self.widget().addNode(node)
#QtCore.QObject.connect(node, QtCore.SIGNAL('closed'), self.nodeClosed)
node.sigClosed.connect(self.nodeClosed) node.sigClosed.connect(self.nodeClosed)
#QtCore.QObject.connect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed)
node.sigRenamed.connect(self.nodeRenamed) node.sigRenamed.connect(self.nodeRenamed)
#QtCore.QObject.connect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged)
node.sigOutputChanged.connect(self.nodeOutputChanged) node.sigOutputChanged.connect(self.nodeOutputChanged)
def removeNode(self, node): def removeNode(self, node):
...@@ -225,17 +220,14 @@ class Flowchart(Node): ...@@ -225,17 +220,14 @@ class Flowchart(Node):
def nodeClosed(self, node): def nodeClosed(self, node):
del self._nodes[node.name()] del self._nodes[node.name()]
self.widget().removeNode(node) self.widget().removeNode(node)
#QtCore.QObject.disconnect(node, QtCore.SIGNAL('closed'), self.nodeClosed)
try: try:
node.sigClosed.disconnect(self.nodeClosed) node.sigClosed.disconnect(self.nodeClosed)
except TypeError: except TypeError:
pass pass
#QtCore.QObject.disconnect(node, QtCore.SIGNAL('renamed'), self.nodeRenamed)
try: try:
node.sigRenamed.disconnect(self.nodeRenamed) node.sigRenamed.disconnect(self.nodeRenamed)
except TypeError: except TypeError:
pass pass
#QtCore.QObject.disconnect(node, QtCore.SIGNAL('outputChanged'), self.nodeOutputChanged)
try: try:
node.sigOutputChanged.disconnect(self.nodeOutputChanged) node.sigOutputChanged.disconnect(self.nodeOutputChanged)
except TypeError: except TypeError:
......
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
<customwidget> <customwidget>
<class>FlowchartGraphicsView</class> <class>FlowchartGraphicsView</class>
<extends>QGraphicsView</extends> <extends>QGraphicsView</extends>
<header>FlowchartGraphicsView</header> <header>pyqtgraph.flowchart.FlowchartGraphicsView</header>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' # Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui'
# #
# Created: Sun Sep 9 14:41:29 2012 # Created: Sun Feb 24 19:47:29 2013
# by: PyQt4 UI code generator 4.9.1 # by: PyQt4 UI code generator 4.9.3
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
...@@ -56,4 +56,4 @@ class Ui_Form(object): ...@@ -56,4 +56,4 @@ class Ui_Form(object):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget
from FlowchartGraphicsView import FlowchartGraphicsView from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' # Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui'
# #
# Created: Sun Sep 9 14:41:30 2012 # Created: Sun Feb 24 19:47:30 2013
# by: pyside-uic 0.2.13 running on PySide 1.1.0 # by: pyside-uic 0.2.13 running on PySide 1.1.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
...@@ -51,4 +51,4 @@ class Ui_Form(object): ...@@ -51,4 +51,4 @@ class Ui_Form(object):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget
from FlowchartGraphicsView import FlowchartGraphicsView from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView
...@@ -152,7 +152,7 @@ class RegionSelectNode(CtrlNode): ...@@ -152,7 +152,7 @@ class RegionSelectNode(CtrlNode):
#print " new rgn:", c, region #print " new rgn:", c, region
#self.items[c].setYRange([0., 0.2], relative=True) #self.items[c].setYRange([0., 0.2], relative=True)
if self.selected.isConnected(): if self['selected'].isConnected():
if data is None: if data is None:
sliced = None sliced = None
elif (hasattr(data, 'implements') and data.implements('MetaArray')): elif (hasattr(data, 'implements') and data.implements('MetaArray')):
...@@ -219,7 +219,6 @@ class EvalNode(Node): ...@@ -219,7 +219,6 @@ class EvalNode(Node):
text = str(self.text.toPlainText()) text = str(self.text.toPlainText())
if text != self.lastText: if text != self.lastText:
self.lastText = text self.lastText = text
print("eval node update")
self.update() self.update()
return QtGui.QTextEdit.focusOutEvent(self.text, ev) return QtGui.QTextEdit.focusOutEvent(self.text, ev)
......
...@@ -21,7 +21,7 @@ class PlotWidgetNode(Node): ...@@ -21,7 +21,7 @@ class PlotWidgetNode(Node):
self.items = {} self.items = {}
def disconnected(self, localTerm, remoteTerm): 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]) self.plot.removeItem(self.items[remoteTerm])
del self.items[remoteTerm] del self.items[remoteTerm]
......
...@@ -84,8 +84,41 @@ class ArrowItem(QtGui.QGraphicsPathItem): ...@@ -84,8 +84,41 @@ class ArrowItem(QtGui.QGraphicsPathItem):
def paint(self, p, *args): def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing) p.setRenderHint(QtGui.QPainter.Antialiasing)
QtGui.QGraphicsPathItem.paint(self, p, *args) QtGui.QGraphicsPathItem.paint(self, p, *args)
#p.setPen(fn.mkPen('r'))
#p.setBrush(fn.mkBrush(None))
#p.drawRect(self.boundingRect())
def shape(self): def shape(self):
#if not self.opts['pxMode']: #if not self.opts['pxMode']:
#return QtGui.QGraphicsPathItem.shape(self) #return QtGui.QGraphicsPathItem.shape(self)
return self.path return self.path
\ No newline at end of file
## 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
...@@ -782,7 +782,8 @@ class GradientEditorItem(TickSliderItem): ...@@ -782,7 +782,8 @@ class GradientEditorItem(TickSliderItem):
self.sigGradientChangeFinished.emit(self) 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 ## private class
sigMoving = QtCore.Signal(object) sigMoving = QtCore.Signal(object)
...@@ -802,7 +803,7 @@ class Tick(GraphicsObject): ...@@ -802,7 +803,7 @@ class Tick(GraphicsObject):
self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale))
self.pg.closeSubpath() self.pg.closeSubpath()
GraphicsObject.__init__(self) QtGui.QGraphicsObject.__init__(self)
self.setPos(pos[0], pos[1]) self.setPos(pos[0], pos[1])
if self.movable: if self.movable:
self.setZValue(1) self.setZValue(1)
......
...@@ -495,8 +495,8 @@ class ScatterPlotItem(GraphicsObject): ...@@ -495,8 +495,8 @@ class ScatterPlotItem(GraphicsObject):
if isinstance(size, np.ndarray) or isinstance(size, list): if isinstance(size, np.ndarray) or isinstance(size, list):
sizes = size sizes = size
if kargs['mask'] is not None: if mask is not None:
sizes = sizes[kargs['mask']] sizes = sizes[mask]
if len(sizes) != len(dataSet): if len(sizes) != len(dataSet):
raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet)))
dataSet['size'] = sizes dataSet['size'] = sizes
...@@ -508,13 +508,13 @@ class ScatterPlotItem(GraphicsObject): ...@@ -508,13 +508,13 @@ class ScatterPlotItem(GraphicsObject):
if update: if update:
self.updateSpots(dataSet) self.updateSpots(dataSet)
def setPointData(self, data, dataSet=None): def setPointData(self, data, dataSet=None, mask=None):
if dataSet is None: if dataSet is None:
dataSet = self.data dataSet = self.data
if isinstance(data, np.ndarray) or isinstance(data, list): if isinstance(data, np.ndarray) or isinstance(data, list):
if kargs['mask'] is not None: if mask is not None:
data = data[kargs['mask']] data = data[mask]
if len(data) != len(dataSet): if len(data) != len(dataSet):
raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet)))
......
...@@ -336,7 +336,7 @@ class ViewBox(GraphicsWidget): ...@@ -336,7 +336,7 @@ class ViewBox(GraphicsWidget):
print("make qrectf failed:", self.state['targetRange']) print("make qrectf failed:", self.state['targetRange'])
raise 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. Set the visible range of the ViewBox.
Must specify at least one of *range*, *xRange*, or *yRange*. Must specify at least one of *range*, *xRange*, or *yRange*.
...@@ -347,7 +347,8 @@ class ViewBox(GraphicsWidget): ...@@ -347,7 +347,8 @@ class ViewBox(GraphicsWidget):
*xRange* (min,max) The range that should be visible along the x-axis. *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. *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. *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): ...@@ -367,6 +368,10 @@ class ViewBox(GraphicsWidget):
changed = [False, False] changed = [False, False]
for ax, range in changes.items(): for ax, range in changes.items():
if padding is None:
xpad = self.suggestPadding(ax)
else:
xpad = padding
mn = min(range) mn = min(range)
mx = max(range) mx = max(range)
if mn == mx: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. 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): ...@@ -375,11 +380,11 @@ class ViewBox(GraphicsWidget):
dy = 1 dy = 1
mn -= dy*0.5 mn -= dy*0.5
mx += 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])): if any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
raise Exception("Not setting range [%s, %s]" % (str(mn), str(mx))) raise Exception("Not setting range [%s, %s]" % (str(mn), str(mx)))
p = (mx-mn) * padding p = (mx-mn) * xpad
mn -= p mn -= p
mx += p mx += p
...@@ -412,34 +417,53 @@ class ViewBox(GraphicsWidget): ...@@ -412,34 +417,53 @@ class ViewBox(GraphicsWidget):
elif changed[1] and self.state['autoVisibleOnly'][0]: elif changed[1] and self.state['autoVisibleOnly'][0]:
self.updateAutoRange() 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*]. 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. 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) 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*]. 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. 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) 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. 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 Note that this is not the same as enableAutoRange, which causes the view to
automatically auto-range whenever its contents are changed. 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