Merge branch 'develop' into pyside2-uic
This commit is contained in:
commit
47f06e78be
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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"))
|
||||||
|
@ -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))
|
||||||
|
@ -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"},
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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.
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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"))
|
||||||
|
@ -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))
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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:"))
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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])
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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'):
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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.."))
|
||||||
|
@ -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))
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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))
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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."))
|
||||||
|
@ -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))
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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>"))
|
||||||
|
@ -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))
|
||||||
|
200
pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBoxZoom.py
Normal file
200
pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBoxZoom.py
Normal 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()
|
@ -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()
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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))
|
||||||
|
@ -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"))
|
||||||
|
@ -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))
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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))
|
||||||
|
@ -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("""
|
||||||
|
@ -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">
|
||||||
|
@ -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.
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user