- PlotItem can now be constructed with customized ViewBox and AxisItems

- Text spacing fix for AxisItem
This commit is contained in:
Luke Campagnola 2012-07-09 08:38:30 -04:00
parent f178919bee
commit 662b319d7b
8 changed files with 168 additions and 530 deletions

View File

@ -505,18 +505,19 @@ class GraphicsScene(QtGui.QGraphicsScene):
menusToAdd = [] menusToAdd = []
while item is not self: while item is not self:
item = item.parentItem() item = item.parentItem()
if item is None: if item is None:
item = self item = self
if not hasattr(item, "getContextMenus"): if not hasattr(item, "getContextMenus"):
continue continue
subMenus = item.getContextMenus(event) 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 if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
subMenus = [subMenus] subMenus = [subMenus]
for sm in subMenus: for sm in subMenus:
menusToAdd.append(sm) menusToAdd.append(sm)

View File

@ -49,10 +49,10 @@ def getConfigOption(opt):
def systemInfo(): def systemInfo():
print "sys.platform:", sys.platform print("sys.platform: %s" % sys.platform)
print "sys.version:", sys.version print("sys.version: %s" % sys.version)
from .Qt import VERSION_INFO from .Qt import VERSION_INFO
print "qt bindings:", VERSION_INFO print("qt bindings: %s" % VERSION_INFO)
global REVISION global REVISION
if REVISION is None: ## this code was probably checked out from bzr; look up the last-revision file 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): if os.path.exists(lastRevFile):
REVISION = open(lastRevFile, 'r').read().strip() REVISION = open(lastRevFile, 'r').read().strip()
print "pyqtgraph:", REVISION print("pyqtgraph: %s" % REVISION)
print "config:" print("config:")
import pprint import pprint
pprint.pprint(CONFIG_OPTIONS) pprint.pprint(CONFIG_OPTIONS)
@ -126,7 +126,7 @@ def importAll(path, excludes=()):
globals()[k] = getattr(mod, k) globals()[k] = getattr(mod, k)
importAll('graphicsItems') importAll('graphicsItems')
importAll('widgets', excludes=['MatplotlibWidget']) importAll('widgets', excludes=['MatplotlibWidget', 'RemoteGraphicsView'])
from .imageview import * from .imageview import *
from .WidgetGroup import * from .WidgetGroup import *

View File

@ -16,6 +16,7 @@ examples = OrderedDict([
('Video speed test', 'VideoSpeedTest.py'), ('Video speed test', 'VideoSpeedTest.py'),
('Plot speed test', 'PlotSpeedTest.py'), ('Plot speed test', 'PlotSpeedTest.py'),
('Data Slicing', 'DataSlicing.py'), ('Data Slicing', 'DataSlicing.py'),
('Plot Customization', 'customPlot.py'),
('GraphicsItems', OrderedDict([ ('GraphicsItems', OrderedDict([
('Scatter Plot', 'ScatterPlot.py'), ('Scatter Plot', 'ScatterPlot.py'),
#('PlotItem', 'PlotItem.py'), #('PlotItem', 'PlotItem.py'),
@ -45,7 +46,7 @@ examples = OrderedDict([
#('VerticalLabel', '../widgets/VerticalLabel.py'), #('VerticalLabel', '../widgets/VerticalLabel.py'),
('JoystickButton', 'JoystickButton.py'), ('JoystickButton', 'JoystickButton.py'),
])), ])),
('GraphicsScene', 'GraphicsScene.py'), ('GraphicsScene', 'GraphicsScene.py'),
('Flowcharts', 'Flowchart.py'), ('Flowcharts', 'Flowchart.py'),
#('Canvas', '../canvas'), #('Canvas', '../canvas'),

52
examples/customPlot.py Normal file
View File

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

View File

@ -363,6 +363,29 @@ class AxisItem(GraphicsWidget):
(intervals[minorIndex], 0) (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): def tickValues(self, minVal, maxVal, size):
""" """
@ -395,7 +418,7 @@ class AxisItem(GraphicsWidget):
## remove any ticks that were present in higher levels ## 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 ## 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. ## 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]) allValues = np.concatenate([allValues, values])
ticks.append((spacing, values)) ticks.append((spacing, values))
@ -601,9 +624,9 @@ class AxisItem(GraphicsWidget):
if tickPositions[i][j] is None: if tickPositions[i][j] is None:
strings[j] = 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 if i > 0: ## always draw top level
## measure all text, make sure there's enough room ## 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: if axis == 0:
textSize = np.sum([r.height() for r in textRects]) textSize = np.sum([r.height() for r in textRects])
else: else:
@ -613,7 +636,6 @@ class AxisItem(GraphicsWidget):
textFillRatio = float(textSize) / lengthInPixels textFillRatio = float(textSize) / lengthInPixels
if textFillRatio > 0.7: if textFillRatio > 0.7:
break break
#spacing, values = tickLevels[best] #spacing, values = tickLevels[best]
#strings = self.tickStrings(values, self.scale, spacing) #strings = self.tickStrings(values, self.scale, spacing)
for j in range(len(strings)): for j in range(len(strings)):

View File

@ -33,17 +33,12 @@ from .. AxisItem import AxisItem
from .. LabelItem import LabelItem from .. LabelItem import LabelItem
from .. GraphicsWidget import GraphicsWidget from .. GraphicsWidget import GraphicsWidget
from .. ButtonItem import ButtonItem from .. ButtonItem import ButtonItem
#from .. GraphicsLayout import GraphicsLayout
from pyqtgraph.WidgetGroup import WidgetGroup from pyqtgraph.WidgetGroup import WidgetGroup
import collections import collections
__all__ = ['PlotItem'] __all__ = ['PlotItem']
#try:
#from WidgetGroup import *
#HAVE_WIDGETGROUP = True
#except:
#HAVE_WIDGETGROUP = False
try: try:
from metaarray import * from metaarray import *
HAVE_METAARRAY = True HAVE_METAARRAY = True
@ -99,26 +94,28 @@ class PlotItem(GraphicsWidget):
lastFileDir = None lastFileDir = None
managers = {} 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. Create a new PlotItem. All arguments are optional.
Any extra keyword arguments are passed to PlotItem.plot(). Any extra keyword arguments are passed to PlotItem.plot().
============= ========================================================================================== ============== ==========================================================================================
**Arguments** **Arguments**
*title* Title to display at the top of the item. Html is allowed. *title* Title to display at the top of the item. Html is allowed.
*labels* A dictionary specifying the axis labels to display:: *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 The name of each axis and the corresponding arguments are passed to
:func:`PlotItem.setLabel() <pyqtgraph.PlotItem.setLabel>` :func:`PlotItem.setLabel() <pyqtgraph.PlotItem.setLabel>`
Optionally, PlotItem my also be initialized with the keyword arguments left, Optionally, PlotItem my also be initialized with the keyword arguments left,
right, top, or bottom to achieve the same effect. right, top, or bottom to achieve the same effect.
*name* Registers a name for this view so that others may link to it *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) GraphicsWidget.__init__(self, parent)
@ -127,8 +124,6 @@ class PlotItem(GraphicsWidget):
## Set up control buttons ## Set up control buttons
path = os.path.dirname(__file__) 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.autoImageFile = os.path.join(path, 'auto.png')
self.lockImageFile = os.path.join(path, 'lock.png') self.lockImageFile = os.path.join(path, 'lock.png')
self.autoBtn = ButtonItem(self.autoImageFile, 14, self) self.autoBtn = ButtonItem(self.autoImageFile, 14, self)
@ -141,32 +136,33 @@ class PlotItem(GraphicsWidget):
self.layout.setHorizontalSpacing(0) self.layout.setHorizontalSpacing(0)
self.layout.setVerticalSpacing(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.sigRangeChanged.connect(self.sigRangeChanged)
self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) self.vb.sigXRangeChanged.connect(self.sigXRangeChanged)
self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) 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.layout.addItem(self.vb, 2, 1)
self.alpha = 1.0 self.alpha = 1.0
self.autoAlpha = True self.autoAlpha = True
self.spectrumMode = False self.spectrumMode = False
#self.autoScale = [True, True] ## Create and place axis items
if axisItems is None:
## Create and place scale items axisItems = {}
self.scales = { self.axes = {}
'top': {'item': AxisItem(orientation='top', linkView=self.vb), 'pos': (1, 1)}, for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
'bottom': {'item': AxisItem(orientation='bottom', linkView=self.vb), 'pos': (3, 1)}, axis = axisItems.get(k, AxisItem(orientation=k))
'left': {'item': AxisItem(orientation='left', linkView=self.vb), 'pos': (2, 0)}, axis.linkToView(self.vb)
'right': {'item': AxisItem(orientation='right', linkView=self.vb), 'pos': (2, 2)} self.axes[k] = {'item': axis, 'pos': pos}
} self.layout.addItem(axis, *pos)
for k in self.scales: axis.setZValue(-1000)
item = self.scales[k]['item'] axis.setFlag(axis.ItemNegativeZStacksBehindParent)
self.layout.addItem(item, *self.scales[k]['pos'])
item.setZValue(-1000)
item.setFlag(item.ItemNegativeZStacksBehindParent)
self.titleLabel = LabelItem('', size='11pt') self.titleLabel = LabelItem('', size='11pt')
self.layout.addItem(self.titleLabel, 0, 1) self.layout.addItem(self.titleLabel, 0, 1)
@ -193,7 +189,6 @@ class PlotItem(GraphicsWidget):
'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'enableAutoRange', 'disableAutoRange', 'setAspectLocked',
'setMenuEnabled', 'menuEnabled',
'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
setattr(self, m, getattr(self.vb, m)) setattr(self, m, getattr(self.vb, m))
@ -233,45 +228,12 @@ class PlotItem(GraphicsWidget):
self.subMenus.append(sm) self.subMenus.append(sm)
self.ctrlMenu.addMenu(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() self.stateGroup = WidgetGroup()
for name, w in menuItems: for name, w in menuItems:
self.stateGroup.autoAdd(w) self.stateGroup.autoAdd(w)
self.fileDialog = None 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.alphaGroup.toggled.connect(self.updateAlpha)
c.alphaSlider.valueChanged.connect(self.updateAlpha) c.alphaSlider.valueChanged.connect(self.updateAlpha)
c.autoAlphaCheck.toggled.connect(self.updateAlpha) c.autoAlphaCheck.toggled.connect(self.updateAlpha)
@ -283,13 +245,6 @@ class PlotItem(GraphicsWidget):
c.fftCheck.toggled.connect(self.updateSpectrumMode) c.fftCheck.toggled.connect(self.updateSpectrumMode)
c.logXCheck.toggled.connect(self.updateLogMode) c.logXCheck.toggled.connect(self.updateLogMode)
c.logYCheck.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) c.downsampleSpin.valueChanged.connect(self.updateDownsampling)
@ -298,24 +253,15 @@ class PlotItem(GraphicsWidget):
self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation)
self.ctrl.maxTracesSpin.valueChanged.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('right')
self.hideAxis('top') self.hideAxis('top')
self.showAxis('left') self.showAxis('left')
self.showAxis('bottom') self.showAxis('bottom')
#if name is not None:
#self.registerPlot(name)
if labels is None: if labels is None:
labels = {} labels = {}
for label in list(self.scales.keys()): for label in list(self.axes.keys()):
if label in kargs: if label in kargs:
labels[label] = kargs[label] labels[label] = kargs[label]
del kargs[label] del kargs[label]
@ -330,15 +276,16 @@ class PlotItem(GraphicsWidget):
if len(kargs) > 0: if len(kargs) > 0:
self.plot(**kargs) self.plot(**kargs)
#self.enableAutoRange()
def implements(self, interface=None): def implements(self, interface=None):
return interface in ['ViewBoxWrapper'] return interface in ['ViewBoxWrapper']
def getViewBox(self): def getViewBox(self):
"""Return the ViewBox within.""" """Return the :class:`ViewBox <pyqtgraph.ViewBox>` contained within."""
return self.vb return self.vb
def setLogMode(self, x, y): def setLogMode(self, x, y):
""" """
Set log scaling for x and y axes. Set log scaling for x and y axes.
@ -399,11 +346,11 @@ class PlotItem(GraphicsWidget):
#self.autoBtn.setParent(None) #self.autoBtn.setParent(None)
#self.autoBtn = None #self.autoBtn = None
for k in self.scales: for k in self.axes:
i = self.scales[k]['item'] i = self.axes[k]['item']
i.close() i.close()
self.scales = None self.axes = None
self.scene().removeItem(self.vb) self.scene().removeItem(self.vb)
self.vb = None self.vb = None
@ -431,47 +378,6 @@ class PlotItem(GraphicsWidget):
def registerPlot(self, name): ## for backward compatibility def registerPlot(self, name): ## for backward compatibility
self.vb.register(name) 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): def updateGrid(self, *args):
alpha = self.ctrl.gridAlphaSlider.value() alpha = self.ctrl.gridAlphaSlider.value()
@ -492,91 +398,6 @@ class PlotItem(GraphicsWidget):
return wr 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): def avgToggled(self, b):
if b: if b:
self.recomputeAverages() self.recomputeAverages()
@ -650,50 +471,6 @@ class PlotItem(GraphicsWidget):
else: else:
plot.setData(x, y) 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): def autoBtnClicked(self):
if self.autoBtn.mode == 'auto': if self.autoBtn.mode == 'auto':
self.enableAutoRange() self.enableAutoRange()
@ -706,72 +483,6 @@ class PlotItem(GraphicsWidget):
""" """
print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.") print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.")
self.vb.enableAutoRange(self.vb.XYAxes) 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): 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) clear = kargs.get('clear', False)
params = kargs.get('params', None) params = kargs.get('params', None)
@ -888,23 +588,7 @@ class PlotItem(GraphicsWidget):
if params is None: if params is None:
params = {} 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) self.addItem(item, params=params)
#if pen is not None:
#curve.setPen(fn.mkPen(pen))
return item return item
@ -922,80 +606,34 @@ class PlotItem(GraphicsWidget):
del kargs['size'] del kargs['size']
return self.plot(*args, **kargs) 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): def replot(self):
#self.plotChanged()
self.update() self.update()
def updateParamList(self): def updateParamList(self):
self.ctrl.avgParamList.clear() self.ctrl.avgParamList.clear()
## Check to see that each parameter for each curve is present in the list ## 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: for c in self.curves:
#print " curve:", c
for p in list(self.itemMeta.get(c, {}).keys()): for p in list(self.itemMeta.get(c, {}).keys()):
#print " param:", p
if type(p) is tuple: if type(p) is tuple:
p = '.'.join(p) p = '.'.join(p)
## If the parameter is not in the list, add it. ## If the parameter is not in the list, add it.
matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly)
#print " matches:", matches
if len(matches) == 0: if len(matches) == 0:
i = QtGui.QListWidgetItem(p) i = QtGui.QListWidgetItem(p)
if p in self.paramList and self.paramList[p] is True: if p in self.paramList and self.paramList[p] is True:
#print " set checked"
i.setCheckState(QtCore.Qt.Checked) i.setCheckState(QtCore.Qt.Checked)
else: else:
#print " set unchecked"
i.setCheckState(QtCore.Qt.Unchecked) i.setCheckState(QtCore.Qt.Unchecked)
self.ctrl.avgParamList.addItem(i) self.ctrl.avgParamList.addItem(i)
else: else:
i = matches[0] i = matches[0]
self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) 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): def writeSvgCurves(self, fileName=None):
if fileName is None: if fileName is None:
self.fileDialog = FileDialog() self.fileDialog = FileDialog()
@ -1190,18 +828,12 @@ class PlotItem(GraphicsWidget):
def saveState(self): def saveState(self):
#if not HAVE_WIDGETGROUP:
#raise Exception("State save/restore requires WidgetGroup class.")
state = self.stateGroup.state() state = self.stateGroup.state()
state['paramList'] = self.paramList.copy() state['paramList'] = self.paramList.copy()
state['view'] = self.vb.getState() 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 return state
def restoreState(self, state): def restoreState(self, state):
#if not HAVE_WIDGETGROUP:
#raise Exception("State save/restore requires WidgetGroup class.")
if 'paramList' in state: if 'paramList' in state:
self.paramList = state['paramList'].copy() self.paramList = state['paramList'].copy()
@ -1218,8 +850,6 @@ class PlotItem(GraphicsWidget):
state['yGridCheck'] = state['gridGroup'] state['yGridCheck'] = state['gridGroup']
self.stateGroup.setState(state) self.stateGroup.setState(state)
#self.updateXScale()
#self.updateYScale()
self.updateParamList() self.updateParamList()
if 'view' not in state: if 'view' not in state:
@ -1232,13 +862,6 @@ class PlotItem(GraphicsWidget):
} }
self.vb.setState(state['view']) 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): def widgetGroupInterface(self):
return (None, PlotItem.saveState, PlotItem.restoreState) return (None, PlotItem.saveState, PlotItem.restoreState)
@ -1269,8 +892,6 @@ class PlotItem(GraphicsWidget):
for c in self.curves: for c in self.curves:
c.setDownsampling(ds) c.setDownsampling(ds)
self.recomputeAverages() self.recomputeAverages()
#for c in self.avgCurves.values():
#c[1].setDownsampling(ds)
def downsampleMode(self): def downsampleMode(self):
@ -1306,8 +927,6 @@ class PlotItem(GraphicsWidget):
(alpha, auto) = self.alphaState() (alpha, auto) = self.alphaState()
for c in self.curves: for c in self.curves:
c.setAlpha(alpha**2, auto) c.setAlpha(alpha**2, auto)
#self.replot(autoRange=False)
def alphaState(self): def alphaState(self):
enabled = self.ctrl.alphaGroup.isChecked() enabled = self.ctrl.alphaGroup.isChecked()
@ -1330,9 +949,6 @@ class PlotItem(GraphicsWidget):
mode = False mode = False
return mode return mode
#def wheelEvent(self, ev):
## disables default panning the whole scene by mousewheel
#ev.accept()
def resizeEvent(self, ev): def resizeEvent(self, ev):
if self.autoBtn is None: ## already closed down if self.autoBtn is None: ## already closed down
@ -1340,29 +956,42 @@ class PlotItem(GraphicsWidget):
btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect())
y = self.size().height() - btnRect.height() y = self.size().height() - btnRect.height()
self.autoBtn.setPos(0, y) 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): def getMenu(self):
return self.ctrlMenu return self.ctrlMenu
def getContextMenus(self, event): def getContextMenus(self, event):
## called when another item is displaying its context menu; we get to add extras to the end of the menu. ## 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): def getLabel(self, key):
pass pass
def _checkScaleKey(self, key): def _checkScaleKey(self, key):
if key not in self.scales: if key not in self.axes:
raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.scales.keys())))) raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys()))))
def getScale(self, key): def getScale(self, key):
return self.getAxis(key) return self.getAxis(key)
@ -1371,7 +1000,7 @@ class PlotItem(GraphicsWidget):
"""Return the specified AxisItem. """Return the specified AxisItem.
*name* should be 'left', 'bottom', 'top', or 'right'.""" *name* should be 'left', 'bottom', 'top', or 'right'."""
self._checkScaleKey(name) self._checkScaleKey(name)
return self.scales[name]['item'] return self.axes[name]['item']
def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): 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' axis must be one of 'left', 'bottom', 'right', or 'top'
""" """
s = self.getScale(axis) s = self.getScale(axis)
p = self.scales[axis]['pos'] p = self.axes[axis]['pos']
if show: if show:
s.show() s.show()
else: else:
@ -1454,7 +1083,6 @@ class PlotItem(GraphicsWidget):
## create curve ## create curve
try: try:
xv = arr.xvals(0) xv = arr.xvals(0)
#print 'xvals:', xv
except: except:
if x is None: if x is None:
xv = np.arange(arr.shape[0]) xv = np.arange(arr.shape[0])
@ -1474,17 +1102,6 @@ class PlotItem(GraphicsWidget):
return c 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): def setExportMode(self, export, opts):
if export: if export:
@ -1492,63 +1109,3 @@ class PlotItem(GraphicsWidget):
else: else:
self.autoBtn.show() 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)

View File

@ -62,7 +62,7 @@ class ViewBox(GraphicsWidget):
NamedViews = weakref.WeakValueDictionary() # name: ViewBox NamedViews = weakref.WeakValueDictionary() # name: ViewBox
AllViews = weakref.WeakKeyDictionary() # ViewBox: None 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** **Arguments**
@ -136,7 +136,7 @@ class ViewBox(GraphicsWidget):
## Make scale box that is shown when dragging on the view ## Make scale box that is shown when dragging on the view
self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) 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.setBrush(fn.mkBrush(255,255,0,100))
self.rbScaleBox.hide() self.rbScaleBox.hide()
self.addItem(self.rbScaleBox) self.addItem(self.rbScaleBox)
@ -358,7 +358,7 @@ class ViewBox(GraphicsWidget):
changes[1] = yRange changes[1] = yRange
if len(changes) == 0: 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))) raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect)))
changed = [False, False] changed = [False, False]
@ -863,7 +863,10 @@ class ViewBox(GraphicsWidget):
return self._menuCopy return self._menuCopy
def getContextMenus(self, event): def getContextMenus(self, event):
return self.menu.subMenus() if self.menuEnabled():
return self.menu.subMenus()
else:
return None
#return [self.getMenu(event)] #return [self.getMenu(event)]

View File

@ -41,6 +41,8 @@ class PlotWidget(GraphicsView):
other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`. other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`.
""" """
def __init__(self, parent=None, **kargs): def __init__(self, parent=None, **kargs):
"""When initializing PlotWidget, all keyword arguments except *parent* are passed
to :func:`PlotItem.__init__() <pyqtgraph.PlotItem.__init__>`."""
GraphicsView.__init__(self, parent) GraphicsView.__init__(self, parent)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.enableMouse(False) self.enableMouse(False)