From 662b319d7b707597b89e11b1cfee99e8b062f649 Mon Sep 17 00:00:00 2001
From: Luke Campagnola <>
Date: Mon, 9 Jul 2012 08:38:30 -0400
Subject: [PATCH] - PlotItem can now be constructed with customized ViewBox and
AxisItems - Text spacing fix for AxisItem
---
GraphicsScene/GraphicsScene.py | 7 +-
__init__.py | 12 +-
examples/__main__.py | 3 +-
examples/customPlot.py | 52 +++
graphicsItems/AxisItem.py | 28 +-
graphicsItems/PlotItem/PlotItem.py | 583 ++++-------------------------
graphicsItems/ViewBox/ViewBox.py | 11 +-
widgets/PlotWidget.py | 2 +
8 files changed, 168 insertions(+), 530 deletions(-)
create mode 100644 examples/customPlot.py
diff --git a/GraphicsScene/GraphicsScene.py b/GraphicsScene/GraphicsScene.py
index 73867059..d4dd8534 100644
--- a/GraphicsScene/GraphicsScene.py
+++ b/GraphicsScene/GraphicsScene.py
@@ -505,18 +505,19 @@ class GraphicsScene(QtGui.QGraphicsScene):
menusToAdd = []
while item is not self:
item = item.parentItem()
-
+
if item is None:
item = self
if not hasattr(item, "getContextMenus"):
continue
-
subMenus = item.getContextMenus(event)
+ if subMenus is None:
+ continue
if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
subMenus = [subMenus]
-
+
for sm in subMenus:
menusToAdd.append(sm)
diff --git a/__init__.py b/__init__.py
index 756e46e8..b31f15d0 100644
--- a/__init__.py
+++ b/__init__.py
@@ -49,10 +49,10 @@ def getConfigOption(opt):
def systemInfo():
- print "sys.platform:", sys.platform
- print "sys.version:", sys.version
+ print("sys.platform: %s" % sys.platform)
+ print("sys.version: %s" % sys.version)
from .Qt import VERSION_INFO
- print "qt bindings:", VERSION_INFO
+ print("qt bindings: %s" % VERSION_INFO)
global REVISION
if REVISION is None: ## this code was probably checked out from bzr; look up the last-revision file
@@ -60,8 +60,8 @@ def systemInfo():
if os.path.exists(lastRevFile):
REVISION = open(lastRevFile, 'r').read().strip()
- print "pyqtgraph:", REVISION
- print "config:"
+ print("pyqtgraph: %s" % REVISION)
+ print("config:")
import pprint
pprint.pprint(CONFIG_OPTIONS)
@@ -126,7 +126,7 @@ def importAll(path, excludes=()):
globals()[k] = getattr(mod, k)
importAll('graphicsItems')
-importAll('widgets', excludes=['MatplotlibWidget'])
+importAll('widgets', excludes=['MatplotlibWidget', 'RemoteGraphicsView'])
from .imageview import *
from .WidgetGroup import *
diff --git a/examples/__main__.py b/examples/__main__.py
index 0881fcea..39c51b7f 100644
--- a/examples/__main__.py
+++ b/examples/__main__.py
@@ -16,6 +16,7 @@ examples = OrderedDict([
('Video speed test', 'VideoSpeedTest.py'),
('Plot speed test', 'PlotSpeedTest.py'),
('Data Slicing', 'DataSlicing.py'),
+ ('Plot Customization', 'customPlot.py'),
('GraphicsItems', OrderedDict([
('Scatter Plot', 'ScatterPlot.py'),
#('PlotItem', 'PlotItem.py'),
@@ -45,7 +46,7 @@ examples = OrderedDict([
#('VerticalLabel', '../widgets/VerticalLabel.py'),
('JoystickButton', 'JoystickButton.py'),
])),
-
+
('GraphicsScene', 'GraphicsScene.py'),
('Flowcharts', 'Flowchart.py'),
#('Canvas', '../canvas'),
diff --git a/examples/customPlot.py b/examples/customPlot.py
new file mode 100644
index 00000000..86ed4829
--- /dev/null
+++ b/examples/customPlot.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+##
+## This example demonstrates the creation of a plot with a customized
+## AxisItem and ViewBox.
+##
+
+
+import initExample ## Add path to library (just for examples; you do not need this)
+
+import pyqtgraph as pg
+from pyqtgraph.Qt import QtCore, QtGui
+import numpy as np
+import time
+
+class DateAxis(pg.AxisItem):
+ def tickStrings(self, values, scale, spacing):
+ return [time.strftime('%b %Y', time.localtime(x)) for x in values]
+
+class CustomViewBox(pg.ViewBox):
+ def __init__(self, *args, **kwds):
+ pg.ViewBox.__init__(self, *args, **kwds)
+ self.setMouseMode(self.RectMode)
+
+ ## reimplement right-click to zoom out
+ def mouseClickEvent(self, ev):
+ if ev.button() == QtCore.Qt.RightButton:
+ self.autoRange()
+
+ def mouseDragEvent(self, ev):
+ if ev.button() == QtCore.Qt.RightButton:
+ ev.ignore()
+ else:
+ pg.ViewBox.mouseDragEvent(self, ev)
+
+
+app = pg.mkQApp()
+
+axis = DateAxis(orientation='bottom')
+vb = CustomViewBox()
+
+pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with custom axis and ViewBox
Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
+dates = np.arange(8) * (3600*24*356)
+pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o')
+pw.show()
+
+r = pg.PolyLineROI([(0,0), (10, 10)])
+pw.addItem(r)
+
+## Start Qt event loop unless running in interactive mode or using pyside.
+import sys
+if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
+ QtGui.QApplication.instance().exec_()
diff --git a/graphicsItems/AxisItem.py b/graphicsItems/AxisItem.py
index 351b4ed5..bcf6ce5c 100644
--- a/graphicsItems/AxisItem.py
+++ b/graphicsItems/AxisItem.py
@@ -363,6 +363,29 @@ class AxisItem(GraphicsWidget):
(intervals[minorIndex], 0)
]
+ ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection
+ ### Determine major/minor tick spacings which flank the optimal spacing.
+ #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit
+ #minorIndex = 0
+ #while intervals[minorIndex+1] <= optimalSpacing:
+ #minorIndex += 1
+
+ ### make sure we never see 5 and 2 at the same time
+ #intIndexes = [
+ #[0,1,3],
+ #[0,2,3],
+ #[2,3,4],
+ #[3,4,6],
+ #[3,5,6],
+ #][minorIndex]
+
+ #return [
+ #(intervals[intIndexes[2]], 0),
+ #(intervals[intIndexes[1]], 0),
+ #(intervals[intIndexes[0]], 0)
+ #]
+
+
def tickValues(self, minVal, maxVal, size):
"""
@@ -395,7 +418,7 @@ class AxisItem(GraphicsWidget):
## remove any ticks that were present in higher levels
## we assume here that if the difference between a tick value and a previously seen tick value
## is less than spacing/100, then they are 'equal' and we can ignore the new tick.
- values = filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values)
+ values = list(filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values) )
allValues = np.concatenate([allValues, values])
ticks.append((spacing, values))
@@ -601,9 +624,9 @@ class AxisItem(GraphicsWidget):
if tickPositions[i][j] is None:
strings[j] = None
+ textRects.extend([p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, s) for s in strings if s is not None])
if i > 0: ## always draw top level
## measure all text, make sure there's enough room
- textRects.extend([p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, s) for s in strings if s is not None])
if axis == 0:
textSize = np.sum([r.height() for r in textRects])
else:
@@ -613,7 +636,6 @@ class AxisItem(GraphicsWidget):
textFillRatio = float(textSize) / lengthInPixels
if textFillRatio > 0.7:
break
-
#spacing, values = tickLevels[best]
#strings = self.tickStrings(values, self.scale, spacing)
for j in range(len(strings)):
diff --git a/graphicsItems/PlotItem/PlotItem.py b/graphicsItems/PlotItem/PlotItem.py
index f027a434..dfd0ad65 100644
--- a/graphicsItems/PlotItem/PlotItem.py
+++ b/graphicsItems/PlotItem/PlotItem.py
@@ -33,17 +33,12 @@ from .. AxisItem import AxisItem
from .. LabelItem import LabelItem
from .. GraphicsWidget import GraphicsWidget
from .. ButtonItem import ButtonItem
+#from .. GraphicsLayout import GraphicsLayout
from pyqtgraph.WidgetGroup import WidgetGroup
import collections
__all__ = ['PlotItem']
-#try:
- #from WidgetGroup import *
- #HAVE_WIDGETGROUP = True
-#except:
- #HAVE_WIDGETGROUP = False
-
try:
from metaarray import *
HAVE_METAARRAY = True
@@ -99,26 +94,28 @@ class PlotItem(GraphicsWidget):
lastFileDir = None
managers = {}
- def __init__(self, parent=None, name=None, labels=None, title=None, **kargs):
+ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
"""
Create a new PlotItem. All arguments are optional.
Any extra keyword arguments are passed to PlotItem.plot().
- ============= ==========================================================================================
+ ============== ==========================================================================================
**Arguments**
- *title* Title to display at the top of the item. Html is allowed.
- *labels* A dictionary specifying the axis labels to display::
+ *title* Title to display at the top of the item. Html is allowed.
+ *labels* A dictionary specifying the axis labels to display::
- {'left': (args), 'bottom': (args), ...}
+ {'left': (args), 'bottom': (args), ...}
- The name of each axis and the corresponding arguments are passed to
- :func:`PlotItem.setLabel() `
- Optionally, PlotItem my also be initialized with the keyword arguments left,
- right, top, or bottom to achieve the same effect.
- *name* Registers a name for this view so that others may link to it
- ============= ==========================================================================================
-
-
+ The name of each axis and the corresponding arguments are passed to
+ :func:`PlotItem.setLabel() `
+ Optionally, PlotItem my also be initialized with the keyword arguments left,
+ right, top, or bottom to achieve the same effect.
+ *name* Registers a name for this view so that others may link to it
+ *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox.
+ *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items
+ for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top')
+ and the values must be instances of AxisItem (or at least compatible with AxisItem).
+ ============== ==========================================================================================
"""
GraphicsWidget.__init__(self, parent)
@@ -127,8 +124,6 @@ class PlotItem(GraphicsWidget):
## Set up control buttons
path = os.path.dirname(__file__)
- #self.ctrlBtn = ButtonItem(os.path.join(path, 'ctrl.png'), 14, self)
- #self.ctrlBtn.clicked.connect(self.ctrlBtnClicked)
self.autoImageFile = os.path.join(path, 'auto.png')
self.lockImageFile = os.path.join(path, 'lock.png')
self.autoBtn = ButtonItem(self.autoImageFile, 14, self)
@@ -141,32 +136,33 @@ class PlotItem(GraphicsWidget):
self.layout.setHorizontalSpacing(0)
self.layout.setVerticalSpacing(0)
- self.vb = ViewBox(name=name)
+ if viewBox is None:
+ viewBox = ViewBox()
+ self.vb = viewBox
+ self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
+
+ if name is not None:
+ self.vb.register(name)
self.vb.sigRangeChanged.connect(self.sigRangeChanged)
self.vb.sigXRangeChanged.connect(self.sigXRangeChanged)
self.vb.sigYRangeChanged.connect(self.sigYRangeChanged)
- #self.vb.sigRangeChangedManually.connect(self.enableManualScale)
- #self.vb.sigRangeChanged.connect(self.viewRangeChanged)
self.layout.addItem(self.vb, 2, 1)
self.alpha = 1.0
self.autoAlpha = True
self.spectrumMode = False
- #self.autoScale = [True, True]
-
- ## Create and place scale items
- self.scales = {
- 'top': {'item': AxisItem(orientation='top', linkView=self.vb), 'pos': (1, 1)},
- 'bottom': {'item': AxisItem(orientation='bottom', linkView=self.vb), 'pos': (3, 1)},
- 'left': {'item': AxisItem(orientation='left', linkView=self.vb), 'pos': (2, 0)},
- 'right': {'item': AxisItem(orientation='right', linkView=self.vb), 'pos': (2, 2)}
- }
- for k in self.scales:
- item = self.scales[k]['item']
- self.layout.addItem(item, *self.scales[k]['pos'])
- item.setZValue(-1000)
- item.setFlag(item.ItemNegativeZStacksBehindParent)
+ ## Create and place axis items
+ if axisItems is None:
+ axisItems = {}
+ self.axes = {}
+ for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
+ axis = axisItems.get(k, AxisItem(orientation=k))
+ axis.linkToView(self.vb)
+ self.axes[k] = {'item': axis, 'pos': pos}
+ self.layout.addItem(axis, *pos)
+ axis.setZValue(-1000)
+ axis.setFlag(axis.ItemNegativeZStacksBehindParent)
self.titleLabel = LabelItem('', size='11pt')
self.layout.addItem(self.titleLabel, 0, 1)
@@ -193,7 +189,6 @@ class PlotItem(GraphicsWidget):
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
'enableAutoRange', 'disableAutoRange', 'setAspectLocked',
- 'setMenuEnabled', 'menuEnabled',
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
setattr(self, m, getattr(self.vb, m))
@@ -233,45 +228,12 @@ class PlotItem(GraphicsWidget):
self.subMenus.append(sm)
self.ctrlMenu.addMenu(sm)
- ## exporting is handled by GraphicsScene now
- #exportOpts = collections.OrderedDict([
- #('SVG - Full Plot', self.saveSvgClicked),
- #('SVG - Curves Only', self.saveSvgCurvesClicked),
- #('Image', self.saveImgClicked),
- #('CSV', self.saveCsvClicked),
- #])
-
- #self.vb.menu.setExportMethods(exportOpts)
-
-
- #if HAVE_WIDGETGROUP:
self.stateGroup = WidgetGroup()
for name, w in menuItems:
self.stateGroup.autoAdd(w)
self.fileDialog = None
- #self.xLinkPlot = None
- #self.yLinkPlot = None
- #self.linksBlocked = False
-
- #self.setAcceptHoverEvents(True)
-
- ## Connect control widgets
- #c.xMinText.editingFinished.connect(self.setManualXScale)
- #c.xMaxText.editingFinished.connect(self.setManualXScale)
- #c.yMinText.editingFinished.connect(self.setManualYScale)
- #c.yMaxText.editingFinished.connect(self.setManualYScale)
-
- #c.xManualRadio.clicked.connect(lambda: self.updateXScale())
- #c.yManualRadio.clicked.connect(lambda: self.updateYScale())
-
- #c.xAutoRadio.clicked.connect(self.updateXScale)
- #c.yAutoRadio.clicked.connect(self.updateYScale)
-
- #c.xAutoPercentSpin.valueChanged.connect(self.replot)
- #c.yAutoPercentSpin.valueChanged.connect(self.replot)
-
c.alphaGroup.toggled.connect(self.updateAlpha)
c.alphaSlider.valueChanged.connect(self.updateAlpha)
c.autoAlphaCheck.toggled.connect(self.updateAlpha)
@@ -283,13 +245,6 @@ class PlotItem(GraphicsWidget):
c.fftCheck.toggled.connect(self.updateSpectrumMode)
c.logXCheck.toggled.connect(self.updateLogMode)
c.logYCheck.toggled.connect(self.updateLogMode)
- #c.saveSvgBtn.clicked.connect(self.saveSvgClicked)
- #c.saveSvgCurvesBtn.clicked.connect(self.saveSvgCurvesClicked)
- #c.saveImgBtn.clicked.connect(self.saveImgClicked)
- #c.saveCsvBtn.clicked.connect(self.saveCsvClicked)
-
- #self.ctrl.xLinkCombo.currentIndexChanged.connect(self.xLinkComboChanged)
- #self.ctrl.yLinkCombo.currentIndexChanged.connect(self.yLinkComboChanged)
c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
@@ -298,24 +253,15 @@ class PlotItem(GraphicsWidget):
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation)
- #c.xMouseCheck.toggled.connect(self.mouseCheckChanged)
- #c.yMouseCheck.toggled.connect(self.mouseCheckChanged)
-
- #self.xLinkPlot = None
- #self.yLinkPlot = None
- #self.linksBlocked = False
- self.manager = None
self.hideAxis('right')
self.hideAxis('top')
self.showAxis('left')
self.showAxis('bottom')
- #if name is not None:
- #self.registerPlot(name)
if labels is None:
labels = {}
- for label in list(self.scales.keys()):
+ for label in list(self.axes.keys()):
if label in kargs:
labels[label] = kargs[label]
del kargs[label]
@@ -330,15 +276,16 @@ class PlotItem(GraphicsWidget):
if len(kargs) > 0:
self.plot(**kargs)
- #self.enableAutoRange()
def implements(self, interface=None):
return interface in ['ViewBoxWrapper']
def getViewBox(self):
- """Return the ViewBox within."""
+ """Return the :class:`ViewBox ` contained within."""
return self.vb
+
+
def setLogMode(self, x, y):
"""
Set log scaling for x and y axes.
@@ -399,11 +346,11 @@ class PlotItem(GraphicsWidget):
#self.autoBtn.setParent(None)
#self.autoBtn = None
- for k in self.scales:
- i = self.scales[k]['item']
+ for k in self.axes:
+ i = self.axes[k]['item']
i.close()
- self.scales = None
+ self.axes = None
self.scene().removeItem(self.vb)
self.vb = None
@@ -431,47 +378,6 @@ class PlotItem(GraphicsWidget):
def registerPlot(self, name): ## for backward compatibility
self.vb.register(name)
- #self.name = name
- #win = str(self.window())
- ##print "register", name, win
- #if win not in PlotItem.managers:
- #PlotItem.managers[win] = PlotWidgetManager()
- #self.manager = PlotItem.managers[win]
- #self.manager.addWidget(self, name)
- ##QtCore.QObject.connect(self.manager, QtCore.SIGNAL('widgetListChanged'), self.updatePlotList)
- #self.manager.sigWidgetListChanged.connect(self.updatePlotList)
- #self.updatePlotList()
-
- #def updatePlotList(self):
- #"""Update the list of all plotWidgets in the "link" combos"""
- ##print "update plot list", self
- #try:
- #for sc in [self.ctrl.xLinkCombo, self.ctrl.yLinkCombo]:
- #current = unicode(sc.currentText())
- #sc.blockSignals(True)
- #try:
- #sc.clear()
- #sc.addItem("")
- #if self.manager is not None:
- #for w in self.manager.listWidgets():
- ##print w
- #if w == self.name:
- #continue
- #sc.addItem(w)
- #if w == current:
- #sc.setCurrentIndex(sc.count()-1)
- #finally:
- #sc.blockSignals(False)
- #if unicode(sc.currentText()) != current:
- #sc.currentItemChanged.emit()
- #except:
- #import gc
- #refs= gc.get_referrers(self)
- #print " error during update of", self
- #print " Referrers are:", refs
- #raise
-
-
def updateGrid(self, *args):
alpha = self.ctrl.gridAlphaSlider.value()
@@ -492,91 +398,6 @@ class PlotItem(GraphicsWidget):
return wr
-
-
-
- #def viewRangeChanged(self, vb, range):
- ##self.emit(QtCore.SIGNAL('viewChanged'), *args)
- #self.sigRangeChanged.emit(self, range)
-
- #def blockLink(self, b):
- #self.linksBlocked = b
-
- #def xLinkComboChanged(self):
- #self.setXLink(str(self.ctrl.xLinkCombo.currentText()))
-
- #def yLinkComboChanged(self):
- #self.setYLink(str(self.ctrl.yLinkCombo.currentText()))
-
- #def setXLink(self, plot=None):
- #"""Link this plot's X axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
- #if isinstance(plot, basestring):
- #if self.manager is None:
- #return
- #if self.xLinkPlot is not None:
- #self.manager.unlinkX(self, self.xLinkPlot)
- #plot = self.manager.getWidget(plot)
- #if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
- #plot = plot.getPlotItem()
- #self.xLinkPlot = plot
- #if plot is not None:
- #self.setManualXScale()
- #self.manager.linkX(self, plot)
-
-
-
- #def setYLink(self, plot=None):
- #"""Link this plot's Y axis to another plot (pass either the PlotItem/PlotWidget or the registered name of the plot)"""
- #if isinstance(plot, basestring):
- #if self.manager is None:
- #return
- #if self.yLinkPlot is not None:
- #self.manager.unlinkY(self, self.yLinkPlot)
- #plot = self.manager.getWidget(plot)
- #if not isinstance(plot, PlotItem) and hasattr(plot, 'getPlotItem'):
- #plot = plot.getPlotItem()
- #self.yLinkPlot = plot
- #if plot is not None:
- #self.setManualYScale()
- #self.manager.linkY(self, plot)
-
- #def linkXChanged(self, plot):
- #"""Called when a linked plot has changed its X scale"""
- ##print "update from", plot
- #if self.linksBlocked:
- #return
- #pr = plot.vb.viewRect()
- #pg = plot.viewGeometry()
- #if pg is None:
- ##print " return early"
- #return
- #sg = self.viewGeometry()
- #upp = float(pr.width()) / pg.width()
- #x1 = pr.left() + (sg.x()-pg.x()) * upp
- #x2 = x1 + sg.width() * upp
- #plot.blockLink(True)
- #self.setManualXScale()
- #self.setXRange(x1, x2, padding=0)
- #plot.blockLink(False)
- #self.replot()
-
- #def linkYChanged(self, plot):
- #"""Called when a linked plot has changed its Y scale"""
- #if self.linksBlocked:
- #return
- #pr = plot.vb.viewRect()
- #pg = plot.vb.boundingRect()
- #sg = self.vb.boundingRect()
- #upp = float(pr.height()) / pg.height()
- #y1 = pr.bottom() + (sg.y()-pg.y()) * upp
- #y2 = y1 + sg.height() * upp
- #plot.blockLink(True)
- #self.setManualYScale()
- #self.setYRange(y1, y2, padding=0)
- #plot.blockLink(False)
- #self.replot()
-
-
def avgToggled(self, b):
if b:
self.recomputeAverages()
@@ -650,50 +471,6 @@ class PlotItem(GraphicsWidget):
else:
plot.setData(x, y)
-
- #def mouseCheckChanged(self):
- #state = [self.ctrl.xMouseCheck.isChecked(), self.ctrl.yMouseCheck.isChecked()]
- #self.vb.setMouseEnabled(*state)
-
- #def xRangeChanged(self, _, range):
- #if any(np.isnan(range)) or any(np.isinf(range)):
- #raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
- #self.ctrl.xMinText.setText('%0.5g' % range[0])
- #self.ctrl.xMaxText.setText('%0.5g' % range[1])
-
- ### automatically change unit scale
- #maxVal = max(abs(range[0]), abs(range[1]))
- #(scale, prefix) = fn.siScale(maxVal)
- ##for l in ['top', 'bottom']:
- ##if self.getLabel(l).isVisible():
- ##self.setLabel(l, unitPrefix=prefix)
- ##self.getScale(l).setScale(scale)
- ##else:
- ##self.setLabel(l, unitPrefix='')
- ##self.getScale(l).setScale(1.0)
-
- ##self.emit(QtCore.SIGNAL('xRangeChanged'), self, range)
- #self.sigXRangeChanged.emit(self, range)
-
- #def yRangeChanged(self, _, range):
- #if any(np.isnan(range)) or any(np.isinf(range)):
- #raise Exception("yRange invalid: %s. Signal came from %s" % (str(range), str(self.sender())))
- #self.ctrl.yMinText.setText('%0.5g' % range[0])
- #self.ctrl.yMaxText.setText('%0.5g' % range[1])
-
- ### automatically change unit scale
- #maxVal = max(abs(range[0]), abs(range[1]))
- #(scale, prefix) = fn.siScale(maxVal)
- ##for l in ['left', 'right']:
- ##if self.getLabel(l).isVisible():
- ##self.setLabel(l, unitPrefix=prefix)
- ##self.getScale(l).setScale(scale)
- ##else:
- ##self.setLabel(l, unitPrefix='')
- ##self.getScale(l).setScale(1.0)
- ##self.emit(QtCore.SIGNAL('yRangeChanged'), self, range)
- #self.sigYRangeChanged.emit(self, range)
-
def autoBtnClicked(self):
if self.autoBtn.mode == 'auto':
self.enableAutoRange()
@@ -706,72 +483,6 @@ class PlotItem(GraphicsWidget):
"""
print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.")
self.vb.enableAutoRange(self.vb.XYAxes)
- #self.ctrl.xAutoRadio.setChecked(True)
- #self.ctrl.yAutoRadio.setChecked(True)
-
- #self.autoBtn.setImageFile(self.lockImageFile)
- #self.autoBtn.mode = 'lock'
- #self.updateXScale()
- #self.updateYScale()
- #self.replot()
-
- #def updateXScale(self):
- #"""Set plot to autoscale or not depending on state of radio buttons"""
- #if self.ctrl.xManualRadio.isChecked():
- #self.setManualXScale()
- #else:
- #self.setAutoXScale()
- #self.replot()
-
- #def updateYScale(self, b=False):
- #"""Set plot to autoscale or not depending on state of radio buttons"""
- #if self.ctrl.yManualRadio.isChecked():
- #self.setManualYScale()
- #else:
- #self.setAutoYScale()
- #self.replot()
-
- #def enableManualScale(self, v=[True, True]):
- #if v[0]:
- #self.autoScale[0] = False
- #self.ctrl.xManualRadio.setChecked(True)
- ##self.setManualXScale()
- #if v[1]:
- #self.autoScale[1] = False
- #self.ctrl.yManualRadio.setChecked(True)
- ##self.setManualYScale()
- ##self.autoBtn.enable()
- #self.autoBtn.setImageFile(self.autoImageFile)
- #self.autoBtn.mode = 'auto'
- ##self.replot()
-
- #def setManualXScale(self):
- #self.autoScale[0] = False
- #x1 = float(self.ctrl.xMinText.text())
- #x2 = float(self.ctrl.xMaxText.text())
- #self.ctrl.xManualRadio.setChecked(True)
- #self.setXRange(x1, x2, padding=0)
- #self.autoBtn.show()
- ##self.replot()
-
- #def setManualYScale(self):
- #self.autoScale[1] = False
- #y1 = float(self.ctrl.yMinText.text())
- #y2 = float(self.ctrl.yMaxText.text())
- #self.ctrl.yManualRadio.setChecked(True)
- #self.setYRange(y1, y2, padding=0)
- #self.autoBtn.show()
- ##self.replot()
-
- #def setAutoXScale(self):
- #self.autoScale[0] = True
- #self.ctrl.xAutoRadio.setChecked(True)
- ##self.replot()
-
- #def setAutoYScale(self):
- #self.autoScale[1] = True
- #self.ctrl.yAutoRadio.setChecked(True)
- ##self.replot()
def addItem(self, item, *args, **kargs):
"""
@@ -867,17 +578,6 @@ class PlotItem(GraphicsWidget):
"""
-
- #if y is not None:
- #data = y
- #if data2 is not None:
- #x = data
- #data = data2
- #if decimate is not None and decimate > 1:
- #data = data[::decimate]
- #if x is not None:
- #x = x[::decimate]
- ## print 'plot with decimate = %d' % (decimate)
clear = kargs.get('clear', False)
params = kargs.get('params', None)
@@ -888,23 +588,7 @@ class PlotItem(GraphicsWidget):
if params is None:
params = {}
- #if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')):
- #curve = self._plotMetaArray(data, x=x, **kargs)
- #elif isinstance(data, np.ndarray):
- #curve = self._plotArray(data, x=x, **kargs)
- #elif isinstance(data, list):
- #if x is not None:
- #x = np.array(x)
- #curve = self._plotArray(np.array(data), x=x, **kargs)
- #elif data is None:
- #curve = PlotCurveItem(**kargs)
- #else:
- #raise Exception('Not sure how to plot object of type %s' % type(data))
-
- #print data, curve
self.addItem(item, params=params)
- #if pen is not None:
- #curve.setPen(fn.mkPen(pen))
return item
@@ -922,80 +606,34 @@ class PlotItem(GraphicsWidget):
del kargs['size']
return self.plot(*args, **kargs)
- #sp = ScatterPlotItem(*args, **kargs)
- #self.addItem(sp)
- #return sp
-
-
-
- #def plotChanged(self, curve=None):
- ## Recompute auto range if needed
- #args = {}
- #for ax in [0, 1]:
- #print "range", ax
- #if self.autoScale[ax]:
- #percentScale = [self.ctrl.xAutoPercentSpin.value(), self.ctrl.yAutoPercentSpin.value()][ax] * 0.01
- #mn = None
- #mx = None
- #for c in self.curves + [c[1] for c in self.avgCurves.values()] + self.dataItems:
- #if not c.isVisible():
- #continue
- #cmn, cmx = c.getRange(ax, percentScale)
- ##print " ", c, cmn, cmx
- #if mn is None or cmn < mn:
- #mn = cmn
- #if mx is None or cmx > mx:
- #mx = cmx
- #if mn is None or mx is None or any(np.isnan([mn, mx])) or any(np.isinf([mn, mx])):
- #continue
- #if mn == mx:
- #mn -= 1
- #mx += 1
- #if ax == 0:
- #args['xRange'] = [mn, mx]
- #else:
- #args['yRange'] = [mn, mx]
-
- #if len(args) > 0:
- ##print args
- #self.setRange(**args)
def replot(self):
- #self.plotChanged()
self.update()
def updateParamList(self):
self.ctrl.avgParamList.clear()
## Check to see that each parameter for each curve is present in the list
- #print "\nUpdate param list", self
- #print "paramList:", self.paramList
for c in self.curves:
- #print " curve:", c
for p in list(self.itemMeta.get(c, {}).keys()):
- #print " param:", p
if type(p) is tuple:
p = '.'.join(p)
## If the parameter is not in the list, add it.
matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly)
- #print " matches:", matches
if len(matches) == 0:
i = QtGui.QListWidgetItem(p)
if p in self.paramList and self.paramList[p] is True:
- #print " set checked"
i.setCheckState(QtCore.Qt.Checked)
else:
- #print " set unchecked"
i.setCheckState(QtCore.Qt.Unchecked)
self.ctrl.avgParamList.addItem(i)
else:
i = matches[0]
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked)
- #print "paramList:", self.paramList
- ## This is bullshit.
+ ## Qt's SVG-writing capabilities are pretty terrible.
def writeSvgCurves(self, fileName=None):
if fileName is None:
self.fileDialog = FileDialog()
@@ -1190,18 +828,12 @@ class PlotItem(GraphicsWidget):
def saveState(self):
- #if not HAVE_WIDGETGROUP:
- #raise Exception("State save/restore requires WidgetGroup class.")
state = self.stateGroup.state()
state['paramList'] = self.paramList.copy()
state['view'] = self.vb.getState()
- #print "\nSAVE %s:\n" % str(self.name), state
- #print "Saving state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup']))
return state
def restoreState(self, state):
- #if not HAVE_WIDGETGROUP:
- #raise Exception("State save/restore requires WidgetGroup class.")
if 'paramList' in state:
self.paramList = state['paramList'].copy()
@@ -1218,8 +850,6 @@ class PlotItem(GraphicsWidget):
state['yGridCheck'] = state['gridGroup']
self.stateGroup.setState(state)
- #self.updateXScale()
- #self.updateYScale()
self.updateParamList()
if 'view' not in state:
@@ -1232,13 +862,6 @@ class PlotItem(GraphicsWidget):
}
self.vb.setState(state['view'])
-
- #print "\nRESTORE %s:\n" % str(self.name), state
- #print "Restoring state. averageGroup.isChecked(): %s state: %s" % (str(self.ctrl.averageGroup.isChecked()), str(state['averageGroup']))
- #avg = self.ctrl.averageGroup.isChecked()
- #if avg != state['averageGroup']:
- #print " WARNING: avgGroup is %s, should be %s" % (str(avg), str(state['averageGroup']))
-
def widgetGroupInterface(self):
return (None, PlotItem.saveState, PlotItem.restoreState)
@@ -1269,8 +892,6 @@ class PlotItem(GraphicsWidget):
for c in self.curves:
c.setDownsampling(ds)
self.recomputeAverages()
- #for c in self.avgCurves.values():
- #c[1].setDownsampling(ds)
def downsampleMode(self):
@@ -1306,8 +927,6 @@ class PlotItem(GraphicsWidget):
(alpha, auto) = self.alphaState()
for c in self.curves:
c.setAlpha(alpha**2, auto)
-
- #self.replot(autoRange=False)
def alphaState(self):
enabled = self.ctrl.alphaGroup.isChecked()
@@ -1330,9 +949,6 @@ class PlotItem(GraphicsWidget):
mode = False
return mode
- #def wheelEvent(self, ev):
- ## disables default panning the whole scene by mousewheel
- #ev.accept()
def resizeEvent(self, ev):
if self.autoBtn is None: ## already closed down
@@ -1340,29 +956,42 @@ class PlotItem(GraphicsWidget):
btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect())
y = self.size().height() - btnRect.height()
self.autoBtn.setPos(0, y)
-
- #def hoverMoveEvent(self, ev):
- #self.mousePos = ev.pos()
- #self.mouseScreenPos = ev.screenPos()
-
-
- #def ctrlBtnClicked(self):
- #self.ctrlMenu.popup(self.mouseScreenPos)
-
+
+
def getMenu(self):
return self.ctrlMenu
def getContextMenus(self, event):
## called when another item is displaying its context menu; we get to add extras to the end of the menu.
- return self.ctrlMenu
-
+ if self.menuEnabled():
+ return self.ctrlMenu
+ else:
+ return None
+
+ def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'):
+ """
+ Enable or disable the context menu for this PlotItem.
+ By default, the ViewBox's context menu will also be affected.
+ (use enableViewBoxMenu=None to leave the ViewBox unchanged)
+ """
+ self._menuEnabled = enableMenu
+ if enableViewBoxMenu is None:
+ return
+ if enableViewBoxMenu is 'same':
+ enableViewBoxMenu = enableMenu
+ self.vb.setMenuEnabled(enableViewBoxMenu)
+
+ def menuEnabled(self):
+ return self._menuEnabled
+
+
def getLabel(self, key):
pass
def _checkScaleKey(self, key):
- if key not in self.scales:
- raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.scales.keys()))))
+ if key not in self.axes:
+ raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys()))))
def getScale(self, key):
return self.getAxis(key)
@@ -1371,7 +1000,7 @@ class PlotItem(GraphicsWidget):
"""Return the specified AxisItem.
*name* should be 'left', 'bottom', 'top', or 'right'."""
self._checkScaleKey(name)
- return self.scales[name]['item']
+ return self.axes[name]['item']
def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args):
"""
@@ -1417,7 +1046,7 @@ class PlotItem(GraphicsWidget):
axis must be one of 'left', 'bottom', 'right', or 'top'
"""
s = self.getScale(axis)
- p = self.scales[axis]['pos']
+ p = self.axes[axis]['pos']
if show:
s.show()
else:
@@ -1454,7 +1083,6 @@ class PlotItem(GraphicsWidget):
## create curve
try:
xv = arr.xvals(0)
- #print 'xvals:', xv
except:
if x is None:
xv = np.arange(arr.shape[0])
@@ -1474,17 +1102,6 @@ class PlotItem(GraphicsWidget):
return c
- #def saveSvgClicked(self):
- #self.writeSvg()
-
- #def saveSvgCurvesClicked(self):
- #self.writeSvgCurves()
-
- #def saveImgClicked(self):
- #self.writeImage()
-
- #def saveCsvClicked(self):
- #self.writeCsv()
def setExportMode(self, export, opts):
if export:
@@ -1492,63 +1109,3 @@ class PlotItem(GraphicsWidget):
else:
self.autoBtn.show()
-
-#class PlotWidgetManager(QtCore.QObject):
-
- #sigWidgetListChanged = QtCore.Signal(object)
-
- #"""Used for managing communication between PlotWidgets"""
- #def __init__(self):
- #QtCore.QObject.__init__(self)
- #self.widgets = weakref.WeakValueDictionary() # Don't keep PlotWidgets around just because they are listed here
-
- #def addWidget(self, w, name):
- #self.widgets[name] = w
- ##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
- #self.sigWidgetListChanged.emit(self.widgets.keys())
-
- #def removeWidget(self, name):
- #if name in self.widgets:
- #del self.widgets[name]
- ##self.emit(QtCore.SIGNAL('widgetListChanged'), self.widgets.keys())
- #self.sigWidgetListChanged.emit(self.widgets.keys())
- #else:
- #print "plot %s not managed" % name
-
-
- #def listWidgets(self):
- #return self.widgets.keys()
-
- #def getWidget(self, name):
- #if name not in self.widgets:
- #return None
- #else:
- #return self.widgets[name]
-
- #def linkX(self, p1, p2):
- ##QtCore.QObject.connect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
- #p1.sigXRangeChanged.connect(p2.linkXChanged)
- ##QtCore.QObject.connect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
- #p2.sigXRangeChanged.connect(p1.linkXChanged)
- #p1.linkXChanged(p2)
- ##p2.setManualXScale()
-
- #def unlinkX(self, p1, p2):
- ##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('xRangeChanged'), p2.linkXChanged)
- #p1.sigXRangeChanged.disconnect(p2.linkXChanged)
- ##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('xRangeChanged'), p1.linkXChanged)
- #p2.sigXRangeChanged.disconnect(p1.linkXChanged)
-
- #def linkY(self, p1, p2):
- ##QtCore.QObject.connect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
- #p1.sigYRangeChanged.connect(p2.linkYChanged)
- ##QtCore.QObject.connect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
- #p2.sigYRangeChanged.connect(p1.linkYChanged)
- #p1.linkYChanged(p2)
- ##p2.setManualYScale()
-
- #def unlinkY(self, p1, p2):
- ##QtCore.QObject.disconnect(p1, QtCore.SIGNAL('yRangeChanged'), p2.linkYChanged)
- #p1.sigYRangeChanged.disconnect(p2.linkYChanged)
- ##QtCore.QObject.disconnect(p2, QtCore.SIGNAL('yRangeChanged'), p1.linkYChanged)
- #p2.sigYRangeChanged.disconnect(p1.linkYChanged)
diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py
index 398b1e8f..694455e5 100644
--- a/graphicsItems/ViewBox/ViewBox.py
+++ b/graphicsItems/ViewBox/ViewBox.py
@@ -62,7 +62,7 @@ class ViewBox(GraphicsWidget):
NamedViews = weakref.WeakValueDictionary() # name: ViewBox
AllViews = weakref.WeakKeyDictionary() # ViewBox: None
- def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu = True, name=None):
+ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None):
"""
============= =============================================================
**Arguments**
@@ -136,7 +136,7 @@ class ViewBox(GraphicsWidget):
## Make scale box that is shown when dragging on the view
self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1)
- self.rbScaleBox.setPen(fn.mkPen((255,0,0), width=1))
+ self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1))
self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100))
self.rbScaleBox.hide()
self.addItem(self.rbScaleBox)
@@ -358,7 +358,7 @@ class ViewBox(GraphicsWidget):
changes[1] = yRange
if len(changes) == 0:
- print rect
+ print(rect)
raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
changed = [False, False]
@@ -863,7 +863,10 @@ class ViewBox(GraphicsWidget):
return self._menuCopy
def getContextMenus(self, event):
- return self.menu.subMenus()
+ if self.menuEnabled():
+ return self.menu.subMenus()
+ else:
+ return None
#return [self.getMenu(event)]
diff --git a/widgets/PlotWidget.py b/widgets/PlotWidget.py
index 82e4701d..1fa07f2a 100644
--- a/widgets/PlotWidget.py
+++ b/widgets/PlotWidget.py
@@ -41,6 +41,8 @@ class PlotWidget(GraphicsView):
other methods, use :func:`getPlotItem `.
"""
def __init__(self, parent=None, **kargs):
+ """When initializing PlotWidget, all keyword arguments except *parent* are passed
+ to :func:`PlotItem.__init__() `."""
GraphicsView.__init__(self, parent)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.enableMouse(False)