Merge branch 'develop' into pyside2-uic

This commit is contained in:
Ogi Moore 2020-06-03 21:48:16 -07:00 committed by GitHub
commit 47f06e78be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 771 additions and 281 deletions

View File

@ -128,16 +128,19 @@ jobs:
displayName: 'Install Wheel' displayName: 'Install Wheel'
- bash: | - bash: |
sudo apt-get install -y libxkbcommon-x11-0 # herbstluftwm sudo apt-get install -y libxkbcommon-x11-dev
# workaround for QTBUG-84489
sudo apt-get install -y libxcb-xfixes0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0
if [ $(install.method) == "conda" ] if [ $(install.method) == "conda" ]
then then
source activate test-environment-$(python.version) source activate test-environment-$(python.version)
fi fi
pip install pytest-xvfb pip install PyVirtualDisplay==0.2.5 pytest-xvfb
displayName: "Virtual Display Setup" displayName: "Virtual Display Setup"
condition: eq(variables['agent.os'], 'Linux' ) condition: eq(variables['agent.os'], 'Linux' )
- bash: | - bash: |
export QT_DEBUG_PLUGINS=1
if [ $(install.method) == "conda" ] if [ $(install.method) == "conda" ]
then then
source activate test-environment-$(python.version) source activate test-environment-$(python.version)

View File

@ -11,7 +11,9 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys, os import sys
import os
from datetime import datetime
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@ -19,6 +21,7 @@ import sys, os
path = os.path.dirname(os.path.abspath(__file__)) path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(path, '..', '..')) sys.path.insert(0, os.path.join(path, '..', '..'))
sys.path.insert(0, os.path.join(path, '..', 'extensions')) sys.path.insert(0, os.path.join(path, '..', 'extensions'))
import pyqtgraph
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
@ -43,16 +46,16 @@ master_doc = 'index'
# General information about the project. # General information about the project.
project = 'pyqtgraph' project = 'pyqtgraph'
copyright = '2011, Luke Campagnola' copyright = '2011 - {}, Luke Campagnola'.format(datetime.now().year)
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.10.0' version = pyqtgraph.__version__
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.10.0' release = version
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="1"> <item row="1" column="1">

View File

@ -41,7 +41,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8)) self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8)) self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -36,7 +36,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8)) self.pixelModeCheck.setText(QtGui.QApplication.translate("Form", "pixel mode", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8)) self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <item row="0" column="0">

View File

@ -89,7 +89,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.graphicsSystemCombo.setItemText(0, _translate("Form", "default", None)) self.graphicsSystemCombo.setItemText(0, _translate("Form", "default", None))
self.graphicsSystemCombo.setItemText(1, _translate("Form", "native", None)) self.graphicsSystemCombo.setItemText(1, _translate("Form", "native", None))
self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster", None)) self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster", None))

View File

@ -78,7 +78,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.graphicsSystemCombo.setItemText(0, _translate("Form", "default")) self.graphicsSystemCombo.setItemText(0, _translate("Form", "default"))
self.graphicsSystemCombo.setItemText(1, _translate("Form", "native")) self.graphicsSystemCombo.setItemText(1, _translate("Form", "native"))
self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster")) self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster"))

View File

@ -78,7 +78,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsSystemCombo.setItemText(0, QtGui.QApplication.translate("Form", "default", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(0, QtGui.QApplication.translate("Form", "default", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsSystemCombo.setItemText(1, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(1, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsSystemCombo.setItemText(2, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8)) self.graphicsSystemCombo.setItemText(2, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -96,6 +96,16 @@ params = [
{'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True}, {'name': 'Renamable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'renamable': True},
{'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True}, {'name': 'Removable', 'type': 'float', 'value': 1.2e6, 'siPrefix': True, 'suffix': 'Hz', 'removable': True},
]}, ]},
{'name': 'Custom context menu', 'type': 'group', 'children': [
{'name': 'List contextMenu', 'type': 'float', 'value': 0, 'context': [
'menu1',
'menu2'
]},
{'name': 'Dict contextMenu', 'type': 'float', 'value': 0, 'context': {
'changeName': 'Title',
'internal': 'What the user sees',
}},
]},
ComplexParameter(name='Custom parameter group (reciprocal values)'), ComplexParameter(name='Custom parameter group (reciprocal values)'),
ScalableGroup(name="Expandable Parameter Group", children=[ ScalableGroup(name="Expandable Parameter Group", children=[
{'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"}, {'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"},

View File

@ -23,8 +23,6 @@ class ExportDialog(QtGui.QWidget):
self.currentExporter = None self.currentExporter = None
self.scene = scene self.scene = scene
self.exporterParameters = {}
self.selectBox = QtGui.QGraphicsRectItem() self.selectBox = QtGui.QGraphicsRectItem()
self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine)) self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine))
self.selectBox.hide() self.selectBox.hide()
@ -124,16 +122,7 @@ class ExportDialog(QtGui.QWidget):
expClass = self.exporterClasses[str(item.text())] expClass = self.exporterClasses[str(item.text())]
exp = expClass(item=self.ui.itemTree.currentItem().gitem) exp = expClass(item=self.ui.itemTree.currentItem().gitem)
if prev:
oldtext = str(prev.text())
self.exporterParameters[oldtext] = self.currentExporter.parameters()
newtext = str(item.text())
if newtext in self.exporterParameters.keys():
params = self.exporterParameters[newtext]
exp.params = params
else:
params = exp.parameters() params = exp.parameters()
self.exporterParameters[newtext] = params
if params is None: if params is None:
self.ui.paramTree.clear() self.ui.paramTree.clear()

View File

@ -1,3 +1,4 @@
import numpy as np
class PlotData(object): class PlotData(object):
@ -50,7 +51,3 @@ class PlotData(object):
mn = np.min(self[field]) mn = np.min(self[field])
self.minVals[field] = mn self.minVals[field] = mn
return mn return mn

View File

@ -413,12 +413,20 @@ def plot(*args, **kargs):
dataArgs[k] = kargs[k] dataArgs[k] = kargs[k]
w = PlotWindow(**pwArgs) w = PlotWindow(**pwArgs)
w.sigClosed.connect(_plotWindowClosed)
if len(args) > 0 or len(dataArgs) > 0: if len(args) > 0 or len(dataArgs) > 0:
w.plot(*args, **dataArgs) w.plot(*args, **dataArgs)
plots.append(w) plots.append(w)
w.show() w.show()
return w return w
def _plotWindowClosed(w):
w.close()
try:
plots.remove(w)
except ValueError:
pass
def image(*args, **kargs): def image(*args, **kargs):
""" """
Create and return an :class:`ImageWindow <pyqtgraph.ImageWindow>` Create and return an :class:`ImageWindow <pyqtgraph.ImageWindow>`
@ -429,11 +437,19 @@ def image(*args, **kargs):
""" """
mkQApp() mkQApp()
w = ImageWindow(*args, **kargs) w = ImageWindow(*args, **kargs)
w.sigClosed.connect(_imageWindowClosed)
images.append(w) images.append(w)
w.show() w.show()
return w return w
show = image ## for backward compatibility show = image ## for backward compatibility
def _imageWindowClosed(w):
w.close()
try:
images.remove(w)
except ValueError:
pass
def dbg(*args, **kwds): def dbg(*args, **kwds):
""" """
Create a console window and begin watching for exceptions. Create a console window and begin watching for exceptions.

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<property name="margin"> <property name="margin">

View File

@ -91,7 +91,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.autoRangeBtn.setText(_translate("Form", "Auto Range", None)) self.autoRangeBtn.setText(_translate("Form", "Auto Range", None))
self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.", None)) self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.", None))
self.redirectCheck.setText(_translate("Form", "Redirect", None)) self.redirectCheck.setText(_translate("Form", "Redirect", None))

View File

@ -79,7 +79,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.autoRangeBtn.setText(_translate("Form", "Auto Range")) self.autoRangeBtn.setText(_translate("Form", "Auto Range"))
self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas."))
self.redirectCheck.setText(_translate("Form", "Redirect")) self.redirectCheck.setText(_translate("Form", "Redirect"))

View File

@ -80,7 +80,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -17,7 +17,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing"> <property name="spacing">

View File

@ -59,7 +59,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.translateLabel.setText(_translate("Form", "Translate:", None)) self.translateLabel.setText(_translate("Form", "Translate:", None))
self.rotateLabel.setText(_translate("Form", "Rotate:", None)) self.rotateLabel.setText(_translate("Form", "Rotate:", None))
self.scaleLabel.setText(_translate("Form", "Scale:", None)) self.scaleLabel.setText(_translate("Form", "Scale:", None))

View File

@ -46,7 +46,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.translateLabel.setText(_translate("Form", "Translate:")) self.translateLabel.setText(_translate("Form", "Translate:"))
self.rotateLabel.setText(_translate("Form", "Rotate:")) self.rotateLabel.setText(_translate("Form", "Rotate:"))
self.scaleLabel.setText(_translate("Form", "Scale:")) self.scaleLabel.setText(_translate("Form", "Scale:"))

View File

@ -46,7 +46,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8))
self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8))
self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -223,6 +223,7 @@ class Dock(QtGui.QWidget, DockDrop):
def close(self): def close(self):
"""Remove this dock from the DockArea it lives inside.""" """Remove this dock from the DockArea it lives inside."""
self.setParent(None) self.setParent(None)
QtGui.QLabel.close(self.label)
self.label.setParent(None) self.label.setParent(None)
self._container.apoptose() self._container.apoptose()
self._container = None self._container = None

View File

@ -3,6 +3,7 @@ from ..Qt import QtGui, QtCore
from .Exporter import Exporter from .Exporter import Exporter
from ..parametertree import Parameter from ..parametertree import Parameter
from .. import PlotItem from .. import PlotItem
from ..python2_3 import asUnicode
__all__ = ['CSVExporter'] __all__ = ['CSVExporter']
@ -57,7 +58,7 @@ class CSVExporter(Exporter):
sep = '\t' sep = '\t'
with open(fileName, 'w') as fd: with open(fileName, 'w') as fd:
fd.write(sep.join(header) + '\n') fd.write(sep.join(map(asUnicode, header)) + '\n')
i = 0 i = 0
numFormat = '%%0.%dg' % self.params['precision'] numFormat = '%%0.%dg' % self.params['precision']
numRows = max([len(d[0]) for d in data]) numRows = max([len(d[0]) for d in data])

View File

@ -45,14 +45,19 @@ class ImageExporter(Exporter):
def parameters(self): def parameters(self):
return self.params return self.params
def export(self, fileName=None, toBytes=False, copy=False): @staticmethod
if fileName is None and not toBytes and not copy: def getSupportedImageFormats():
filter = ["*."+f.data().decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] filter = ["*."+f.data().decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()]
preferred = ['*.png', '*.tif', '*.jpg'] preferred = ['*.png', '*.tif', '*.jpg']
for p in preferred[::-1]: for p in preferred[::-1]:
if p in filter: if p in filter:
filter.remove(p) filter.remove(p)
filter.insert(0, p) filter.insert(0, p)
return filter
def export(self, fileName=None, toBytes=False, copy=False):
if fileName is None and not toBytes and not copy:
filter = self.getSupportedImageFormats()
self.fileSaveDialog(filter=filter) self.fileSaveDialog(filter=filter)
return return

View File

@ -763,6 +763,9 @@ class FlowchartCtrlWidget(QtGui.QWidget):
item = self.items[node] item = self.items[node]
self.ui.ctrlList.setCurrentItem(item) self.ui.ctrlList.setCurrentItem(item)
def clearSelection(self):
self.ui.ctrlList.selectionModel().clearSelection()
class FlowchartWidget(dockarea.DockArea): class FlowchartWidget(dockarea.DockArea):
"""Includes the actual graphical flowchart and debugging interface""" """Includes the actual graphical flowchart and debugging interface"""
@ -890,7 +893,10 @@ class FlowchartWidget(dockarea.DockArea):
item = items[0] item = items[0]
if hasattr(item, 'node') and isinstance(item.node, Node): if hasattr(item, 'node') and isinstance(item.node, Node):
n = item.node n = item.node
if n in self.ctrl.items:
self.ctrl.select(n) self.ctrl.select(n)
else:
self.ctrl.clearSelection()
data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} data = {'outputs': n.outputValues(), 'inputs': n.inputValues()}
self.selNameLabel.setText(n.name()) self.selNameLabel.setText(n.name())
if hasattr(n, 'nodeName'): if hasattr(n, 'nodeName'):

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="verticalSpacing"> <property name="verticalSpacing">

View File

@ -69,7 +69,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.loadBtn.setText(_translate("Form", "Load..", None)) self.loadBtn.setText(_translate("Form", "Load..", None))
self.saveBtn.setText(_translate("Form", "Save", None)) self.saveBtn.setText(_translate("Form", "Save", None))
self.saveAsBtn.setText(_translate("Form", "As..", None)) self.saveAsBtn.setText(_translate("Form", "As..", None))

View File

@ -56,7 +56,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.loadBtn.setText(_translate("Form", "Load..")) self.loadBtn.setText(_translate("Form", "Load.."))
self.saveBtn.setText(_translate("Form", "Save")) self.saveBtn.setText(_translate("Form", "Save"))
self.saveAsBtn.setText(_translate("Form", "As..")) self.saveAsBtn.setText(_translate("Form", "As.."))

View File

@ -55,7 +55,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8))
self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8))
self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<widget class="QWidget" name="selInfoWidget" native="true"> <widget class="QWidget" name="selInfoWidget" native="true">
<property name="geometry"> <property name="geometry">

View File

@ -62,7 +62,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView
from ..widgets.DataTreeWidget import DataTreeWidget from ..widgets.DataTreeWidget import DataTreeWidget

View File

@ -49,7 +49,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
from ..widgets.DataTreeWidget import DataTreeWidget from ..widgets.DataTreeWidget import DataTreeWidget
from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView

View File

@ -48,7 +48,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView
from ..widgets.DataTreeWidget import DataTreeWidget from ..widgets.DataTreeWidget import DataTreeWidget

View File

@ -2,6 +2,7 @@
from ..Node import Node from ..Node import Node
from ...Qt import QtGui, QtCore from ...Qt import QtGui, QtCore
import numpy as np import numpy as np
import sys
from .common import * from .common import *
from ...SRTTransform import SRTTransform from ...SRTTransform import SRTTransform
from ...Point import Point from ...Point import Point
@ -238,7 +239,12 @@ class EvalNode(Node):
fn = "def fn(**args):\n" fn = "def fn(**args):\n"
run = "\noutput=fn(**args)\n" run = "\noutput=fn(**args)\n"
text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
if sys.version_info.major == 2:
exec(text) exec(text)
elif sys.version_info.major == 3:
ldict = locals()
exec(text, globals(), ldict)
output = ldict['output']
except: except:
print("Error processing node: %s" % self.name()) print("Error processing node: %s" % self.name())
raise raise

View File

@ -4,6 +4,7 @@ from ..python2_3 import asUnicode
import numpy as np import numpy as np
from ..Point import Point from ..Point import Point
from .. import debug as debug from .. import debug as debug
import sys
import weakref import weakref
from .. import functions as fn from .. import functions as fn
from .. import getConfigOption from .. import getConfigOption
@ -44,11 +45,8 @@ class AxisItem(GraphicsWidget):
GraphicsWidget.__init__(self, parent) GraphicsWidget.__init__(self, parent)
self.label = QtGui.QGraphicsTextItem(self) self.label = QtGui.QGraphicsTextItem(self)
self.picture = None self.picture = None
self.orientation = orientation self.orientation = None
if orientation not in ['left', 'right', 'top', 'bottom']: self.setOrientation(orientation)
raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.")
if orientation in ['left', 'right']:
self.label.rotate(-90)
self.style = { self.style = {
'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis 'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis
@ -110,6 +108,27 @@ class AxisItem(GraphicsWidget):
self.grid = False self.grid = False
#self.setCacheMode(self.DeviceCoordinateCache) #self.setCacheMode(self.DeviceCoordinateCache)
def setOrientation(self, orientation):
"""
orientation = 'left', 'right', 'top', 'bottom'
"""
if orientation != self.orientation:
if orientation not in ['left', 'right', 'top', 'bottom']:
raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.")
#rotate absolute allows to change orientation multiple times:
if orientation in ['left', 'right']:
self.label.setRotation(-90)
if self.orientation:
self._updateWidth()
self.setMaximumHeight(16777215)
else:
self.label.setRotation(0)
if self.orientation:
self._updateHeight()
self.setMaximumWidth(16777215)
self.orientation = orientation
def setStyle(self, **kwds): def setStyle(self, **kwds):
""" """
Set various style options. Set various style options.
@ -513,6 +532,7 @@ class AxisItem(GraphicsWidget):
self.unlinkFromView() self.unlinkFromView()
self._linkedView = weakref.ref(view) self._linkedView = weakref.ref(view)
if self.orientation in ['right', 'left']: if self.orientation in ['right', 'left']:
view.sigYRangeChanged.connect(self.linkedViewChanged) view.sigYRangeChanged.connect(self.linkedViewChanged)
else: else:
@ -813,7 +833,37 @@ class AxisItem(GraphicsWidget):
return strings return strings
def logTickStrings(self, values, scale, spacing): def logTickStrings(self, values, scale, spacing):
return ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)] estrings = ["%0.1g"%x for x in 10 ** np.array(values).astype(float) * np.array(scale)]
if sys.version_info < (3, 0):
# python 2 does not support unicode strings like that
return estrings
else: # python 3+
convdict = {"0": "",
"1": "¹",
"2": "²",
"3": "³",
"4": "",
"5": "",
"6": "",
"7": "",
"8": "",
"9": "",
}
dstrings = []
for e in estrings:
if e.count("e"):
v, p = e.split("e")
sign = "" if p[0] == "-" else ""
pot = "".join([convdict[pp] for pp in p[1:].lstrip("0")])
if v == "1":
v = ""
else:
v = v + "·"
dstrings.append(v + "10" + sign + pot)
else:
dstrings.append(e)
return dstrings
def generateDrawSpecs(self, p): def generateDrawSpecs(self, p):
""" """
@ -1110,23 +1160,26 @@ class AxisItem(GraphicsWidget):
self._updateHeight() self._updateHeight()
def wheelEvent(self, ev): def wheelEvent(self, ev):
if self.linkedView() is None: lv = self.linkedView()
if lv is None:
return return
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
self.linkedView().wheelEvent(ev, axis=1) lv.wheelEvent(ev, axis=1)
else: else:
self.linkedView().wheelEvent(ev, axis=0) lv.wheelEvent(ev, axis=0)
ev.accept() ev.accept()
def mouseDragEvent(self, event): def mouseDragEvent(self, event):
if self.linkedView() is None: lv = self.linkedView()
if lv is None:
return return
if self.orientation in ['left', 'right']: if self.orientation in ['left', 'right']:
return self.linkedView().mouseDragEvent(event, axis=1) return lv.mouseDragEvent(event, axis=1)
else: else:
return self.linkedView().mouseDragEvent(event, axis=0) return lv.mouseDragEvent(event, axis=0)
def mouseClickEvent(self, event): def mouseClickEvent(self, event):
if self.linkedView() is None: lv = self.linkedView()
if lv is None:
return return
return self.linkedView().mouseClickEvent(event) return lv.mouseClickEvent(event)

View File

@ -461,6 +461,19 @@ class GradientEditorItem(TickSliderItem):
self.addTick(1, QtGui.QColor(255,0,0), True) self.addTick(1, QtGui.QColor(255,0,0), True)
self.setColorMode('rgb') self.setColorMode('rgb')
self.updateGradient() self.updateGradient()
self.linkedGradients = {}
def showTicks(self, show=True):
for tick in self.ticks.keys():
if show:
tick.show()
orig = getattr(self, '_allowAdd_backup', None)
if orig:
self.allowAdd = orig
else:
self._allowAdd_backup = self.allowAdd
self.allowAdd = False #block tick creation
tick.hide()
def setOrientation(self, orientation): def setOrientation(self, orientation):
## public ## public
@ -764,7 +777,9 @@ class GradientEditorItem(TickSliderItem):
for t in self.ticks: for t in self.ticks:
c = t.color c = t.color
ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha())))
state = {'mode': self.colorMode, 'ticks': ticks} state = {'mode': self.colorMode,
'ticks': ticks,
'ticksVisible': next(iter(self.ticks)).isVisible()}
return state return state
def restoreState(self, state): def restoreState(self, state):
@ -789,6 +804,8 @@ class GradientEditorItem(TickSliderItem):
for t in state['ticks']: for t in state['ticks']:
c = QtGui.QColor(*t[1]) c = QtGui.QColor(*t[1])
self.addTick(t[0], c, finish=False) self.addTick(t[0], c, finish=False)
self.showTicks( state.get('ticksVisible',
next(iter(self.ticks)).isVisible()) )
self.updateGradient() self.updateGradient()
self.sigGradientChangeFinished.emit(self) self.sigGradientChangeFinished.emit(self)
@ -804,6 +821,18 @@ class GradientEditorItem(TickSliderItem):
self.updateGradient() self.updateGradient()
self.sigGradientChangeFinished.emit(self) self.sigGradientChangeFinished.emit(self)
def linkGradient(self, slaveGradient, connect=True):
if connect:
fn = lambda g, slave=slaveGradient:slave.restoreState(
g.saveState())
self.linkedGradients[id(slaveGradient)] = fn
self.sigGradientChanged.connect(fn)
self.sigGradientChanged.emit(self)
else:
fn = self.linkedGradients.get(id(slaveGradient), None)
if fn:
self.sigGradientChanged.disconnect(fn)
class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsObject instead results in class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsObject instead results in
## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86
@ -874,8 +903,8 @@ class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsO
self.view().tickMoveFinished(self) self.view().tickMoveFinished(self)
def mouseClickEvent(self, ev): def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton and self.moving:
ev.accept() ev.accept()
if ev.button() == QtCore.Qt.RightButton and self.moving:
self.setPos(self.startPosition) self.setPos(self.startPosition)
self.view().tickMoved(self, self.startPosition) self.view().tickMoved(self, self.startPosition)
self.moving = False self.moving = False
@ -883,7 +912,6 @@ class Tick(QtGui.QGraphicsWidget): ## NOTE: Making this a subclass of GraphicsO
self.sigMoved.emit(self) self.sigMoved.emit(self)
else: else:
self.view().tickClicked(self, ev) self.view().tickClicked(self, ev)
##remove
def hoverEvent(self, ev): def hoverEvent(self, ev):
if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):

View File

@ -19,6 +19,7 @@ class GraphicsItem(object):
The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task.
""" """
_pixelVectorGlobalCache = LRUCache(100, 70) _pixelVectorGlobalCache = LRUCache(100, 70)
_mapRectFromViewGlobalCache = LRUCache(100, 70)
def __init__(self, register=None): def __init__(self, register=None):
if not hasattr(self, '_qtBaseClass'): if not hasattr(self, '_qtBaseClass'):
@ -188,24 +189,23 @@ class GraphicsItem(object):
## (such as when looking at unix timestamps), we can get floating-point errors. ## (such as when looking at unix timestamps), we can get floating-point errors.
dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1)
if direction is None:
direction = QtCore.QPointF(1, 0)
elif direction.manhattanLength() == 0:
raise Exception("Cannot compute pixel length for 0-length vector.")
key = (dt.m11(), dt.m21(), dt.m12(), dt.m22(), direction.x(), direction.y())
## check local cache ## check local cache
if direction is None and dt == self._pixelVectorCache[0]: if key == self._pixelVectorCache[0]:
return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy*
## check global cache ## check global cache
#key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
key = (dt.m11(), dt.m21(), dt.m12(), dt.m22())
pv = self._pixelVectorGlobalCache.get(key, None) pv = self._pixelVectorGlobalCache.get(key, None)
if direction is None and pv is not None: if pv is not None:
self._pixelVectorCache = [dt, pv] self._pixelVectorCache = [key, pv]
return tuple(map(Point,pv)) ## return a *copy* return tuple(map(Point,pv)) ## return a *copy*
if direction is None:
direction = QtCore.QPointF(1, 0)
if direction.manhattanLength() == 0:
raise Exception("Cannot compute pixel length for 0-length vector.")
## attempt to re-scale direction vector to fit within the precision of the coordinate system ## attempt to re-scale direction vector to fit within the precision of the coordinate system
## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'.
## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen.
@ -368,8 +368,21 @@ class GraphicsItem(object):
vt = self.viewTransform() vt = self.viewTransform()
if vt is None: if vt is None:
return None return None
vt = fn.invertQTransform(vt)
return vt.mapRect(obj) cache = self._mapRectFromViewGlobalCache
k = (
vt.m11(), vt.m12(), vt.m13(),
vt.m21(), vt.m22(), vt.m23(),
vt.m31(), vt.m32(), vt.m33(),
)
try:
inv_vt = cache[k]
except KeyError:
inv_vt = fn.invertQTransform(vt)
cache[k] = inv_vt
return inv_vt.mapRect(obj)
def pos(self): def pos(self):
return Point(self._qtBaseClass.pos(self)) return Point(self._qtBaseClass.pos(self))

View File

@ -134,6 +134,9 @@ class GraphicsLayout(GraphicsWidget):
item.geometryChanged.connect(self._updateItemBorder) item.geometryChanged.connect(self._updateItemBorder)
self.layout.addItem(item, row, col, rowspan, colspan) self.layout.addItem(item, row, col, rowspan, colspan)
self.layout.activate() # Update layout, recalculating bounds.
# Allows some PyQtGraph features to also work without Qt event loop.
self.nextColumn() self.nextColumn()
def getItem(self, row, col): def getItem(self, row, col):

View File

@ -51,6 +51,7 @@ class ImageItem(GraphicsObject):
self.levels = None ## [min, max] or [[redMin, redMax], ...] self.levels = None ## [min, max] or [[redMin, redMax], ...]
self.lut = None self.lut = None
self.autoDownsample = False self.autoDownsample = False
self._lastDownsample = (1, 1)
self.axisOrder = getConfigOption('imageAxisOrder') self.axisOrder = getConfigOption('imageAxisOrder')
@ -551,6 +552,17 @@ class ImageItem(GraphicsObject):
def viewTransformChanged(self): def viewTransformChanged(self):
if self.autoDownsample: if self.autoDownsample:
o = self.mapToDevice(QtCore.QPointF(0,0))
x = self.mapToDevice(QtCore.QPointF(1,0))
y = self.mapToDevice(QtCore.QPointF(0,1))
w = Point(x-o).length()
h = Point(y-o).length()
if w == 0 or h == 0:
self.qimage = None
return
xds = max(1, int(1.0 / w))
yds = max(1, int(1.0 / h))
if (xds, yds) != self._lastDownsample:
self.qimage = None self.qimage = None
self.update() self.update()

View File

@ -153,9 +153,9 @@ class PlotItem(GraphicsWidget):
self.legend = None self.legend = None
# Initialize axis items ## Create and place axis items
self.axes = {} self.axes = {}
self.setAxisItems(axisItems) self.setAxes(axisItems)
self.titleLabel = LabelItem('', size='11pt', parent=self) self.titleLabel = LabelItem('', size='11pt', parent=self)
self.layout.addItem(self.titleLabel, 0, 1) self.layout.addItem(self.titleLabel, 0, 1)
@ -260,6 +260,43 @@ class PlotItem(GraphicsWidget):
if len(kargs) > 0: if len(kargs) > 0:
self.plot(**kargs) self.plot(**kargs)
def setAxes(self, axisItems):
"""
Create and place axis items
For valid values for axisItems see __init__
"""
for v in self.axes.values():
item = v['item']
self.layout.removeItem(item)
self.vb.removeItem(item)
self.axes = {}
if axisItems is None:
axisItems = {}
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
axis = axisItems.get(k, None)
if axis:
axis.setOrientation(k)
else:
axis = 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)
#show/hide axes:
all_dir = ['left', 'bottom', 'right', 'top']
if axisItems:
to_show = list(axisItems.keys())
to_hide = [a for a in all_dir if a not in to_show]
else:
to_show = all_dir[:2]
to_hide = all_dir[2:]
for a in to_hide:
self.hideAxis(a)
for a in to_show:
self.showAxis(a)
def implements(self, interface=None): def implements(self, interface=None):
return interface in ['ViewBoxWrapper'] return interface in ['ViewBoxWrapper']
@ -1123,8 +1160,8 @@ class PlotItem(GraphicsWidget):
Show or hide one of the plot's axes. Show or hide one of the plot's axes.
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.getAxis(axis)
p = self.axes[axis]['pos'] #p = self.axes[axis]['pos']
if show: if show:
s.show() s.show()
else: else:

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<widget class="QGroupBox" name="averageGroup"> <widget class="QGroupBox" name="averageGroup">
<property name="geometry"> <property name="geometry">

View File

@ -148,7 +148,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None)) self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None))
self.averageGroup.setTitle(_translate("Form", "Average", None)) self.averageGroup.setTitle(_translate("Form", "Average", None))
self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None)) self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None))

View File

@ -135,7 +135,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available)."))
self.averageGroup.setTitle(_translate("Form", "Average")) self.averageGroup.setTitle(_translate("Form", "Average"))
self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced."))

View File

@ -134,7 +134,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8))
self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -15,6 +15,7 @@ from ..pgcollections import OrderedDict
from .. import debug from .. import debug
from ..python2_3 import basestring from ..python2_3 import basestring
__all__ = ['ScatterPlotItem', 'SpotItem'] __all__ = ['ScatterPlotItem', 'SpotItem']
@ -128,8 +129,12 @@ class SymbolAtlas(object):
sourceRecti = None sourceRecti = None
symbol_map = self.symbolMap symbol_map = self.symbolMap
for i, rec in enumerate(opts.tolist()): symbols = opts['symbol'].tolist()
size, symbol, pen, brush = rec[2: 6] sizes = opts['size'].tolist()
pens = opts['pen'].tolist()
brushes = opts['brush'].tolist()
for symbol, size, pen, brush in zip(symbols, sizes, pens, brushes):
key = id(symbol), size, id(pen), id(brush) key = id(symbol), size, id(pen), id(brush)
if key == keyi: if key == keyi:
@ -560,6 +565,7 @@ class ScatterPlotItem(GraphicsObject):
self.invalidate() self.invalidate()
def updateSpots(self, dataSet=None): def updateSpots(self, dataSet=None):
if dataSet is None: if dataSet is None:
dataSet = self.data dataSet = self.data
@ -610,8 +616,6 @@ class ScatterPlotItem(GraphicsObject):
recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush'])
return recs return recs
def measureSpotSizes(self, dataSet): def measureSpotSizes(self, dataSet):
for rec in dataSet: for rec in dataSet:
## keep track of the maximum spot size and pixel size ## keep track of the maximum spot size and pixel size
@ -630,7 +634,6 @@ class ScatterPlotItem(GraphicsObject):
self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth)
self.bounds = [None, None] self.bounds = [None, None]
def clear(self): def clear(self):
"""Remove all spots from the scatter plot""" """Remove all spots from the scatter plot"""
#self.clearItems() #self.clearItems()
@ -757,8 +760,10 @@ class ScatterPlotItem(GraphicsObject):
if self.opts['pxMode'] is True: if self.opts['pxMode'] is True:
p.resetTransform() p.resetTransform()
data = self.data
# Map point coordinates to device # Map point coordinates to device
pts = np.vstack([self.data['x'], self.data['y']]) pts = np.vstack([data['x'], data['y']])
pts = self.mapPointsToDevice(pts) pts = self.mapPointsToDevice(pts)
if pts is None: if pts is None:
return return
@ -770,25 +775,31 @@ class ScatterPlotItem(GraphicsObject):
# Draw symbols from pre-rendered atlas # Draw symbols from pre-rendered atlas
atlas = self.fragmentAtlas.getAtlas() atlas = self.fragmentAtlas.getAtlas()
target_rect = data['targetRect']
source_rect = data['sourceRect']
widths = data['width']
# Update targetRects if necessary # Update targetRects if necessary
updateMask = viewMask & np.equal(self.data['targetRect'], None) updateMask = viewMask & np.equal(target_rect, None)
if np.any(updateMask): if np.any(updateMask):
updatePts = pts[:,updateMask] updatePts = pts[:,updateMask]
width = self.data[updateMask]['width']*2 width = widths[updateMask] * 2
self.data['targetRect'][updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width)) target_rect[updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width))
data = self.data[viewMask]
if QT_LIB == 'PyQt4': if QT_LIB == 'PyQt4':
p.drawPixmapFragments(data['targetRect'].tolist(), data['sourceRect'].tolist(), atlas) p.drawPixmapFragments(
target_rect[viewMask].tolist(),
source_rect[viewMask].tolist(),
atlas
)
else: else:
list(imap(p.drawPixmap, data['targetRect'], repeat(atlas), data['sourceRect'])) list(imap(p.drawPixmap, target_rect[viewMask].tolist(), repeat(atlas), source_rect[viewMask].tolist()))
else: else:
# render each symbol individually # render each symbol individually
p.setRenderHint(p.Antialiasing, aa) p.setRenderHint(p.Antialiasing, aa)
data = self.data[viewMask]
pts = pts[:,viewMask] pts = pts[:,viewMask]
for i, rec in enumerate(data): for i, rec in enumerate(data[viewMask]):
p.resetTransform() p.resetTransform()
p.translate(pts[0,i] + rec['width']/2, pts[1,i] + rec['width']/2) p.translate(pts[0,i] + rec['width']/2, pts[1,i] + rec['width']/2)
drawSymbol(p, *self.getSpotOpts(rec, scale)) drawSymbol(p, *self.getSpotOpts(rec, scale))

View File

@ -233,6 +233,17 @@ class ViewBox(GraphicsWidget):
if name is None: if name is None:
self.updateViewLists() self.updateViewLists()
def getAspectRatio(self):
'''return the current aspect ratio'''
rect = self.rect()
vr = self.viewRect()
if rect.height() == 0 or vr.width() == 0 or vr.height() == 0:
currentRatio = 1.0
else:
currentRatio = (rect.width()/float(rect.height())) / (
vr.width()/vr.height())
return currentRatio
def register(self, name): def register(self, name):
""" """
Add this ViewBox to the registered list of views. Add this ViewBox to the registered list of views.
@ -537,6 +548,10 @@ class ViewBox(GraphicsWidget):
self.enableAutoRange(x=xOff, y=yOff) self.enableAutoRange(x=xOff, y=yOff)
changed.append(True) changed.append(True)
limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]]
maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]]
for ax, range in changes.items(): for ax, range in changes.items():
mn = min(range) mn = min(range)
mx = max(range) mx = max(range)
@ -564,6 +579,39 @@ class ViewBox(GraphicsWidget):
mn -= p mn -= p
mx += p mx += p
# max range cannot be larger than bounds, if they are given
if limits[ax][0] is not None and limits[ax][1] is not None:
if maxRng[ax] is not None:
maxRng[ax] = min(maxRng[ax], limits[ax][1] - limits[ax][0])
else:
maxRng[ax] = limits[ax][1] - limits[ax][0]
# If we have limits, we will have at least a max range as well
if maxRng[ax] is not None or minRng[ax] is not None:
diff = mx - mn
if maxRng[ax] is not None and diff > maxRng[ax]:
delta = maxRng[ax] - diff
elif minRng[ax] is not None and diff < minRng[ax]:
delta = minRng[ax] - diff
else:
delta = 0
mn -= delta / 2.
mx += delta / 2.
# Make sure our requested area is within limits, if any
if limits[ax][0] is not None or limits[ax][1] is not None:
lmn, lmx = limits[ax]
if lmn is not None and mn < lmn:
delta = lmn - mn # Shift the requested view to match our lower limit
mn = lmn
mx += delta
elif lmx is not None and mx > lmx:
delta = lmx - mx
mx = lmx
mn += delta
# Set target range # Set target range
if self.state['targetRange'][ax] != [mn, mx]: if self.state['targetRange'][ax] != [mn, mx]:
self.state['targetRange'][ax] = [mn, mx] self.state['targetRange'][ax] = [mn, mx]
@ -1097,12 +1145,7 @@ class ViewBox(GraphicsWidget):
return return
self.state['aspectLocked'] = False self.state['aspectLocked'] = False
else: else:
rect = self.rect() currentRatio = self.getAspectRatio()
vr = self.viewRect()
if rect.height() == 0 or vr.width() == 0 or vr.height() == 0:
currentRatio = 1.0
else:
currentRatio = (rect.width()/float(rect.height())) / (vr.width()/vr.height())
if ratio is None: if ratio is None:
ratio = currentRatio ratio = currentRatio
if self.state['aspectLocked'] == ratio: # nothing to change if self.state['aspectLocked'] == ratio: # nothing to change
@ -1443,40 +1486,6 @@ class ViewBox(GraphicsWidget):
aspect = self.state['aspectLocked'] # size ratio / view ratio aspect = self.state['aspectLocked'] # size ratio / view ratio
tr = self.targetRect() tr = self.targetRect()
bounds = self.rect() bounds = self.rect()
if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]:
## This is the view range aspect ratio we have requested
targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1
## This is the view range aspect ratio we need to obey aspect constraint
viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect
viewRatio = 1 if viewRatio == 0 else viewRatio
# Decide which range to keep unchanged
#print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange']
if forceX:
ax = 0
elif forceY:
ax = 1
else:
# if we are not required to keep a particular axis unchanged,
# then make the entire target range visible
ax = 0 if targetRatio > viewRatio else 1
if ax == 0:
## view range needs to be taller than target
dy = 0.5 * (tr.width() / viewRatio - tr.height())
if dy != 0:
changed[1] = True
viewRange[1] = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
else:
## view range needs to be wider than target
dx = 0.5 * (tr.height() * viewRatio - tr.width())
if dx != 0:
changed[0] = True
viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx]
# ----------- Make corrections for view limits -----------
limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits']) limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]]
@ -1493,39 +1502,54 @@ class ViewBox(GraphicsWidget):
else: else:
maxRng[axis] = limits[axis][1] - limits[axis][0] maxRng[axis] = limits[axis][1] - limits[axis][0]
#print "\nLimits for axis %d: range=%s min=%s max=%s" % (axis, limits[axis], minRng[axis], maxRng[axis]) if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]:
#print "Starting range:", viewRange[axis]
# Apply xRange, yRange ## This is the view range aspect ratio we have requested
diff = viewRange[axis][1] - viewRange[axis][0] targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1
if maxRng[axis] is not None and diff > maxRng[axis]: ## This is the view range aspect ratio we need to obey aspect constraint
delta = maxRng[axis] - diff viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect
changed[axis] = True viewRatio = 1 if viewRatio == 0 else viewRatio
elif minRng[axis] is not None and diff < minRng[axis]:
delta = minRng[axis] - diff # Calculate both the x and y ranges that would be needed to obtain the desired aspect ratio
changed[axis] = True dy = 0.5 * (tr.width() / viewRatio - tr.height())
dx = 0.5 * (tr.height() * viewRatio - tr.width())
rangeY = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy]
rangeX = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx]
canidateRange = [rangeX, rangeY]
# Decide which range to try to keep unchanged
#print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange']
if forceX:
ax = 0
elif forceY:
ax = 1
else: else:
delta = 0 # if we are not required to keep a particular axis unchanged,
# then try to make the entire target range visible
ax = 0 if targetRatio > viewRatio else 1
target = 0 if ax == 1 else 1
# See if this choice would cause out-of-range issues
if maxRng is not None or minRng is not None:
diff = canidateRange[target][1] - canidateRange[target][0]
if maxRng[target] is not None and diff > maxRng[target] or \
minRng[target] is not None and diff < minRng[target]:
# tweak the target range down so we can still pan properly
self.state['targetRange'][ax] = canidateRange[ax]
ax = target # Switch the "fixed" axes
viewRange[axis][0] -= delta/2. if ax == 0:
viewRange[axis][1] += delta/2. ## view range needs to be taller than target
if dy != 0:
changed[1] = True
viewRange[1] = rangeY
else:
## view range needs to be wider than target
if dx != 0:
changed[0] = True
viewRange[0] = rangeX
#print "after applying min/max:", viewRange[axis]
# Apply xLimits, yLimits
mn, mx = limits[axis]
if mn is not None and viewRange[axis][0] < mn:
delta = mn - viewRange[axis][0]
viewRange[axis][0] += delta
viewRange[axis][1] += delta
changed[axis] = True
elif mx is not None and viewRange[axis][1] > mx:
delta = mx - viewRange[axis][1]
viewRange[axis][0] += delta
viewRange[axis][1] += delta
changed[axis] = True
#print "after applying edge limits:", viewRange[axis]
changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)]
self.state['viewRange'] = viewRange self.state['viewRange'] = viewRange

View File

@ -17,7 +17,7 @@
</size> </size>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="margin"> <property name="margin">

View File

@ -78,7 +78,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.label.setText(_translate("Form", "Link Axis:", None)) self.label.setText(_translate("Form", "Link Axis:", None))
self.linkCombo.setToolTip(_translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>", None)) self.linkCombo.setToolTip(_translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>", None))
self.autoPercentSpin.setToolTip(_translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>", None)) self.autoPercentSpin.setToolTip(_translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>", None))

View File

@ -65,7 +65,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.label.setText(_translate("Form", "Link Axis:")) self.label.setText(_translate("Form", "Link Axis:"))
self.linkCombo.setToolTip(_translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>")) self.linkCombo.setToolTip(_translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>"))
self.autoPercentSpin.setToolTip(_translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>")) self.autoPercentSpin.setToolTip(_translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>"))

View File

@ -64,7 +64,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8))
self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8)) self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))
self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8)) self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "<html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html>", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
import pyqtgraph as pg
import pytest
app = pg.mkQApp()
def test_zoom_normal():
vb = pg.ViewBox()
testRange = pg.QtCore.QRect(0, 0, 10, 20)
vb.setRange(testRange, padding=0)
vbViewRange = vb.getState()['viewRange']
assert vbViewRange == [[testRange.left(), testRange.right()],
[testRange.top(), testRange.bottom()]]
def test_zoom_limit():
"""Test zooming with X and Y limits set"""
vb = pg.ViewBox()
vb.setLimits(xMin=0, xMax=10, yMin=0, yMax=10)
# Try zooming within limits. Should return unmodified
testRange = pg.QtCore.QRect(0, 0, 9, 9)
vb.setRange(testRange, padding=0)
vbViewRange = vb.getState()['viewRange']
assert vbViewRange == [[testRange.left(), testRange.right()],
[testRange.top(), testRange.bottom()]]
# And outside limits. both view range and targetRange should be set to limits
testRange = pg.QtCore.QRect(-5, -5, 16, 20)
vb.setRange(testRange, padding=0)
expected = [[0, 10], [0, 10]]
vbState = vb.getState()
assert vbState['targetRange'] == expected
assert vbState['viewRange'] == expected
def test_zoom_range_limit():
"""Test zooming with XRange and YRange limits set, but no X and Y limits"""
vb = pg.ViewBox()
vb.setLimits(minXRange=5, maxXRange=10, minYRange=5, maxYRange=10)
# Try something within limits
testRange = pg.QtCore.QRect(-15, -15, 7, 7)
vb.setRange(testRange, padding=0)
expected = [[testRange.left(), testRange.right()],
[testRange.top(), testRange.bottom()]]
vbViewRange = vb.getState()['viewRange']
assert vbViewRange == expected
# and outside limits
testRange = pg.QtCore.QRect(-15, -15, 17, 17)
# Code should center the required width reduction, so move each side by 3
expected = [[testRange.left() + 3, testRange.right() - 3],
[testRange.top() + 3, testRange.bottom() - 3]]
vb.setRange(testRange, padding=0)
vbViewRange = vb.getState()['viewRange']
vbTargetRange = vb.getState()['targetRange']
assert vbViewRange == expected
assert vbTargetRange == expected
def test_zoom_ratio():
"""Test zooming with a fixed aspect ratio set"""
vb = pg.ViewBox(lockAspect=1)
# Give the viewbox a size of the proper aspect ratio to keep things easy
vb.setFixedHeight(10)
vb.setFixedWidth(10)
# request a range with a good ratio
testRange = pg.QtCore.QRect(0, 0, 10, 10)
vb.setRange(testRange, padding=0)
expected = [[testRange.left(), testRange.right()],
[testRange.top(), testRange.bottom()]]
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# Assert that the width and height are equal, since we locked the aspect ratio at 1
assert viewWidth == viewHeight
# and for good measure, that it is the same as the test range
assert viewRange == expected
# Now try to set to something with a different aspect ratio
testRange = pg.QtCore.QRect(0, 0, 10, 20)
vb.setRange(testRange, padding=0)
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# Don't really care what we got here, as long as the width and height are the same
assert viewWidth == viewHeight
def test_zoom_ratio2():
"""Slightly more complicated zoom ratio test, where the view box shape does not match the ratio"""
vb = pg.ViewBox(lockAspect=1)
# twice as wide as tall
vb.setFixedHeight(10)
vb.setFixedWidth(20)
# more or less random requested range
testRange = pg.QtCore.QRect(0, 0, 10, 15)
vb.setRange(testRange, padding=0)
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# View width should be twice as wide as the height,
# since the viewbox is twice as wide as it is tall.
assert viewWidth == 2 * viewHeight
def test_zoom_ratio_with_limits1():
"""Test zoom with both ratio and limits set"""
vb = pg.ViewBox(lockAspect=1)
# twice as wide as tall
vb.setFixedHeight(10)
vb.setFixedWidth(20)
# set some limits
vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5)
# Try to zoom too tall
testRange = pg.QtCore.QRect(0, 0, 6, 10)
vb.setRange(testRange, padding=0)
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# Make sure our view is within limits and the proper aspect ratio
assert viewRange[0][0] >= -5
assert viewRange[0][1] <= 5
assert viewRange[1][0] >= -5
assert viewRange[1][1] <= 5
assert viewWidth == 2 * viewHeight
def test_zoom_ratio_with_limits2():
vb = pg.ViewBox(lockAspect=1)
# twice as wide as tall
vb.setFixedHeight(10)
vb.setFixedWidth(20)
# set some limits
vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5)
# Same thing, but out-of-range the other way
testRange = pg.QtCore.QRect(0, 0, 16, 6)
vb.setRange(testRange, padding=0)
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# Make sure our view is within limits and the proper aspect ratio
assert viewRange[0][0] >= -5
assert viewRange[0][1] <= 5
assert viewRange[1][0] >= -5
assert viewRange[1][1] <= 5
assert viewWidth == 2 * viewHeight
def test_zoom_ratio_with_limits_out_of_range():
vb = pg.ViewBox(lockAspect=1)
# twice as wide as tall
vb.setFixedHeight(10)
vb.setFixedWidth(20)
# set some limits
vb.setLimits(xMin=-5, xMax=5, yMin=-5, yMax=5)
# Request something completely out-of-range and out-of-aspect
testRange = pg.QtCore.QRect(10, 10, 25, 100)
vb.setRange(testRange, padding=0)
viewRange = vb.getState()['viewRange']
viewWidth = viewRange[0][1] - viewRange[0][0]
viewHeight = viewRange[1][1] - viewRange[1][0]
# Make sure our view is within limits and the proper aspect ratio
assert viewRange[0][0] >= -5
assert viewRange[0][1] <= 5
assert viewRange[1][0] >= -5
assert viewRange[1][1] <= 5
assert viewWidth == 2 * viewHeight
if __name__ == "__main__":
setup_module(None)
test_zoom_ratio()

View File

@ -48,38 +48,39 @@ class TabWindow(QtGui.QMainWindow):
class PlotWindow(PlotWidget): class PlotWindow(PlotWidget):
sigClosed = QtCore.Signal(object)
""" """
(deprecated; use :class:`~pyqtgraph.PlotWidget` instead) (deprecated; use :class:`~pyqtgraph.PlotWidget` instead)
""" """
def __init__(self, title=None, **kargs): def __init__(self, title=None, **kargs):
mkQApp() mkQApp()
self.win = QtGui.QMainWindow()
PlotWidget.__init__(self, **kargs) PlotWidget.__init__(self, **kargs)
self.win.setCentralWidget(self)
for m in ['resize']:
setattr(self, m, getattr(self.win, m))
if title is not None: if title is not None:
self.win.setWindowTitle(title) self.setWindowTitle(title)
self.win.show() self.show()
def closeEvent(self, event):
PlotWidget.closeEvent(self, event)
self.sigClosed.emit(self)
class ImageWindow(ImageView): class ImageWindow(ImageView):
sigClosed = QtCore.Signal(object)
""" """
(deprecated; use :class:`~pyqtgraph.ImageView` instead) (deprecated; use :class:`~pyqtgraph.ImageView` instead)
""" """
def __init__(self, *args, **kargs): def __init__(self, *args, **kargs):
mkQApp() mkQApp()
self.win = QtGui.QMainWindow() ImageView.__init__(self)
self.win.resize(800,600)
if 'title' in kargs: if 'title' in kargs:
self.win.setWindowTitle(kargs['title']) self.setWindowTitle(kargs['title'])
del kargs['title'] del kargs['title']
ImageView.__init__(self, self.win)
if len(args) > 0 or len(kargs) > 0: if len(args) > 0 or len(kargs) > 0:
self.setImage(*args, **kargs) self.setImage(*args, **kargs)
self.win.setCentralWidget(self) self.show()
for m in ['resize']:
setattr(self, m, getattr(self.win, m)) def closeEvent(self, event):
#for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']: ImageView.closeEvent(self, event)
#setattr(self, m, getattr(self.cw, m)) self.sigClosed.emit(self)
self.win.show()

View File

@ -411,11 +411,9 @@ class ImageView(QtGui.QWidget):
def close(self): def close(self):
"""Closes the widget nicely, making sure to clear the graphics scene and release memory.""" """Closes the widget nicely, making sure to clear the graphics scene and release memory."""
self.ui.roiPlot.close() self.clear()
self.ui.graphicsView.close() self.imageDisp = None
self.scene.clear() self.imageItem.setParent(None)
del self.image
del self.imageDisp
super(ImageView, self).close() super(ImageView, self).close()
self.setParent(None) self.setParent(None)

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<property name="margin"> <property name="margin">

View File

@ -146,7 +146,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(_translate("Form", "Form", None)) Form.setWindowTitle(_translate("Form", "PyQtGraph", None))
self.roiBtn.setText(_translate("Form", "ROI", None)) self.roiBtn.setText(_translate("Form", "ROI", None))
self.menuBtn.setText(_translate("Form", "Menu", None)) self.menuBtn.setText(_translate("Form", "Menu", None))
self.normGroup.setTitle(_translate("Form", "Normalization", None)) self.normGroup.setTitle(_translate("Form", "Normalization", None))

View File

@ -134,7 +134,7 @@ class Ui_Form(object):
def retranslateUi(self, Form): def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate _translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form")) Form.setWindowTitle(_translate("Form", "PyQtGraph"))
self.roiBtn.setText(_translate("Form", "ROI")) self.roiBtn.setText(_translate("Form", "ROI"))
self.menuBtn.setText(_translate("Form", "Norm")) self.menuBtn.setText(_translate("Form", "Norm"))
self.normGroup.setTitle(_translate("Form", "Normalization")) self.normGroup.setTitle(_translate("Form", "Normalization"))

View File

@ -132,7 +132,7 @@ class Ui_Form(object):
QtCore.QMetaObject.connectSlotsByName(Form) QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form): def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) Form.setWindowTitle(QtGui.QApplication.translate("Form", "PyQtGraph", None, QtGui.QApplication.UnicodeUTF8))
self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
self.menuBtn.setText(QtGui.QApplication.translate("Form", "Menu", None, QtGui.QApplication.UnicodeUTF8)) self.menuBtn.setText(QtGui.QApplication.translate("Form", "Menu", None, QtGui.QApplication.UnicodeUTF8))
self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -55,6 +55,7 @@ class Parameter(QtCore.QObject):
sigDefaultChanged(self, default) Emitted when this parameter's default value has changed sigDefaultChanged(self, default) Emitted when this parameter's default value has changed
sigNameChanged(self, name) Emitted when this parameter's name has changed sigNameChanged(self, name) Emitted when this parameter's name has changed
sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed
sigContextMenu(self, name) Emitted when a context menu was clicked
=================================== ========================================================= =================================== =========================================================
""" """
## name, type, limits, etc. ## name, type, limits, etc.
@ -81,6 +82,7 @@ class Parameter(QtCore.QObject):
## (but only if monitorChildren() is called) ## (but only if monitorChildren() is called)
sigTreeStateChanged = QtCore.Signal(object, object) # self, changes sigTreeStateChanged = QtCore.Signal(object, object) # self, changes
# changes = [(param, change, info), ...] # changes = [(param, change, info), ...]
sigContextMenu = QtCore.Signal(object, object) # self, name
# bad planning. # bad planning.
#def __new__(cls, *args, **opts): #def __new__(cls, *args, **opts):
@ -135,9 +137,12 @@ class Parameter(QtCore.QObject):
(default=False) (default=False)
removable If True, the user may remove this Parameter. removable If True, the user may remove this Parameter.
(default=False) (default=False)
expanded If True, the Parameter will appear expanded when expanded If True, the Parameter will initially be expanded in
displayed in a ParameterTree (its children will be ParameterTrees: Its children will be visible.
visible). (default=True) (default=True)
syncExpanded If True, the `expanded` state of this Parameter is
synchronized with all ParameterTrees it is displayed in.
(default=False)
title (str or None) If specified, then the parameter will be title (str or None) If specified, then the parameter will be
displayed to the user using this string as its name. displayed to the user using this string as its name.
However, the parameter will still be referred to However, the parameter will still be referred to
@ -159,6 +164,7 @@ class Parameter(QtCore.QObject):
'removable': False, 'removable': False,
'strictNaming': False, # forces name to be usable as a python variable 'strictNaming': False, # forces name to be usable as a python variable
'expanded': True, 'expanded': True,
'syncExpanded': False,
'title': None, 'title': None,
#'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits.
} }
@ -199,6 +205,8 @@ class Parameter(QtCore.QObject):
self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data)) self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data))
self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data)) self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data))
self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data)) self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data))
self.sigContextMenu.connect(lambda param, data: self.emitStateChanged('contextMenu', data))
#self.watchParam(self) ## emit treechange signals if our own state changes #self.watchParam(self) ## emit treechange signals if our own state changes
@ -206,6 +214,10 @@ class Parameter(QtCore.QObject):
"""Return the name of this Parameter.""" """Return the name of this Parameter."""
return self.opts['name'] return self.opts['name']
def contextMenu(self, name):
""""A context menu entry was clicked"""
self.sigContextMenu.emit(self, name)
def setName(self, name): def setName(self, name):
"""Attempt to change the name of this parameter; return the actual name. """Attempt to change the name of this parameter; return the actual name.
(The parameter may reject the name change or automatically pick a different name)""" (The parameter may reject the name change or automatically pick a different name)"""
@ -453,7 +465,7 @@ class Parameter(QtCore.QObject):
Set any arbitrary options on this parameter. Set any arbitrary options on this parameter.
The exact behavior of this function will depend on the parameter type, but The exact behavior of this function will depend on the parameter type, but
most parameters will accept a common set of options: value, name, limits, most parameters will accept a common set of options: value, name, limits,
default, readonly, removable, renamable, visible, enabled, and expanded. default, readonly, removable, renamable, visible, enabled, expanded and syncExpanded.
See :func:`Parameter.__init__ <pyqtgraph.parametertree.Parameter.__init__>` See :func:`Parameter.__init__ <pyqtgraph.parametertree.Parameter.__init__>`
for more information on default options. for more information on default options.

View File

@ -34,19 +34,20 @@ class ParameterItem(QtGui.QTreeWidgetItem):
param.sigOptionsChanged.connect(self.optsChanged) param.sigOptionsChanged.connect(self.optsChanged)
param.sigParentChanged.connect(self.parentChanged) param.sigParentChanged.connect(self.parentChanged)
opts = param.opts self.updateFlags()
## flag used internally during name editing
self.ignoreNameColumnChange = False
def updateFlags(self):
## called when Parameter opts changed
opts = self.param.opts
## Generate context menu for renaming/removing parameter
self.contextMenu = QtGui.QMenu()
self.contextMenu.addSeparator()
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
if opts.get('renamable', False): if opts.get('renamable', False):
if param.opts.get('title', None) is not None: if opts.get('title', None) is not None:
raise Exception("Cannot make parameter with both title != None and renamable == True.") raise Exception("Cannot make parameter with both title != None and renamable == True.")
flags |= QtCore.Qt.ItemIsEditable flags |= QtCore.Qt.ItemIsEditable
self.contextMenu.addAction('Rename').triggered.connect(self.editName)
if opts.get('removable', False):
self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove)
## handle movable / dropEnabled options ## handle movable / dropEnabled options
if opts.get('movable', False): if opts.get('movable', False):
@ -55,9 +56,6 @@ class ParameterItem(QtGui.QTreeWidgetItem):
flags |= QtCore.Qt.ItemIsDropEnabled flags |= QtCore.Qt.ItemIsDropEnabled
self.setFlags(flags) self.setFlags(flags)
## flag used internally during name editing
self.ignoreNameColumnChange = False
def valueChanged(self, param, val): def valueChanged(self, param, val):
## called when the parameter's value has changed ## called when the parameter's value has changed
@ -106,9 +104,29 @@ class ParameterItem(QtGui.QTreeWidgetItem):
pass pass
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False)\
and "context" not in self.param.opts:
return return
## Generate context menu for renaming/removing parameter
self.contextMenu = QtGui.QMenu() # Put in global name space to prevent garbage collection
self.contextMenu.addSeparator()
if self.param.opts.get('renamable', False):
self.contextMenu.addAction('Rename').triggered.connect(self.editName)
if self.param.opts.get('removable', False):
self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove)
# context menu
context = opts.get('context', None)
if isinstance(context, list):
for name in context:
self.contextMenu.addAction(name).triggered.connect(
self.contextMenuTriggered(name))
elif isinstance(context, dict):
for name, title in context.items():
self.contextMenu.addAction(title).triggered.connect(
self.contextMenuTriggered(name))
self.contextMenu.popup(ev.globalPos()) self.contextMenu.popup(ev.globalPos())
def columnChangedEvent(self, col): def columnChangedEvent(self, col):
@ -130,6 +148,10 @@ class ParameterItem(QtGui.QTreeWidgetItem):
finally: finally:
self.ignoreNameColumnChange = False self.ignoreNameColumnChange = False
def expandedChangedEvent(self, expanded):
if self.param.opts['syncExpanded']:
self.param.setOpts(expanded=expanded)
def nameChanged(self, param, name): def nameChanged(self, param, name):
## called when the parameter's name has changed. ## called when the parameter's name has changed.
if self.param.opts.get('title', None) is None: if self.param.opts.get('title', None) is None:
@ -146,10 +168,27 @@ class ParameterItem(QtGui.QTreeWidgetItem):
def optsChanged(self, param, opts): def optsChanged(self, param, opts):
"""Called when any options are changed that are not """Called when any options are changed that are not
name, value, default, or limits""" name, value, default, or limits"""
#print opts
if 'visible' in opts: if 'visible' in opts:
self.setHidden(not opts['visible']) self.setHidden(not opts['visible'])
if 'expanded' in opts:
if self.param.opts['syncExpanded']:
if self.isExpanded() != opts['expanded']:
self.setExpanded(opts['expanded'])
if 'syncExpanded' in opts:
if opts['syncExpanded']:
if self.isExpanded() != self.param.opts['expanded']:
self.setExpanded(self.param.opts['expanded'])
self.updateFlags()
def contextMenuTriggered(self, name):
def trigger():
self.param.contextMenu(name)
return trigger
def editName(self): def editName(self):
self.treeWidget().editItem(self, 0) self.treeWidget().editItem(self, 0)

View File

@ -28,6 +28,8 @@ class ParameterTree(TreeWidget):
self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents)
self.setHeaderHidden(not showHeader) self.setHeaderHidden(not showHeader)
self.itemChanged.connect(self.itemChangedEvent) self.itemChanged.connect(self.itemChangedEvent)
self.itemExpanded.connect(self.itemExpandedEvent)
self.itemCollapsed.connect(self.itemCollapsedEvent)
self.lastSel = None self.lastSel = None
self.setRootIsDecorated(False) self.setRootIsDecorated(False)
@ -135,6 +137,14 @@ class ParameterTree(TreeWidget):
if hasattr(item, 'columnChangedEvent'): if hasattr(item, 'columnChangedEvent'):
item.columnChangedEvent(col) item.columnChangedEvent(col)
def itemExpandedEvent(self, item):
if hasattr(item, 'expandedChangedEvent'):
item.expandedChangedEvent(True)
def itemCollapsedEvent(self, item):
if hasattr(item, 'expandedChangedEvent'):
item.expandedChangedEvent(False)
def selectionChanged(self, *args): def selectionChanged(self, *args):
sel = self.selectedItems() sel = self.selectedItems()
if len(sel) != 1: if len(sel) != 1:

View File

@ -44,10 +44,6 @@ class WidgetParameterItem(ParameterItem):
self.widget = w self.widget = w
self.eventProxy = EventProxy(w, self.widgetEventFilter) self.eventProxy = EventProxy(w, self.widgetEventFilter)
opts = self.param.opts
if 'tip' in opts:
w.setToolTip(opts['tip'])
self.defaultBtn = QtGui.QPushButton() self.defaultBtn = QtGui.QPushButton()
self.defaultBtn.setFixedWidth(20) self.defaultBtn.setFixedWidth(20)
self.defaultBtn.setFixedHeight(20) self.defaultBtn.setFixedHeight(20)
@ -73,6 +69,7 @@ class WidgetParameterItem(ParameterItem):
w.sigChanging.connect(self.widgetValueChanging) w.sigChanging.connect(self.widgetValueChanging)
## update value shown in widget. ## update value shown in widget.
opts = self.param.opts
if opts.get('value', None) is not None: if opts.get('value', None) is not None:
self.valueChanged(self, opts['value'], force=True) self.valueChanged(self, opts['value'], force=True)
else: else:
@ -81,6 +78,8 @@ class WidgetParameterItem(ParameterItem):
self.updateDefaultBtn() self.updateDefaultBtn()
self.optsChanged(self.param, self.param.opts)
def makeWidget(self): def makeWidget(self):
""" """
Return a single widget that should be placed in the second tree column. Return a single widget that should be placed in the second tree column.
@ -280,6 +279,9 @@ class WidgetParameterItem(ParameterItem):
if isinstance(self.widget, (QtGui.QCheckBox,ColorButton)): if isinstance(self.widget, (QtGui.QCheckBox,ColorButton)):
self.widget.setEnabled(not opts['readonly']) self.widget.setEnabled(not opts['readonly'])
if 'tip' in opts:
self.widget.setToolTip(opts['tip'])
## If widget is a SpinBox, pass options straight through ## If widget is a SpinBox, pass options straight through
if isinstance(self.widget, SpinBox): if isinstance(self.widget, SpinBox):
# send only options supported by spinbox # send only options supported by spinbox
@ -426,10 +428,13 @@ class GroupParameterItem(ParameterItem):
def treeWidgetChanged(self): def treeWidgetChanged(self):
ParameterItem.treeWidgetChanged(self) ParameterItem.treeWidgetChanged(self)
self.treeWidget().setFirstItemColumnSpanned(self, True) tw = self.treeWidget()
if tw is None:
return
tw.setFirstItemColumnSpanned(self, True)
if self.addItem is not None: if self.addItem is not None:
self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox) tw.setItemWidget(self.addItem, 0, self.addWidgetBox)
self.treeWidget().setFirstItemColumnSpanned(self.addItem, True) tw.setFirstItemColumnSpanned(self.addItem, True)
def addChild(self, child): ## make sure added childs are actually inserted before add btn def addChild(self, child): ## make sure added childs are actually inserted before add btn
if self.addItem is not None: if self.addItem is not None:
@ -664,8 +669,12 @@ class TextParameterItem(WidgetParameterItem):
## TODO: fix so that superclass method can be called ## TODO: fix so that superclass method can be called
## (WidgetParameter should just natively support this style) ## (WidgetParameter should just natively support this style)
#WidgetParameterItem.treeWidgetChanged(self) #WidgetParameterItem.treeWidgetChanged(self)
self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) tw = self.treeWidget()
self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) if tw is None:
return
tw.setFirstItemColumnSpanned(self.subItem, True)
tw.setItemWidget(self.subItem, 0, self.textBox)
# for now, these are copied from ParameterItem.treeWidgetChanged # for now, these are copied from ParameterItem.treeWidgetChanged
self.setHidden(not self.param.opts.get('visible', True)) self.setHidden(not self.param.opts.get('visible', True))

View File

@ -67,7 +67,7 @@ def test_exit_crash():
os.remove(tmp) os.remove(tmp)
@pytest.mark.skipif(pg.Qt.QtVersion.startswith("5.9"), reason="Functionality not well supported, failing only on this config")
def test_pg_exit(): def test_pg_exit():
# test the pg.exit() function # test the pg.exit() function
code = textwrap.dedent(""" code = textwrap.dedent("""

View File

@ -11,7 +11,7 @@
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>PyQtGraph</string>
</property> </property>
<widget class="PlotWidget" name="widget" native="true"> <widget class="PlotWidget" name="widget" native="true">
<property name="geometry"> <property name="geometry">

View File

@ -44,7 +44,7 @@ class PlotWidget(GraphicsView):
For all For all
other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`. other methods, use :func:`getPlotItem <pyqtgraph.PlotWidget.getPlotItem>`.
""" """
def __init__(self, parent=None, background='default', **kargs): def __init__(self, parent=None, background='default', plotItem=None, **kargs):
"""When initializing PlotWidget, *parent* and *background* are passed to """When initializing PlotWidget, *parent* and *background* are passed to
:func:`GraphicsWidget.__init__() <pyqtgraph.GraphicsWidget.__init__>` :func:`GraphicsWidget.__init__() <pyqtgraph.GraphicsWidget.__init__>`
and all others are passed and all others are passed
@ -52,7 +52,10 @@ class PlotWidget(GraphicsView):
GraphicsView.__init__(self, parent, background=background) GraphicsView.__init__(self, parent, background=background)
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.enableMouse(False) self.enableMouse(False)
if plotItem is None:
self.plotItem = PlotItem(**kargs) self.plotItem = PlotItem(**kargs)
else:
self.plotItem = plotItem
self.setCentralItem(self.plotItem) self.setCentralItem(self.plotItem)
## Explicitly wrap methods from plotItem ## Explicitly wrap methods from plotItem
## NOTE: If you change this list, update the documentation above as well. ## NOTE: If you change this list, update the documentation above as well.

View File

@ -96,7 +96,7 @@ class ScatterPlotWidget(QtGui.QSplitter):
try: try:
self.fieldList.clearSelection() self.fieldList.clearSelection()
for f in fields: for f in fields:
i = self.fields.keys().index(f) i = list(self.fields.keys()).index(f)
item = self.fieldList.item(i) item = self.fieldList.item(i)
item.setSelected(True) item.setSelected(True)
finally: finally: