Merge branch 'develop' into derivativePlots_cleanup
This commit is contained in:
commit
14d5085636
49
.flake8
Normal file
49
.flake8
Normal file
@ -0,0 +1,49 @@
|
||||
[flake8]
|
||||
exclude = .git,.tox,__pycache__,doc,old,build,dist
|
||||
show_source = True
|
||||
statistics = True
|
||||
verbose = 2
|
||||
select =
|
||||
E101,
|
||||
E112,
|
||||
E122,
|
||||
E125,
|
||||
E133,
|
||||
E223,
|
||||
E224,
|
||||
E242,
|
||||
E273,
|
||||
E274,
|
||||
E901,
|
||||
E902,
|
||||
W191,
|
||||
W601,
|
||||
W602,
|
||||
W603,
|
||||
W604,
|
||||
E124,
|
||||
E231,
|
||||
E211,
|
||||
E261,
|
||||
E271,
|
||||
E272,
|
||||
E304,
|
||||
F401,
|
||||
F402,
|
||||
F403,
|
||||
F404,
|
||||
E501,
|
||||
E502,
|
||||
E702,
|
||||
E703,
|
||||
E711,
|
||||
E712,
|
||||
E721,
|
||||
F811,
|
||||
F812,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F841,
|
||||
W292
|
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- In the following, please describe your issue in detail! -->
|
||||
<!-- If some of the sections do not apply, just remove them. -->
|
||||
|
||||
### Short description
|
||||
<!-- This should summarize the issue. -->
|
||||
|
||||
### Code to reproduce
|
||||
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
|
||||
Ideally, this should be a full example someone else could run without additional setup. -->
|
||||
```python
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
```
|
||||
|
||||
### Expected behavior
|
||||
<!-- What should happen? -->
|
||||
|
||||
### Real behavior
|
||||
<!-- What happens? -->
|
||||
|
||||
```
|
||||
An error occurred?
|
||||
Post the full traceback inside these 'code fences'!
|
||||
```
|
||||
|
||||
### Tested environment(s)
|
||||
|
||||
* PyQtGraph version: <!-- output of pyqtgraph.__version__ -->
|
||||
* Qt Python binding: <!-- output of pyqtgraph.Qt.VERSION_INFO -->
|
||||
* Python version:
|
||||
* NumPy version: <!-- output of numpy.__version__ -->
|
||||
* Operating system:
|
||||
* Installation method: <!-- e.g. pip, conda, system packages, ... -->
|
||||
|
||||
### Additional context
|
12
.mailmap
12
.mailmap
@ -1,12 +0,0 @@
|
||||
Luke Campagnola <lcampagn@email.unc.edu> Luke Campagnola <>
|
||||
Luke Campagnola <lcampagn@email.unc.edu> Luke Campagnola <luke.campagnola@gmail.com>
|
||||
Megan Kratz <meganbkratz@gmail.com> meganbkratz@gmail.com <>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@megancomputer.local>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@wireless152023024102.med.unc.edu>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@wireless152023025209.med.unc.edu>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@p152023031037.med.unc.edu>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@wire152019114033.med.unc.edu>
|
||||
Megan Kratz <meganbkratz@gmail.com> Megan Kratz <megankratz@wireless152023024078.med.unc.edu>
|
||||
Ingo Breßler <dev@ingobressler.net> Ingo Breßler <ingo.bressler@bam.de>
|
||||
Ingo Breßler <dev@ingobressler.net> Ingo B. <dev@ingobressler.net>
|
||||
|
11
.pre-commit-config.yaml
Normal file
11
.pre-commit-config.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
sha: master
|
||||
hooks:
|
||||
- id: check-added-large-files
|
||||
args: ['--maxkb=100']
|
||||
- id: check-case-conflict
|
||||
- id: end-of-file-fixer
|
||||
- id: fix-encoding-pragma
|
||||
- id: mixed-line-ending
|
||||
args: [--fix=lf]
|
12
.readthedocs.yml
Normal file
12
.readthedocs.yml
Normal file
@ -0,0 +1,12 @@
|
||||
# Read the Docs configuration file
|
||||
# https://docs.readthedocs.io/en/stable/config-file/v2.html
|
||||
|
||||
version: 2
|
||||
|
||||
python:
|
||||
version: 3
|
||||
install:
|
||||
- requirements: doc/requirements.txt
|
||||
|
||||
sphinx:
|
||||
fail_on_warning: true
|
198
.travis.yml
198
.travis.yml
@ -1,198 +0,0 @@
|
||||
language: python
|
||||
sudo: false
|
||||
# Credit: Original .travis.yml lifted from VisPy
|
||||
|
||||
# Here we use anaconda for 2.6 and 3.3, since it provides the simplest
|
||||
# interface for running different versions of Python. We could also use
|
||||
# it for 2.7, but the Ubuntu system has installable 2.7 Qt4-GL, which
|
||||
# allows for more complete testing.
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
|
||||
|
||||
env:
|
||||
# Enable python 2 and python 3 builds
|
||||
# Note that the 2.6 build doesn't get flake8, and runs old versions of
|
||||
# Pyglet and GLFW to make sure we deal with those correctly
|
||||
- PYTHON=2.6 QT=pyqt4 TEST=standard
|
||||
- PYTHON=2.7 QT=pyqt4 TEST=extra
|
||||
- PYTHON=2.7 QT=pyside TEST=standard
|
||||
- PYTHON=3.4 QT=pyqt5 TEST=standard
|
||||
# - PYTHON=3.4 QT=pyside TEST=standard # pyside isn't available for 3.4 with conda
|
||||
#- PYTHON=3.2 QT=pyqt5 TEST=standard
|
||||
|
||||
|
||||
before_install:
|
||||
- if [ ${TRAVIS_PYTHON_VERSION:0:1} == "2" ]; then wget http://repo.continuum.io/miniconda/Miniconda-3.5.5-Linux-x86_64.sh -O miniconda.sh; else wget http://repo.continuum.io/miniconda/Miniconda3-3.5.5-Linux-x86_64.sh -O miniconda.sh; fi
|
||||
- chmod +x miniconda.sh
|
||||
- ./miniconda.sh -b -p /home/travis/mc
|
||||
- export PATH=/home/travis/mc/bin:$PATH
|
||||
|
||||
# not sure what is if block is for
|
||||
- if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
|
||||
GIT_TARGET_EXTRA="+refs/heads/${TRAVIS_BRANCH}";
|
||||
GIT_SOURCE_EXTRA="+refs/pull/${TRAVIS_PULL_REQUEST}/merge";
|
||||
else
|
||||
GIT_TARGET_EXTRA="";
|
||||
GIT_SOURCE_EXTRA="";
|
||||
fi;
|
||||
|
||||
# to aid in debugging
|
||||
- echo ${TRAVIS_BRANCH}
|
||||
- echo ${TRAVIS_REPO_SLUG}
|
||||
- echo ${GIT_TARGET_EXTRA}
|
||||
- echo ${GIT_SOURCE_EXTRA}
|
||||
|
||||
install:
|
||||
- export GIT_FULL_HASH=`git rev-parse HEAD`
|
||||
- conda update conda --yes
|
||||
- conda create -n test_env python=${PYTHON} --yes
|
||||
- source activate test_env
|
||||
- conda install numpy scipy pyopengl pytest flake8 six coverage --yes
|
||||
- echo ${QT}
|
||||
- echo ${TEST}
|
||||
- echo ${PYTHON}
|
||||
|
||||
- if [ "${QT}" == "pyqt5" ]; then
|
||||
conda install pyqt --yes;
|
||||
fi;
|
||||
- if [ "${QT}" == "pyqt4" ]; then
|
||||
conda install pyqt=4 --yes;
|
||||
fi;
|
||||
- if [ "${QT}" == "pyside" ]; then
|
||||
conda install pyside --yes;
|
||||
fi;
|
||||
- pip install pytest-xdist # multi-thread py.test
|
||||
- pip install pytest-cov # add coverage stats
|
||||
|
||||
# required for example testing on python 2.6
|
||||
- if [ "${PYTHON}" == "2.6" ]; then
|
||||
pip install importlib;
|
||||
fi;
|
||||
|
||||
# Debugging helpers
|
||||
- uname -a
|
||||
- cat /etc/issue
|
||||
- if [ "${PYTHON}" == "2.7" ]; then
|
||||
python --version;
|
||||
else
|
||||
python3 --version;
|
||||
fi;
|
||||
|
||||
before_script:
|
||||
# We need to create a (fake) display on Travis, let's use a funny resolution
|
||||
- export DISPLAY=:99.0
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1400x900x24 -ac +extension GLX +render
|
||||
|
||||
# Make sure everyone uses the correct python (this is handled by conda)
|
||||
- which python
|
||||
- python --version
|
||||
- pwd
|
||||
- ls
|
||||
# Help color output from each test
|
||||
- RESET='\033[0m';
|
||||
RED='\033[00;31m';
|
||||
GREEN='\033[00;32m';
|
||||
YELLOW='\033[00;33m';
|
||||
BLUE='\033[00;34m';
|
||||
PURPLE='\033[00;35m';
|
||||
CYAN='\033[00;36m';
|
||||
WHITE='\033[00;37m';
|
||||
start_test() {
|
||||
echo -e "${BLUE}======== Starting $1 ========${RESET}";
|
||||
};
|
||||
check_output() {
|
||||
ret=$?;
|
||||
if [ $ret == 0 ]; then
|
||||
echo -e "${GREEN}>>>>>> $1 passed <<<<<<${RESET}";
|
||||
else
|
||||
echo -e "${RED}>>>>>> $1 FAILED <<<<<<${RESET}";
|
||||
fi;
|
||||
return $ret;
|
||||
};
|
||||
|
||||
- if [ "${TEST}" == "extra" ]; then
|
||||
start_test "repo size check";
|
||||
mkdir ~/repo-clone && cd ~/repo-clone &&
|
||||
git init && git remote add -t ${TRAVIS_BRANCH} origin git://github.com/${TRAVIS_REPO_SLUG}.git &&
|
||||
git fetch origin ${GIT_TARGET_EXTRA} &&
|
||||
git checkout -qf FETCH_HEAD &&
|
||||
git tag travis-merge-target &&
|
||||
git gc --aggressive &&
|
||||
TARGET_SIZE=`du -s . | sed -e "s/\t.*//"` &&
|
||||
git pull origin ${GIT_SOURCE_EXTRA} &&
|
||||
git gc --aggressive &&
|
||||
MERGE_SIZE=`du -s . | sed -e "s/\t.*//"` &&
|
||||
if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then
|
||||
SIZE_DIFF=`expr \( ${MERGE_SIZE} - ${TARGET_SIZE} \)`;
|
||||
else
|
||||
SIZE_DIFF=0;
|
||||
fi;
|
||||
fi;
|
||||
|
||||
script:
|
||||
|
||||
- source activate test_env
|
||||
|
||||
# Check system info
|
||||
- python -c "import pyqtgraph as pg; pg.systemInfo()"
|
||||
|
||||
# Run unit tests
|
||||
- start_test "unit tests";
|
||||
PYTHONPATH=. py.test --cov pyqtgraph -sv;
|
||||
check_output "unit tests";
|
||||
- echo "test script finished. Current directory:"
|
||||
- pwd
|
||||
|
||||
# check line endings
|
||||
- if [ "${TEST}" == "extra" ]; then
|
||||
start_test "line ending check";
|
||||
! find ./ -name "*.py" | xargs file | grep CRLF &&
|
||||
! find ./ -name "*.rst" | xargs file | grep CRLF;
|
||||
check_output "line ending check";
|
||||
fi;
|
||||
|
||||
# Check repo size does not expand too much
|
||||
- if [ "${TEST}" == "extra" ]; then
|
||||
start_test "repo size check";
|
||||
echo -e "Estimated content size difference = ${SIZE_DIFF} kB" &&
|
||||
test ${SIZE_DIFF} -lt 100;
|
||||
check_output "repo size check";
|
||||
fi;
|
||||
|
||||
# Check for style issues
|
||||
- if [ "${TEST}" == "extra" ]; then
|
||||
start_test "style check";
|
||||
cd ~/repo-clone &&
|
||||
git reset -q travis-merge-target &&
|
||||
python setup.py style &&
|
||||
check_output "style check";
|
||||
fi;
|
||||
|
||||
# Check install works
|
||||
- start_test "install test";
|
||||
python setup.py --quiet install;
|
||||
check_output "install test";
|
||||
|
||||
# Check double-install fails
|
||||
# Note the bash -c is because travis strips off the ! otherwise.
|
||||
- start_test "double install test";
|
||||
bash -c "! python setup.py --quiet install";
|
||||
check_output "double install test";
|
||||
|
||||
# Check we can import pg
|
||||
- start_test "import test";
|
||||
echo "import sys; print(sys.path)" | python &&
|
||||
cd /; echo "import pyqtgraph.examples" | python;
|
||||
check_output "import test";
|
||||
|
||||
after_success:
|
||||
- cd /home/travis/build/pyqtgraph/pyqtgraph
|
||||
- pip install codecov --upgrade # add coverage integration service
|
||||
- codecov
|
||||
- pip install coveralls --upgrade # add another coverage integration service
|
||||
- coveralls
|
314
CHANGELOG
314
CHANGELOG
@ -1,3 +1,317 @@
|
||||
pyqtgraph-0.11.0
|
||||
|
||||
NOTICE: This is the _last_ feature release to support Python 2 and Qt 4 (PyQt4 or pyside 1)
|
||||
|
||||
New Features:
|
||||
- #101: GridItem formatting options
|
||||
- #410: SpinBox custom formatting options
|
||||
- #415: ROI.getArrayRegion supports nearest-neighbor interpolation (especially handy for label images)
|
||||
- #428: DataTreeWidget:
|
||||
- Add DiffTreeWidget, which highlights differences between two DataTreeWidgets
|
||||
- Improved support for displaying tracebacks
|
||||
- Use TableWidget to represent arrays rather than plain text
|
||||
- #446: Added Perceptually Uniform Sequential colormaps from the matplotlib 2.0 release
|
||||
- #476: Add option to set composition mode for scatterplotitem
|
||||
- #518: TreeWidget:
|
||||
- Add new signals: sigItemCheckStateChanged, sigItemTextChanged, sigColumnCountChanged
|
||||
- Allow setting expansion state of items before they are added to a treewidget
|
||||
- Support for using TreeWidget.invisibleRootItem() (see also #592, #595)
|
||||
- #542: Add collapsible QGroupBox widgets
|
||||
- #543: Add TargetItem: simple graphicsitem that draws a scale-invariant circle + crosshair
|
||||
- #544: Make DockArea.restoreState behavior configurable in cases where either a dock to be restored is
|
||||
missing, or an extra dock exists that is not mentioned in the restore state.
|
||||
- #545: Allow more types to be mapped through Transform3D
|
||||
- #548: Adds a disconnect() function that allows to conditionally disconnect signals,
|
||||
including after reload.
|
||||
Also, a SignalBlock class used to temporarily block a signal-slot pair
|
||||
- #557: Allow console stack to be set outside of exceptions (see also: pg.stack)
|
||||
- #558: CanvasItem save/restore, make Canvas ui easier to embed
|
||||
- #559: Image exporter gets option to invert value while leaving hue fixed
|
||||
- #560: Add function to enable faulthandler on all threads, also allow Mutex to be used as
|
||||
drop-in replacement for python's Lock
|
||||
- #567: Flowchart
|
||||
- Add several new data nodes
|
||||
- Add floordiv node
|
||||
- Add EvalNode.setCode
|
||||
- Binary operator nodes can select output array type
|
||||
- #568: LinearRegionItem
|
||||
- InfiniteLine can draw markers attached to the line
|
||||
- InfiniteLine can limit the region of the viewbox over which it is drawn
|
||||
- LinearRegionItem gets customizable line swap behavior (lines can block or push each other)
|
||||
- Added LinearRegionItem.setHoverBrush
|
||||
- #580: Allow calling sip.setapi in subprocess before pyqtgraph is imported
|
||||
- #582: Add ComboBox save/restoreState methods
|
||||
- #586: ParameterTree
|
||||
- Add GroupParameter.sigAddNew signal
|
||||
- systemsolver: add method for checking constraints / DOF
|
||||
- add systemsolver copy method
|
||||
- Parameter.child raises KeyError if requested child name does not exist
|
||||
- #587: Make PathButton margin customizable
|
||||
- #588: Add PlotCurveItem composition mode
|
||||
- #589: Add RulerROI
|
||||
- #591: Add nested progress dialogs
|
||||
- #597: Fancy new interactive fractal demo
|
||||
- #621: RGB mode for HistogramLUTWidget
|
||||
- #628,670: Add point selection in ScatterPlotWidget
|
||||
- #635: PySide2 support
|
||||
- #671: Add SVG export option to force non-scaling stroke
|
||||
- #676: OpenGL allow for panning in the plane of the camera
|
||||
- #683: Allow data filter entries to be updated after they are created
|
||||
- #685: Add option to set enum default values in DataFilterWidget
|
||||
- #710: Adds ability to rotate/scale ROIs by mouse drag on the ROI itself (using alt/shift modifiers)
|
||||
- #813,814,817: Performance improvements
|
||||
- #837: Added options for field variables in ColorMapWidget
|
||||
- #840, 932: Improve clipping behavior
|
||||
- #841: Set color of tick-labels separately
|
||||
- #922: Curve fill for fill-patches
|
||||
- #996: Allow the update of LegendItem
|
||||
- #1023: Add bookkeeping exporter parameters
|
||||
- #1072: HDF5Exporter handling of ragged curves with tests
|
||||
- #1124: Syntax highlighting for examples.
|
||||
- #1154: Date axis item
|
||||
- #393: NEW show/hide gradient ticks NEW link gradientEditor to others
|
||||
- #1211: Add support for running pyside2-uic binary to dynamically compile ui files
|
||||
|
||||
API / behavior changes:
|
||||
- Deprecated graphicsWindow classes; these have been unnecessary for many years because
|
||||
widgets can be placed into a new window just by calling show().
|
||||
- #158: Make DockArea compatible with Qt Designer
|
||||
- #406: Applying alpha mask on numpy.nan data values
|
||||
- #566: ArrowItem's `angle` option now rotates the arrow without affecting its coordinate system.
|
||||
The result is visually the same, but children of ArrowItem are no longer rotated
|
||||
(this allows screen-aligned text to be attached more easily).
|
||||
To mimic the old behavior, use ArrowItem.rotate() instead of the `angle` argument.
|
||||
- #673: Integer values in ParameterTree are now formatted as integer (%d) by default, rather than
|
||||
scientific notation (%g). This can be overridden by providing `format={value:g}` when
|
||||
creating the parameter.
|
||||
- #374: ConsoleWidget uses the console's namespace as both global and local scope, which
|
||||
- #410: SpinBox siPrefix without suffix is not longer allowed, select only numerical portion of text on focus-in
|
||||
allows functions defined in the console to access the global namespace.
|
||||
- #479,521: ParameterTree simple parameters check types before setting value
|
||||
- #555: multiprocess using callSync='sync' no longer returns a future in case of timeout
|
||||
- #583: eq() no longer compares array values if they have different shape
|
||||
- #589: Remove SpiralROI (this was unintentionally added in the first case)
|
||||
- #593: Override qAbort on slot exceptions for PyQt>=5.5
|
||||
- #657: When a floating Dock window is closed, the dock is now returned home
|
||||
- #771: Suppress RuntimeWarning for arrays containing zeros in logscale
|
||||
- #942: If the visible GraphicsView is garbage collected, a warning is issued.
|
||||
- #958: Nicer Legend
|
||||
- #963: Last image in image-stack can now be selected with the z-slider
|
||||
- #992: Added a setter for GlGridItem.color.
|
||||
- #999: Make outline around fillLevel optional.
|
||||
- #1014: Enable various arguments as color in colormap.
|
||||
- #1044: Raise AttributeError in __getattr__ in graphicsWindows (deprecated)
|
||||
- #1055: Remove global for CONFIG_OPTIONS in setConfigOption
|
||||
- #1066: Add RemoteGraphicsView to __init__.py
|
||||
- #1069: Allow actions to display title instead of name
|
||||
- #1074: Validate min/max text inputs in ViewBoxMenu
|
||||
- #1076: Reset currentRow and currentCol on GraphicsLayout.clear()
|
||||
- #1079: Improve performance of updateData PlotCurveItem
|
||||
- #1082: Allow MetaArray.__array__ to accept an optional dtype arg
|
||||
- #841: set color of tick-labels separately
|
||||
- #1111: Add name label to GradientEditorItem
|
||||
- #1145: Pass showAxRect keyword arguments to setRange
|
||||
- #1184: improve SymbolAtlas.getSymbolCoords performance
|
||||
- #1198: improve SymbolAtlas.getSymbolCoords and ScatterPlotItem.plot performance
|
||||
- #1197: Disable remove ROI menu action in handle context menu
|
||||
- #1188: Added support for plot curve to handle both fill and connect args
|
||||
- #801: Remove use of GraphicsScene._addressCache in translateGraphicsItem
|
||||
- Deprecates registerObject meethod of GraphicsScene
|
||||
- Deprecates regstar argument to GraphicsScene.__init__
|
||||
- #1166: pg.mkQApp: Pass non-empty string array to QApplication() as default
|
||||
- #1199: Pass non-empty sys.argv to QApplication
|
||||
- #1090: dump ExportDialog.exporterParameters
|
||||
- #1173: GraphicsLayout: Always call layout.activate() after adding items
|
||||
- #1097: pretty-print log-scale axes labels
|
||||
- #755: Check lastDownsample in viewTransformChanged
|
||||
- #1216: Add cache for mapRectFromView
|
||||
- #444: Fix duplicate menus in GradientEditorItem
|
||||
- #151: Optionally provide custom PlotItem to PlotWidget
|
||||
- #1093: Fix aspectRatio and zoom range issues when zooming
|
||||
- #390: moved some functionality from method 'export' to new method
|
||||
- #468: Patch/window handling
|
||||
- #392: new method 'getAxpectRatio' with code taken from 'setAspectLocked'
|
||||
- #1206: Added context menu option to parametertree
|
||||
- #1228: Minor improvements to LegendItem
|
||||
|
||||
Bugfixes:
|
||||
- #88: Fixed image scatterplot export
|
||||
- #356: Fix some NumPy warnings
|
||||
- #408: Fix `cleanup` when the running qt application is not a QApplication
|
||||
- #410: SpinBox fixes
|
||||
- fixed bug with exponents disappearing after edit
|
||||
- fixed parsing of values with junk after suffix
|
||||
- fixed red border
|
||||
- reverted default decimals to 6
|
||||
- make suffix editable (but show red border if it's wrong)
|
||||
- revert invalid text on focus lost
|
||||
- siPrefix without suffix is no longer allowed
|
||||
- fixed parametree sending invalid options to spinbox
|
||||
- fix spinbox wrapping (merged #159 from @lidstrom83)
|
||||
- fixed parametertree ignoring spinbox bounds (merged #329 from @lidstrom83)
|
||||
- fixed spinbox height too small for font size
|
||||
- ROI subclass getArrayRegion methods are a bit more consistent (still need work)
|
||||
- #424: Fix crash when running pyqtgraph with python -OO
|
||||
- #429: Fix fft premature slicing away of 0 freq bin
|
||||
- #458: Fixed image export problems with new numpy API
|
||||
- #478: Fixed PySide image memory leak
|
||||
- #475: Fixed unicode error when exporting to SVG with non-ascii symbols
|
||||
- #477: Fix handling of the value argument to functions.intColor
|
||||
- #485: Fixed incorrect height in VTickGroup
|
||||
- #514: Fixes bug where ViewBox emits sigRangeChanged before it has marked its transform dirty.
|
||||
- #516,668: Fix GL Views being half size on hidpi monitors
|
||||
- #526: Fix autorange exception with empty scatterplot
|
||||
- #528: Prevent image downsampling causing exception in makeQImage
|
||||
- #530: Fixed issue where setData only updated opts if data is given
|
||||
- #541: Fixed issue where render would error because 'mapToDevice' would return None if the view size was too small.
|
||||
- #553: Fixed legend size after remove item
|
||||
- #555: Fixed console color issues, problems with subprocess closing
|
||||
- #559: HDF5 exporter: check for ragged array length
|
||||
- #563: Prevent viewbox auto-scaling to items that are not in the same scene. (This could
|
||||
happen if an item that was previously added to the viewbox is then removed using scene.removeItem().
|
||||
- #564: Allow console exception label to wrap text (prevents console
|
||||
growing too large for long exception messages)
|
||||
- #565: Fixed AxisItem preventing mouse events reaching the ViewBox if it is displaying grid lines
|
||||
and has its Z value set higher than the ViewBox.
|
||||
- #567: fix flowchart spinbox bounds
|
||||
- #569: PlotItem.addLegend will not try to add more than once
|
||||
- #570: ViewBox: make sure transform is up to date in all mapping functions
|
||||
- #577: Fix bargraphitem plotting horizontal bars
|
||||
- #581: Fix colormapwidget saveState
|
||||
- #586: ParameterTree
|
||||
- Make parameter name,value inint args go through setValue and setName
|
||||
- Fix colormapwidget saveState
|
||||
- #589: Fix click area for small ellipse/circle ROIs
|
||||
- #592,595: Fix InvisibleRootItem issues introduced in #518
|
||||
- #596: Fix polyline click causing lines to bedrawn to the wrong node
|
||||
- #598: Better ParameterTree support for dark themes
|
||||
- #599: Prevent invalid list access in GraphicsScene
|
||||
- #623: Fix PyQt5 / ScatterPlot issue with custom symbols
|
||||
- #626: Fix OpenGL texture state leaking to wrong items
|
||||
- #627: Fix ConsoleWidget stack handling on python 3.5
|
||||
- #633: Fix OpenGL cylinder geometry
|
||||
- #637: Fix TypeError in isosurface
|
||||
- #641,642: Fix SVG export on Qt5 / high-DPI displays
|
||||
- #645: ScatterPlotWidget behaves nicely when data contains infs
|
||||
- #653: ScatterPlotItem: Fix a GC memory leak due to numpy issue 6581
|
||||
- #648: fix color ignored in GLGridItem
|
||||
- #671: Fixed SVG export failing if the first value of a plot is nan
|
||||
- #674: Fixed parallelizer leaking file handles
|
||||
- #675: Gracefully handle case where image data has size==0
|
||||
- #679: Fix overflow in Point.length()
|
||||
- #682: Fix: mkQApp returned None if a QApplication was already created elsewhere
|
||||
- #689: ViewBox fix: don't call setRange with empty args
|
||||
- #693: Fix GLLinePlotItem setting color
|
||||
- #696: Fix error when using PlotDataItem with both stepMode and symbol
|
||||
- #697: Fix SpinBox validation on python 3
|
||||
- #699: Fix nan handling in ImageItem.setData
|
||||
- #713: ConsoleWidget: Fixed up/down arrows sometimes unable to get back to the original
|
||||
(usually blank) input state
|
||||
- #715: Fix file dialog handling in Qt 5
|
||||
- #718: Fix SVG export with items that require option.exposedRect
|
||||
- #721: Fixes mouse wheel ignoring disabled mouse axes -- although the scaling was correct,
|
||||
it was causing auto range to be disabled.
|
||||
- #723: Fix axis ticks when using self.scale
|
||||
- #739: Fix handling of 2-axis mouse wheel events
|
||||
- #742: Fix Metaarray in python 3
|
||||
- #758: Fix remote graphicsview "ValueError: mmap length is greater than file size" on OSX.
|
||||
- #763: Fix OverflowError when using Auto Downsampling.
|
||||
- #767: Fix Image display for images with the same value everywhere.
|
||||
- #770: Fix GLVieWidget.setCameraPosition ignoring first parameter.
|
||||
- #782: Fix missing FileForwarder thread termination.
|
||||
- #787: Fix encoding errors in checkOpenGLVersion.
|
||||
- #793: Fix wrong default scaling in makeARGB
|
||||
- #815: Fixed mirroring of x-axis with "invert Axis" submenu.
|
||||
- #824: Fix several issues related with mouse movement and GraphicsView.
|
||||
- #832: Fix Permission error in tests due to unclosed filehandle.
|
||||
- #836: Fix tickSpacing bug that lead to axis not being drawn.
|
||||
- #861: Fix crash of PlotWidget if empty ErrorBarItem is added.
|
||||
- #868: Fix segfault on repeated closing of matplotlib exporter.
|
||||
- #875,876,887,934,947,980: Fix deprecation warnings.
|
||||
- #886: Fix flowchart saving on python3.
|
||||
- #888: Fix TreeWidget.topLevelItems in python3.
|
||||
- #924: Fix QWheelEvent in RemoteGraphicsView with pyqt5.
|
||||
- #935: Fix PlotItem.addLine with 'pos' and 'angle' parameter.
|
||||
- #949: Fix multiline parameters (such as arrays) reading from config files.
|
||||
- #951: Fix event firing from scale handler.
|
||||
- #952: Fix RotateFree handle dragging
|
||||
- #953: Fix HistogramLUTWidget with background parameter
|
||||
- #968: Fix Si units in AxisItem leading to an incorrect unit.
|
||||
- #970: Always update transform when setting angle of a TextItem
|
||||
- #971: Fix a segfault stemming from incorrect signal disconnection.
|
||||
- #972: Correctly include SI units for log AxisItems
|
||||
- #974: Fix recursion error when instancing CtrlNode.
|
||||
- #987: Fix visibility reset when PlotItems are removed.
|
||||
- #998: Fix QtProcess proxy being unable to handle numpy arrays with dtype uint8.
|
||||
- #1010: Fix matplotlib/CSV export.
|
||||
- #1012: Fix circular texture centering
|
||||
- #1015: Iterators are now converted to NumPy arrays.
|
||||
- #1016: Fix synchronisation of multiple ImageViews with time axis.
|
||||
- #1017: Fix duplicate paint calls emitted by Items on ViewBox.
|
||||
- #1019: Fix disappearing GLGridItems when PlotItems are removed and readded.
|
||||
- #1024: Prevent element-wise string comparison
|
||||
- #1031: Reset ParentItem to None on removing from PlotItem/ViewBox
|
||||
- #1044: Fix PlotCurveItem.paintGL
|
||||
- #1048: Fix bounding box for InfiniteLine
|
||||
- #1062: Fix flowchart context menu redundant menu
|
||||
- #1062: Fix a typo
|
||||
- #1073: Fix Python3 compatibility
|
||||
- #1083: Fix SVG export of scatter plots
|
||||
- #1085: Fix ofset when drawing symbol
|
||||
- #1101: Fix small oversight in LegendItem
|
||||
- #1113: Correctly call hasFaceIndexedData function
|
||||
- #1139: Bug fix in LegendItem for `setPen`, `setBrush` etc (Call update instead of paint)
|
||||
- #1110: fix for makeARGB error after #955
|
||||
- #1063: Fix: AttributeError in ViewBox.setEnableMenu
|
||||
- #1151: ImageExporter py2-pyside fix with test
|
||||
- #1133: compatibility-fix for py2/pyside
|
||||
- #1152: Nanmask fix in makeARGB
|
||||
- #1159: Fix: Update axes after data is set
|
||||
- #1156: SVGExporter: Correct image pixelation
|
||||
- #1169: Replace default list arg with None
|
||||
- #770: Do not ignore pos argument of setCameraPosition
|
||||
- #1180: Fix: AxisItem tickFont is defined in two places while only one is used
|
||||
- #1168: GroupParameterItem: Did not pass changed options to ParameterItem
|
||||
- #1174: Fixed a possible race condition with linked views
|
||||
- #809: Fix selection of FlowchartWidget input/output nodes
|
||||
- #1071: Fix py3 execution in flowchart
|
||||
- #1212: Fix PixelVectors cache
|
||||
- #1161: Correctly import numpy where needed
|
||||
- #1218: Fix ParameterTree.clear()
|
||||
- #1175: Fix: Parameter tree ignores user-set 'expanded' state
|
||||
- #1219: Encode csv export header as unicode
|
||||
- #507: Fix Dock close event QLabel still running with no parent
|
||||
- #1222: py3 fix for ScatterPlotWidget.setSelectedFields
|
||||
- #1203: Image axis order bugfix
|
||||
- #1225: ParameterTree: Fix custom context menu
|
||||
|
||||
|
||||
Maintenance:
|
||||
- Lots of new unit tests
|
||||
- Lots of code cleanup
|
||||
- A lot of work on CI pipelines, test coverage and test passing (see e.g. #903,911)
|
||||
- #546: Add check for EINTR during example testing to avoid sporadic test failures on travis
|
||||
- #624: TravisCI no longer running python 2.6 tests
|
||||
- #695: "dev0" added to version string
|
||||
- #865,873,877 (and more): Implement Azure CI pipelines, fix Travis CI
|
||||
- #991: Use Azure Pipelines to do style checks, Add .pre-commit-config.yaml
|
||||
- #1042: Close windows at the end of test functions
|
||||
- #1046: Establish minimum numpy version, remove legacy workarounds
|
||||
- #1067: Make scipy dependency optional
|
||||
- #1114: doc: Fix small mistake in introduction
|
||||
- #1131: Update CI/tox and Enable More Tests
|
||||
- #1142: Miscellaneous doc fixups
|
||||
- #1179: DateAxisItem: AxisItem unlinking tests and doc fixed
|
||||
- #1201: Get readthedocs working
|
||||
- #1214: Pin PyVirtualDisplay Version
|
||||
- #1215: Skip test when on qt 5.9
|
||||
- #1221: Identify pyqt5 5.15 ci issue
|
||||
- #1223: Remove workaround for memory leak in QImage
|
||||
- #1217: Get docs version and copyright year dynamically
|
||||
- #1229: Wrap text in tables in docs
|
||||
- #1231: Update readme for 0.11 release
|
||||
|
||||
|
||||
pyqtgraph-0.10.0
|
||||
|
||||
New Features:
|
||||
|
72
CONTRIBUTING.md
Normal file
72
CONTRIBUTING.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Contributing to PyQtGraph
|
||||
|
||||
Contributions to pyqtgraph are welcome!
|
||||
|
||||
Please use the following guidelines when preparing changes:
|
||||
|
||||
## Submitting Code Changes
|
||||
|
||||
* The preferred method for submitting changes is by github pull request against the "develop" branch.
|
||||
* Pull requests should include only a focused and related set of changes. Mixed features and unrelated changes may be rejected.
|
||||
* For major changes, it is recommended to discuss your plans on the mailing list or in a github issue before putting in too much effort.
|
||||
* The following deprecations are being considered by the maintainers
|
||||
* `pyqtgraph.opengl` may be deprecated and replaced with `VisPy` functionality
|
||||
* After v0.11, pyqtgraph will adopt [NEP-29](https://numpy.org/neps/nep-0029-deprecation_policy.html) which will effectively mean that python2 support will be deprecated
|
||||
* Qt4 will be deprecated shortly, as well as Qt5<5.9 (and potentially <5.12)
|
||||
|
||||
## Documentation
|
||||
|
||||
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph uses pytest style testing, so tests should usually be included in a tests/ directory adjacent to the relevant code.
|
||||
* Documentation is generated with sphinx; please check that docstring changes compile correctly
|
||||
|
||||
## Style guidelines
|
||||
|
||||
### Rules
|
||||
|
||||
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced rigorously as long as the code is clean and readable.
|
||||
* Use `python setup.py style` to see whether your code follows the mandatory style guidelines checked by flake8.
|
||||
* Exception 1: All variable names should use camelCase rather than underscore_separation. This is done for consistency with Qt
|
||||
* Exception 2: Function docstrings use ReStructuredText tables for describing arguments:
|
||||
|
||||
```text
|
||||
============== ========================================================
|
||||
**Arguments:**
|
||||
argName1 (type) Description of argument
|
||||
argName2 (type) Description of argument. Longer descriptions must
|
||||
be wrapped within the column guidelines defined by the
|
||||
"====" header and footer.
|
||||
============== ========================================================
|
||||
```
|
||||
|
||||
QObject subclasses that implement new signals should also describe
|
||||
these in a similar table.
|
||||
|
||||
### Pre-Commit
|
||||
|
||||
PyQtGraph developers are highly encouraged to (but not required) to use [`pre-commit`](https://pre-commit.com/). `pre-commit` does a number of checks when attempting to commit the code to ensure it conforms to various standards, such as `flake8`, utf-8 encoding pragma, line-ending fixers, and so on. If any of the checks fail, the commit will be rejected, and you will have the opportunity to make the necessary fixes before adding and committing a file again. This ensures that every commit made conforms to (most) of the styling standards that the library enforces; and you will most likely pass the code style checks by the CI.
|
||||
|
||||
To make use of `pre-commit`, have it available in your `$PATH` and run `pre-commit install` from the root directory of PyQtGraph.
|
||||
|
||||
## Testing Setting up a test environment
|
||||
|
||||
### Dependencies
|
||||
|
||||
* tox
|
||||
* tox-conda
|
||||
* pytest
|
||||
* pytest-cov
|
||||
* pytest-xdist
|
||||
* Optional: pytest-xvfb
|
||||
|
||||
If you have `pytest<5` (used in python2), you may also want to install `pytest-faulthandler==1.6` plugin to output extra debugging information in case of test failures. This isn't necessary with `pytest>=5`
|
||||
|
||||
### Tox
|
||||
|
||||
As PyQtGraph supports a wide array of Qt-bindings, and python versions, we make use of `tox` to test against most of the configurations in our test matrix. As some of the qt-bindings are only installable via `conda`, `conda` needs to be in your `PATH`, and we utilize the `tox-conda` plugin.
|
||||
|
||||
* Tests for a module should ideally cover all code in that module, i.e., statement coverage should be at 100%.
|
||||
* To measure the test coverage, un `pytest --cov -n 4` to run the test suite with coverage on 4 cores.
|
||||
|
||||
### Continous Integration
|
||||
|
||||
For our Continuous Integration, we utilize Azure Pipelines. Tested configurations are visible on [README](README.md). More information on coverage and test failures can be found on the respective tabs of the [build results page](https://dev.azure.com/pyqtgraph/pyqtgraph/_build?definitionId=1)
|
@ -1,58 +0,0 @@
|
||||
Contributions to pyqtgraph are welcome!
|
||||
|
||||
Please use the following guidelines when preparing changes:
|
||||
|
||||
* The preferred method for submitting changes is by github pull request
|
||||
against the "develop" branch.
|
||||
|
||||
* Pull requests should include only a focused and related set of changes.
|
||||
Mixed features and unrelated changes may be rejected.
|
||||
|
||||
* For major changes, it is recommended to discuss your plans on the mailing
|
||||
list or in a github issue before putting in too much effort.
|
||||
|
||||
* Along these lines, please note that pyqtgraph.opengl will be deprecated
|
||||
soon and replaced with VisPy.
|
||||
|
||||
* Writing proper documentation and unit tests is highly encouraged. PyQtGraph
|
||||
uses nose / py.test style testing, so tests should usually be included in a
|
||||
tests/ directory adjacent to the relevant code.
|
||||
|
||||
* Documentation is generated with sphinx; please check that docstring changes
|
||||
compile correctly.
|
||||
|
||||
* Style guidelines:
|
||||
|
||||
* PyQtGraph prefers PEP8 for most style issues, but this is not enforced
|
||||
rigorously as long as the code is clean and readable.
|
||||
|
||||
* Use `python setup.py style` to see whether your code follows
|
||||
the mandatory style guidelines checked by flake8.
|
||||
|
||||
* Exception 1: All variable names should use camelCase rather than
|
||||
underscore_separation. This is done for consistency with Qt
|
||||
|
||||
* Exception 2: Function docstrings use ReStructuredText tables for
|
||||
describing arguments:
|
||||
|
||||
```
|
||||
============== ========================================================
|
||||
**Arguments:**
|
||||
argName1 (type) Description of argument
|
||||
argName2 (type) Description of argument. Longer descriptions must
|
||||
be wrapped within the column guidelines defined by the
|
||||
"====" header and footer.
|
||||
============== ========================================================
|
||||
```
|
||||
|
||||
QObject subclasses that implement new signals should also describe
|
||||
these in a similar table.
|
||||
|
||||
* Setting up a test environment.
|
||||
|
||||
Tests for a module should ideally cover all code in that module,
|
||||
i.e., statement coverage should be at 100%.
|
||||
|
||||
To measure the test coverage, install py.test, pytest-cov and pytest-xdist.
|
||||
Then run 'py.test --cov -n 4' to run the test suite with coverage on 4 cores.
|
||||
|
107
README.md
107
README.md
@ -1,85 +1,72 @@
|
||||
[![Build Status](https://travis-ci.org/pyqtgraph/pyqtgraph.svg?branch=develop)](https://travis-ci.org/pyqtgraph/pyqtgraph)
|
||||
[![codecov.io](http://codecov.io/github/pyqtgraph/pyqtgraph/coverage.svg?branch=develop)](http://codecov.io/github/pyqtgraph/pyqtgraph?branch=develop)
|
||||
|
||||
[![Build Status](https://pyqtgraph.visualstudio.com/pyqtgraph/_apis/build/status/pyqtgraph.pyqtgraph?branchName=develop)](https://pyqtgraph.visualstudio.com/pyqtgraph/_build/latest?definitionId=17&branchName=develop)
|
||||
[![Documentation Status](https://readthedocs.org/projects/pyqtgraph/badge/?version=latest)](https://pyqtgraph.readthedocs.io/en/latest/?badge=latest)
|
||||
|
||||
PyQtGraph
|
||||
=========
|
||||
|
||||
A pure-Python graphics library for PyQt/PySide
|
||||
A pure-Python graphics library for PyQt/PySide/PyQt5/PySide2
|
||||
|
||||
Copyright 2012 Luke Campagnola, University of North Carolina at Chapel Hill
|
||||
Copyright 2020 Luke Campagnola, University of North Carolina at Chapel Hill
|
||||
|
||||
<http://www.pyqtgraph.org>
|
||||
|
||||
Maintainer
|
||||
----------
|
||||
|
||||
* Luke Campagnola <luke.campagnola@gmail.com>
|
||||
|
||||
Contributors
|
||||
------------
|
||||
|
||||
* Megan Kratz
|
||||
* Paul Manis
|
||||
* Ingo Breßler
|
||||
* Christian Gavin
|
||||
* Michael Cristopher Hogg
|
||||
* Ulrich Leutner
|
||||
* Felix Schill
|
||||
* Guillaume Poulin
|
||||
* Antony Lee
|
||||
* Mattias Põldaru
|
||||
* Thomas S.
|
||||
* Fabio Zadrozny
|
||||
* Mikhail Terekhov
|
||||
* Pietro Zambelli
|
||||
* Stefan Holzmann
|
||||
* Nicholas TJ
|
||||
* John David Reaver
|
||||
* David Kaplan
|
||||
* Martin Fitzpatrick
|
||||
* Daniel Lidstrom
|
||||
* Eric Dill
|
||||
* Vincent LeSaux
|
||||
PyQtGraph is intended for use in mathematics / scientific / engineering applications.
|
||||
Despite being written entirely in python, the library is fast due to its
|
||||
heavy leverage of numpy for number crunching, Qt's GraphicsView framework for
|
||||
2D display, and OpenGL for 3D display.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* PyQt 4.7+, PySide, or PyQt5
|
||||
* python 2.6, 2.7, or 3.x
|
||||
* NumPy
|
||||
* For 3D graphics: pyopengl and qt-opengl
|
||||
* Known to run on Windows, Linux, and Mac.
|
||||
* Python 2.7, or 3.x
|
||||
* Required
|
||||
* PyQt 4.8+, PySide, PyQt5, or PySide2
|
||||
* `numpy`
|
||||
* Optional
|
||||
* `scipy` for image processing
|
||||
* `pyopengl` for 3D graphics
|
||||
* `hdf5` for large hdf5 binary format support
|
||||
|
||||
Qt Bindings Test Matrix
|
||||
-----------------------
|
||||
|
||||
The following table represents the python environments we test in our CI system. Our CI system uses Ubuntu 18.04, Windows Server 2019, and macOS 10.15 base images.
|
||||
|
||||
| Qt-Bindings | Python 2.7 | Python 3.6 | Python 3.7 | Python 3.8 |
|
||||
| :------------- | :----------------: | :----------------: | :----------------: | :----------------: |
|
||||
| PyQt-4 | :white_check_mark: | :x: | :x: | :x: |
|
||||
| PySide1 | :white_check_mark: | :x: | :x: | :x: |
|
||||
| PyQt5-5.9 | :x: | :white_check_mark: | :x: | :x: |
|
||||
| PySide2-5.13 | :x: | :x: | :white_check_mark: | :x: |
|
||||
| PyQt5-Latest | :x: | :x: | :x: | :white_check_mark: |
|
||||
| PySide2-Latest | :x: | :x: | :x: | :white_check_mark: |
|
||||
|
||||
* pyqtgraph has had some incompatibilities with PySide2 versions 5.6-5.11, and we recommend you avoid those versions if possible
|
||||
* on macOS with Python 2.7 and Qt4 bindings (PyQt4 or PySide) the openGL related visualizations do not work reliably
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
Post at the [mailing list / forum](https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph)
|
||||
* Report issues on the [GitHub issue tracker](https://github.com/pyqtgraph/pyqtgraph/issues)
|
||||
* Post questions to the [mailing list / forum](https://groups.google.com/forum/?fromgroups#!forum/pyqtgraph) or [StackOverflow](https://stackoverflow.com/questions/tagged/pyqtgraph)
|
||||
|
||||
Installation Methods
|
||||
--------------------
|
||||
|
||||
* To use with a specific project, simply copy the pyqtgraph subdirectory
|
||||
anywhere that is importable from your project. PyQtGraph may also be
|
||||
used as a git subtree by cloning the git-core repository from github.
|
||||
* To install system-wide from source distribution:
|
||||
`$ python setup.py install`
|
||||
* For installation packages, see the website (pyqtgraph.org)
|
||||
* On debian-like systems, pyqtgraph requires the following packages:
|
||||
python-numpy, python-qt4 | python-pyside
|
||||
For 3D support: python-opengl, python-qt4-gl | python-pyside.qtopengl
|
||||
* From PyPI:
|
||||
* Last released version: `pip install pyqtgraph`
|
||||
* Latest development version: `pip install git+https://github.com/pyqtgraph/pyqtgraph@master`
|
||||
* From conda
|
||||
* Last released version: `conda install -c conda-forge pyqtgraph`
|
||||
* To install system-wide from source distribution: `python setup.py install`
|
||||
* Many linux package repositories have release versions.
|
||||
* To use with a specific project, simply copy the pyqtgraph subdirectory
|
||||
anywhere that is importable from your project.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
There are many examples; run `python -m pyqtgraph.examples` for a menu.
|
||||
The official documentation lives at https://pyqtgraph.readthedocs.io
|
||||
|
||||
Some (incomplete) documentation exists at this time.
|
||||
* Easiest place to get documentation is at <http://www.pyqtgraph.org/documentation>
|
||||
* If you acquired this code as a .tar.gz file from the website, then you can also look in
|
||||
doc/html.
|
||||
* If you acquired this code via GitHub, then you can build the documentation using sphinx.
|
||||
From the documentation directory, run:
|
||||
`$ make html`
|
||||
|
||||
Please feel free to pester Luke or post to the forum if you need a specific
|
||||
section of documentation to be expanded.
|
||||
The easiest way to learn pyqtgraph is to browse through the examples; run `python -m pyqtgraph.examples` to launch the examples application.
|
||||
|
99
azure-pipelines.yml
Normal file
99
azure-pipelines.yml
Normal file
@ -0,0 +1,99 @@
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- '*' # Build for all branches if they have a azure-pipelines.yml file.
|
||||
tags:
|
||||
include:
|
||||
- 'v*' # Ensure that we are building for tags starting with 'v' (Official Versions)
|
||||
|
||||
# Build only for PRs for master branch
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- master
|
||||
- develop
|
||||
|
||||
variables:
|
||||
OFFICIAL_REPO: 'pyqtgraph/pyqtgraph'
|
||||
DEFAULT_MERGE_BRANCH: 'develop'
|
||||
disable.coverage.autogenerate: 'true'
|
||||
|
||||
stages:
|
||||
- stage: "pre_test"
|
||||
jobs:
|
||||
- job: check_diff_size
|
||||
pool:
|
||||
vmImage: 'Ubuntu 18.04'
|
||||
steps:
|
||||
- bash: |
|
||||
git config --global advice.detachedHead false
|
||||
mkdir ~/repo-clone && cd ~/repo-clone
|
||||
git init
|
||||
|
||||
git remote add -t $(Build.SourceBranchName) origin $(Build.Repository.Uri)
|
||||
git remote add -t ${DEFAULT_MERGE_BRANCH} upstream https://github.com/${OFFICIAL_REPO}.git
|
||||
|
||||
git fetch origin $(Build.SourceBranchName)
|
||||
git fetch upstream ${DEFAULT_MERGE_BRANCH}
|
||||
|
||||
git checkout $(Build.SourceBranchName)
|
||||
MERGE_SIZE=`du -s . | sed -e "s/\t.*//"`
|
||||
echo -e "Merge Size ${MERGE_SIZE}"
|
||||
|
||||
git checkout ${DEFAULT_MERGE_BRANCH}
|
||||
TARGET_SIZE=`du -s . | sed -e "s/\t.*//"`
|
||||
echo -e "Target Size ${TARGET_SIZE}"
|
||||
|
||||
if [ "${MERGE_SIZE}" != "${TARGET_SIZE}" ]; then
|
||||
SIZE_DIFF=`expr \( ${MERGE_SIZE} - ${TARGET_SIZE} \)`;
|
||||
else
|
||||
SIZE_DIFF=0;
|
||||
fi;
|
||||
echo -e "Estimated content size difference = ${SIZE_DIFF} kB" &&
|
||||
test ${SIZE_DIFF} -lt 100;
|
||||
displayName: 'Diff Size Check'
|
||||
continueOnError: true
|
||||
|
||||
- job: "style_check"
|
||||
pool:
|
||||
vmImage: "Ubuntu 18.04"
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: 3.7
|
||||
- bash: |
|
||||
pip install flake8
|
||||
python setup.py style
|
||||
displayName: 'flake8 check'
|
||||
continueOnError: true
|
||||
|
||||
- job: "build_wheel"
|
||||
pool:
|
||||
vmImage: 'Ubuntu 18.04'
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: 3.8
|
||||
- script: |
|
||||
python -m pip install setuptools wheel
|
||||
python setup.py bdist_wheel --universal
|
||||
displayName: "Build Python Wheel"
|
||||
continueOnError: false
|
||||
- publish: dist
|
||||
artifact: wheel
|
||||
|
||||
- stage: "test"
|
||||
jobs:
|
||||
- template: azure-test-template.yml
|
||||
parameters:
|
||||
name: linux
|
||||
vmImage: 'Ubuntu 18.04'
|
||||
- template: azure-test-template.yml
|
||||
parameters:
|
||||
name: windows
|
||||
vmImage: 'windows-2019'
|
||||
- template: azure-test-template.yml
|
||||
parameters:
|
||||
name: macOS
|
||||
vmImage: 'macOS-10.15'
|
203
azure-test-template.yml
Normal file
203
azure-test-template.yml
Normal file
@ -0,0 +1,203 @@
|
||||
# Azure Pipelines CI job template for PyDM Tests
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/languages/anaconda?view=azure-devops
|
||||
parameters:
|
||||
name: ''
|
||||
vmImage: ''
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
pool:
|
||||
vmImage: ${{ parameters.vmImage }}
|
||||
strategy:
|
||||
matrix:
|
||||
Python27-PyQt4-4.8:
|
||||
python.version: '2.7'
|
||||
qt.bindings: "pyqt=4"
|
||||
install.method: "conda"
|
||||
Python27-PySide-4.8:
|
||||
python.version: '2.7'
|
||||
qt.bindings: "pyside"
|
||||
install.method: "conda"
|
||||
Python36-PyQt5-5.9:
|
||||
python.version: "3.6"
|
||||
qt.bindings: "pyqt"
|
||||
install.method: "conda"
|
||||
Python37-PySide2-5.13:
|
||||
python.version: "3.7"
|
||||
qt.bindings: "pyside2"
|
||||
install.method: "conda"
|
||||
Python38-PyQt5-Latest:
|
||||
python.version: '3.8'
|
||||
qt.bindings: "PyQt5"
|
||||
install.method: "pip"
|
||||
Python38-PySide2-Latest:
|
||||
python.version: '3.8'
|
||||
qt.bindings: "PySide2"
|
||||
install.method: "pip"
|
||||
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
source: 'current'
|
||||
artifact: wheel
|
||||
path: 'dist'
|
||||
|
||||
- task: ScreenResolutionUtility@1
|
||||
inputs:
|
||||
displaySettings: 'specific'
|
||||
width: '1920'
|
||||
height: '1080'
|
||||
condition: eq(variables['agent.os'], 'Windows_NT' )
|
||||
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: $(python.version)
|
||||
condition: eq(variables['install.method'], 'pip')
|
||||
|
||||
- script: |
|
||||
curl -LJO https://github.com/pal1000/mesa-dist-win/releases/download/19.1.0/mesa3d-19.1.0-release-msvc.exe
|
||||
7z x mesa3d-19.1.0-release-msvc.exe
|
||||
cd x64
|
||||
xcopy opengl32.dll C:\windows\system32\mesadrv.dll*
|
||||
xcopy opengl32.dll C:\windows\syswow64\mesadrv.dll*
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DLL /t REG_SZ /d "mesadrv.dll" /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DriverVersion /t REG_DWORD /d 1 /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Flags /t REG_DWORD /d 1 /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Version /t REG_DWORD /d 2 /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DLL /t REG_SZ /d "mesadrv.dll" /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v DriverVersion /t REG_DWORD /d 1 /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Flags /t REG_DWORD /d 1 /f
|
||||
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\OpenGLDrivers\MSOGL" /v Version /t REG_DWORD /d 2 /f
|
||||
displayName: "Install Windows-Mesa OpenGL DLL"
|
||||
condition: eq(variables['agent.os'], 'Windows_NT')
|
||||
|
||||
- bash: |
|
||||
if [ $(agent.os) == 'Linux' ]
|
||||
then
|
||||
echo "##vso[task.prependpath]$CONDA/bin"
|
||||
elif [ $(agent.os) == 'Darwin' ]
|
||||
then
|
||||
sudo chown -R $USER $CONDA
|
||||
echo "##vso[task.prependpath]$CONDA/bin"
|
||||
elif [ $(agent.os) == 'Windows_NT' ]
|
||||
then
|
||||
echo "##vso[task.prependpath]$CONDA/Scripts"
|
||||
else
|
||||
echo 'Just what OS are you using?'
|
||||
fi
|
||||
displayName: 'Add Conda To $PATH'
|
||||
condition: eq(variables['install.method'], 'conda' )
|
||||
continueOnError: false
|
||||
|
||||
- bash: |
|
||||
if [ $(install.method) == "conda" ]
|
||||
then
|
||||
conda update --all --yes --quiet
|
||||
conda create --name test-environment-$(python.version) python=$(python.version) --yes --quiet
|
||||
source activate test-environment-$(python.version)
|
||||
conda config --env --set always_yes true
|
||||
if [ $(python.version) == '2.7' ]
|
||||
then
|
||||
conda config --set restore_free_channel true
|
||||
fi
|
||||
if [ $(qt.bindings) == "pyside2" ] || ([ $(qt.bindings) == 'pyside' ] && [ $(agent.os) == 'Darwin' ])
|
||||
then
|
||||
conda config --prepend channels conda-forge
|
||||
fi
|
||||
conda info
|
||||
conda install $(qt.bindings) numpy scipy pyopengl h5py six --yes --quiet
|
||||
else
|
||||
pip install $(qt.bindings) numpy scipy pyopengl h5py six
|
||||
fi
|
||||
pip install pytest pytest-cov coverage pytest-xdist
|
||||
if [ $(python.version) == "2.7" ]
|
||||
then
|
||||
pip install pytest-faulthandler==1.6.0
|
||||
export PYTEST_ADDOPTS="--faulthandler-timeout=15"
|
||||
else
|
||||
pip install pytest pytest-cov coverage
|
||||
fi
|
||||
displayName: "Install Dependencies"
|
||||
|
||||
- bash: |
|
||||
if [ $(install.method) == "conda" ]
|
||||
then
|
||||
source activate test-environment-$(python.version)
|
||||
fi
|
||||
python -m pip install --no-index --find-links=dist pyqtgraph
|
||||
displayName: 'Install Wheel'
|
||||
|
||||
- bash: |
|
||||
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" ]
|
||||
then
|
||||
source activate test-environment-$(python.version)
|
||||
fi
|
||||
pip install PyVirtualDisplay==0.2.5 pytest-xvfb
|
||||
displayName: "Virtual Display Setup"
|
||||
condition: eq(variables['agent.os'], 'Linux' )
|
||||
|
||||
- bash: |
|
||||
export QT_DEBUG_PLUGINS=1
|
||||
if [ $(install.method) == "conda" ]
|
||||
then
|
||||
source activate test-environment-$(python.version)
|
||||
fi
|
||||
echo python location: `which python`
|
||||
echo python version: `python --version`
|
||||
echo pytest location: `which pytest`
|
||||
echo installed packages
|
||||
pip list
|
||||
echo pyqtgraph system info
|
||||
python -c "import pyqtgraph as pg; pg.systemInfo()"
|
||||
echo display information
|
||||
if [ $(agent.os) == 'Linux' ]
|
||||
then
|
||||
export DISPLAY=:99.0
|
||||
Xvfb :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset &
|
||||
sleep 3
|
||||
fi
|
||||
python -m pyqtgraph.util.get_resolution
|
||||
echo openGL information
|
||||
python -c "from pyqtgraph.opengl.glInfo import GLTest"
|
||||
displayName: 'Debug Info'
|
||||
continueOnError: false
|
||||
|
||||
- bash: |
|
||||
if [ $(install.method) == "conda" ]
|
||||
then
|
||||
source activate test-environment-$(python.version)
|
||||
fi
|
||||
mkdir -p "$SCREENSHOT_DIR"
|
||||
# echo "If Screenshots are generated, they may be downloaded from:"
|
||||
# echo "https://dev.azure.com/pyqtgraph/pyqtgraph/_apis/build/builds/$(Build.BuildId)/artifacts?artifactName=Screenshots&api-version=5.0"
|
||||
pytest . -v \
|
||||
-n 1 \
|
||||
--junitxml=junit/test-results.xml \
|
||||
--cov pyqtgraph --cov-report=xml --cov-report=html
|
||||
displayName: 'Unit tests'
|
||||
env:
|
||||
AZURE: 1
|
||||
SCREENSHOT_DIR: $(Build.ArtifactStagingDirectory)/screenshots
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Screenshots'
|
||||
condition: failed()
|
||||
inputs:
|
||||
pathtoPublish: $(Build.ArtifactStagingDirectory)/screenshots
|
||||
artifactName: Screenshots
|
||||
|
||||
- task: PublishTestResults@2
|
||||
condition: succeededOrFailed()
|
||||
inputs:
|
||||
testResultsFiles: '**/test-*.xml'
|
||||
testRunTitle: 'Test Results for $(agent.os) - $(python.version) - $(qt.bindings) - $(install.method)'
|
||||
publishRunAttachments: true
|
||||
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: '$(System.DefaultWorkingDirectory)/**/coverage.xml'
|
||||
reportDirectory: '$(System.DefaultWorkingDirectory)/**/htmlcov'
|
5
doc/requirements.txt
Normal file
5
doc/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
pyside2
|
||||
numpy
|
||||
pyopengl
|
||||
sphinx
|
||||
sphinx_rtd_theme
|
14
doc/source/_static/custom.css
Normal file
14
doc/source/_static/custom.css
Normal file
@ -0,0 +1,14 @@
|
||||
/* Customizations to the theme */
|
||||
|
||||
/* override table width restrictions */
|
||||
/* https://github.com/readthedocs/sphinx_rtd_theme/issues/117 */
|
||||
@media screen and (min-width: 768px) {
|
||||
.wy-table-responsive table td, .wy-table-responsive table th {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
overflow: visible !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
@ -13,5 +13,7 @@ Contents:
|
||||
3dgraphics/index
|
||||
colormap
|
||||
parametertree/index
|
||||
dockarea
|
||||
graphicsscene/index
|
||||
flowchart/index
|
||||
graphicswindow
|
||||
|
@ -11,7 +11,9 @@
|
||||
# All configuration values have a default; values that are commented out
|
||||
# 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,
|
||||
# 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__))
|
||||
sys.path.insert(0, os.path.join(path, '..', '..'))
|
||||
sys.path.insert(0, os.path.join(path, '..', 'extensions'))
|
||||
import pyqtgraph
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
@ -43,16 +46,16 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
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
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.10.0'
|
||||
version = pyqtgraph.__version__
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.10.0'
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@ -88,12 +91,18 @@ pygments_style = 'sphinx'
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
autodoc_inherit_docstrings = False
|
||||
autodoc_mock_imports = [
|
||||
"scipy",
|
||||
"h5py",
|
||||
"matplotlib",
|
||||
]
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@ -124,6 +133,10 @@ html_theme = 'default'
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# add the theme customizations
|
||||
def setup(app):
|
||||
app.add_stylesheet("custom.css")
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
@ -216,4 +229,3 @@ man_pages = [
|
||||
('index', 'pyqtgraph', 'pyqtgraph Documentation',
|
||||
['Luke Campagnola'], 1)
|
||||
]
|
||||
|
||||
|
11
doc/source/dockarea.rst
Normal file
11
doc/source/dockarea.rst
Normal file
@ -0,0 +1,11 @@
|
||||
Dock Area Module
|
||||
================
|
||||
|
||||
.. automodule:: pyqtgraph.dockarea
|
||||
:members:
|
||||
|
||||
.. autoclass:: pyqtgraph.dockarea.DockArea
|
||||
:members:
|
||||
|
||||
.. autoclass:: pyqtgraph.dockarea.Dock
|
||||
:members:
|
@ -30,8 +30,13 @@ Export Formats
|
||||
for export.
|
||||
* Printer - Exports to the operating system's printing service. This exporter is provided for completeness,
|
||||
but is not well supported due to problems with Qt's printing system.
|
||||
* HDF5 - Exports data from a :class:`~pyqtgraph.PlotItem` to a HDF5 file if
|
||||
h5py_ is installed. This exporter supports :class:`~pyqtgraph.PlotItem`
|
||||
objects containing multiple curves, stacking the data into a single HDF5
|
||||
dataset based on the ``columnMode`` parameter. If data items aren't the same
|
||||
size, each one is given its own dataset.
|
||||
|
||||
|
||||
.. _h5py: https://www.h5py.org/
|
||||
|
||||
Exporting from the API
|
||||
----------------------
|
||||
|
8
doc/source/graphicsItems/bargraphitem.rst
Normal file
8
doc/source/graphicsItems/bargraphitem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
BarGraphItem
|
||||
============
|
||||
|
||||
.. autoclass:: pyqtgraph.BarGraphItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.BarGraphItem.__init__
|
||||
|
8
doc/source/graphicsItems/dateaxisitem.rst
Normal file
8
doc/source/graphicsItems/dateaxisitem.rst
Normal file
@ -0,0 +1,8 @@
|
||||
DateAxisItem
|
||||
============
|
||||
|
||||
.. autoclass:: pyqtgraph.DateAxisItem
|
||||
:members:
|
||||
|
||||
.. automethod:: pyqtgraph.DateAxisItem.__init__
|
||||
|
@ -24,6 +24,7 @@ Contents:
|
||||
axisitem
|
||||
textitem
|
||||
errorbaritem
|
||||
bargraphitem
|
||||
arrowitem
|
||||
fillbetweenitem
|
||||
curvepoint
|
||||
@ -42,4 +43,4 @@ Contents:
|
||||
graphicsitem
|
||||
uigraphicsitem
|
||||
graphicswidgetanchor
|
||||
|
||||
dateaxisitem
|
||||
|
@ -2,6 +2,7 @@ files = """ArrowItem
|
||||
AxisItem
|
||||
ButtonItem
|
||||
CurvePoint
|
||||
DateAxisItem
|
||||
GradientEditorItem
|
||||
GradientLegend
|
||||
GraphicsLayout
|
||||
|
@ -1,8 +1,16 @@
|
||||
Basic display widgets
|
||||
=====================
|
||||
Deprecated Window Classes
|
||||
=========================
|
||||
|
||||
- GraphicsWindow
|
||||
- GraphicsView
|
||||
- GraphicsLayoutItem
|
||||
- ViewBox
|
||||
.. automodule:: pyqtgraph.graphicsWindows
|
||||
|
||||
.. autoclass:: pyqtgraph.GraphicsWindow
|
||||
:members:
|
||||
|
||||
.. autoclass:: pyqtgraph.TabWindow
|
||||
:members:
|
||||
|
||||
.. autoclass:: pyqtgraph.PlotWindow
|
||||
:members:
|
||||
|
||||
.. autoclass:: pyqtgraph.ImageWindow
|
||||
:members:
|
||||
|
@ -1,9 +1,70 @@
|
||||
Installation
|
||||
============
|
||||
|
||||
PyQtGraph does not really require any installation scripts. All that is needed is for the pyqtgraph folder to be placed someplace importable. Most people will prefer to simply place this folder within a larger project folder. If you want to make pyqtgraph available system-wide, use one of the methods listed below:
|
||||
PyQtGraph depends on:
|
||||
|
||||
* **Debian, Ubuntu, and similar Linux:** Download the .deb file linked at the top of the pyqtgraph web page or install using apt by putting "deb http://luke.campagnola.me/debian dev/" in your /etc/apt/sources.list file and install the python-pyqtgraph package.
|
||||
* **Arch Linux:** Looks like someone has posted unofficial packages for Arch (thanks windel). (https://aur.archlinux.org/packages.php?ID=62577)
|
||||
* **Windows:** Download and run the .exe installer file linked at the top of the pyqtgraph web page.
|
||||
* **Everybody (including OSX):** Download the .tar.gz source package linked at the top of the pyqtgraph web page, extract its contents, and run "python setup.py install" from within the extracted directory.
|
||||
* Python 2.7 or Python 3.x
|
||||
* A Qt library such as PyQt4, PyQt5, PySide, or PySide2
|
||||
* numpy
|
||||
|
||||
The easiest way to meet these dependencies is with ``pip`` or with a scientific
|
||||
python distribution like Anaconda.
|
||||
|
||||
There are many different ways to install pyqtgraph, depending on your needs:
|
||||
|
||||
pip
|
||||
---
|
||||
|
||||
The most common way to install pyqtgraph is with pip::
|
||||
|
||||
$ pip install pyqtgraph
|
||||
|
||||
Some users may need to call ``pip3`` instead. This method should work on all
|
||||
platforms.
|
||||
|
||||
conda
|
||||
-----
|
||||
|
||||
pyqtgraph is on the default Anaconda channel::
|
||||
|
||||
$ conda install pyqtgraph
|
||||
|
||||
It is also available in the conda-forge channel::
|
||||
|
||||
$ conda install -c conda-forge pyqtgraph
|
||||
|
||||
From Source
|
||||
-----------
|
||||
|
||||
To get access to the very latest features and bugfixes you have three choices:
|
||||
|
||||
1. Clone pyqtgraph from github::
|
||||
|
||||
$ git clone https://github.com/pyqtgraph/pyqtgraph
|
||||
$ cd pyqtgraph
|
||||
|
||||
Now you can install pyqtgraph from the source::
|
||||
|
||||
$ pip install .
|
||||
|
||||
2. Directly install from GitHub repo::
|
||||
|
||||
$ pip install git+git://github.com/pyqtgraph/pyqtgraph.git@develop
|
||||
|
||||
You can change ``develop`` of the above command to the branch name or the
|
||||
commit you prefer.
|
||||
|
||||
3. You can simply place the pyqtgraph folder someplace importable, such as
|
||||
inside the root of another project. PyQtGraph does not need to be "built" or
|
||||
compiled in any way.
|
||||
|
||||
Other Packages
|
||||
--------------
|
||||
|
||||
Packages for pyqtgraph are also available in a few other forms:
|
||||
|
||||
* **Debian, Ubuntu, and similar Linux:** Use ``apt install python-pyqtgraph`` or
|
||||
download the .deb file linked at the top of the pyqtgraph web page.
|
||||
* **Arch Linux:** https://www.archlinux.org/packages/community/any/python-pyqtgraph/
|
||||
* **Windows:** Download and run the .exe installer file linked at the top of the
|
||||
pyqtgraph web page: http://pyqtgraph.org
|
||||
|
@ -73,7 +73,7 @@ How does it compare to...
|
||||
such as image interaction, volumetric rendering, parameter trees,
|
||||
flowcharts, etc.
|
||||
|
||||
* pyqwt5: About as fast as pyqwt5, but not quite as complete for plotting
|
||||
* pyqwt5: About as fast as pyqtgraph, but not quite as complete for plotting
|
||||
functionality. Image handling in pyqtgraph is much more complete (again, no
|
||||
ROI widgets in qwt). Also, pyqtgraph is written in pure python, so it is
|
||||
more portable than pyqwt, which often lags behind pyqt in development (I
|
||||
|
@ -9,11 +9,11 @@ Most applications that use pyqtgraph's data visualization will generate widgets
|
||||
|
||||
In pyqtgraph, most 2D visualizations follow the following mouse interaction:
|
||||
|
||||
* Left button: Interacts with items in the scene (select/move objects, etc). If there are no movable objects under the mouse cursor, then dragging with the left button will pan the scene instead.
|
||||
* Right button drag: Scales the scene. Dragging left/right scales horizontally; dragging up/down scales vertically (although some scenes will have their x/y scales locked together). If there are x/y axes fisible in the scene, then right-dragging over the axis will _only_ affect that axis.
|
||||
* Right button click: Clicking the right button in most cases will show a context menu with a variety of options depending on the object(s) under the mouse cursor.
|
||||
* Middle button (or wheel) drag: Dragging the mouse with the wheel pressed down will always pan the scene (this is useful in instances where panning with the left button is prevented by other objects in the scene).
|
||||
* Wheel spin: Zooms the scene in and out.
|
||||
* **Left button:** Interacts with items in the scene (select/move objects, etc). If there are no movable objects under the mouse cursor, then dragging with the left button will pan the scene instead.
|
||||
* **Right button drag:** Scales the scene. Dragging left/right scales horizontally; dragging up/down scales vertically (although some scenes will have their x/y scales locked together). If there are x/y axes visible in the scene, then right-dragging over the axis will _only_ affect that axis.
|
||||
* **Right button click:** Clicking the right button in most cases will show a context menu with a variety of options depending on the object(s) under the mouse cursor.
|
||||
* **Middle button (or wheel) drag:** Dragging the mouse with the wheel pressed down will always pan the scene (this is useful in instances where panning with the left button is prevented by other objects in the scene).
|
||||
* **Wheel spin:** Zooms the scene in and out.
|
||||
|
||||
For machines where dragging with the right or middle buttons is difficult (usually Mac), another mouse interaction mode exists. In this mode, dragging with the left mouse button draws a box over a region of the scene. After the button is released, the scene is scaled and panned to fit the box. This mode can be accessed in the context menu or by calling::
|
||||
|
||||
@ -38,11 +38,11 @@ The exact set of items available in the menu depends on the contents of the scen
|
||||
|
||||
3D visualizations use the following mouse interaction:
|
||||
|
||||
* Left button drag: Rotates the scene around a central point
|
||||
* Middle button drag: Pan the scene by moving the central "look-at" point within the x-y plane
|
||||
* Middle button drag + CTRL: Pan the scene by moving the central "look-at" point along the z axis
|
||||
* Wheel spin: zoom in/out
|
||||
* Wheel + CTRL: change field-of-view angle
|
||||
* **Left button drag:** Rotates the scene around a central point
|
||||
* **Middle button drag:** Pan the scene by moving the central "look-at" point within the x-y plane
|
||||
* **Middle button drag + CTRL:** Pan the scene by moving the central "look-at" point along the z axis
|
||||
* **Wheel spin:** zoom in/out
|
||||
* **Wheel + CTRL:** change field-of-view angle
|
||||
|
||||
And keyboard controls:
|
||||
|
||||
|
@ -41,7 +41,7 @@ There are several classes invloved in displaying plot data. Most of these classe
|
||||
* :class:`AxisItem <pyqtgraph.AxisItem>` - Displays axis values, ticks, and labels. Most commonly used with PlotItem.
|
||||
* Container Classes (subclasses of QWidget; may be embedded in PyQt GUIs)
|
||||
* :class:`PlotWidget <pyqtgraph.PlotWidget>` - A subclass of GraphicsView with a single PlotItem displayed. Most of the methods provided by PlotItem are also available through PlotWidget.
|
||||
* :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>` - QWidget subclass displaying a single GraphicsLayoutItem. Most of the methods provided by GraphicsLayoutItem are also available through GraphicsLayoutWidget.
|
||||
* :class:`GraphicsLayoutWidget <pyqtgraph.GraphicsLayoutWidget>` - QWidget subclass displaying a single :class:`~pyqtgraph.GraphicsLayout`. Most of the methods provided by :class:`~pyqtgraph.GraphicsLayout` are also available through GraphicsLayoutWidget.
|
||||
|
||||
.. image:: images/plottingClasses.png
|
||||
|
||||
@ -69,5 +69,3 @@ Create/show a plot widget, display three data curves::
|
||||
for i in range(3):
|
||||
plotWidget.plot(x, y[i], pen=(i,3)) ## setting pen=(i,3) automaticaly creates three different-colored pens
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
dockarea module
|
||||
===============
|
||||
|
||||
.. automodule:: pyqtgraph.dockarea
|
||||
:members:
|
@ -12,11 +12,9 @@ Contents:
|
||||
|
||||
plotwidget
|
||||
imageview
|
||||
dockarea
|
||||
spinbox
|
||||
gradientwidget
|
||||
histogramlutwidget
|
||||
parametertree
|
||||
consolewidget
|
||||
colormapwidget
|
||||
scatterplotwidget
|
||||
|
@ -1,5 +0,0 @@
|
||||
parametertree module
|
||||
====================
|
||||
|
||||
.. automodule:: pyqtgraph.parametertree
|
||||
:members:
|
@ -12,7 +12,7 @@ import numpy as np
|
||||
# Enable antialiasing for prettier plots
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
||||
w = pg.GraphicsWindow()
|
||||
w = pg.GraphicsLayoutWidget(show=True)
|
||||
w.setWindowTitle('pyqtgraph example: CustomGraphItem')
|
||||
v = w.addViewBox()
|
||||
v.setAspectLocked()
|
||||
|
@ -11,15 +11,29 @@ from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
|
||||
# for generating a traceback object to display
|
||||
def some_func1():
|
||||
return some_func2()
|
||||
def some_func2():
|
||||
try:
|
||||
raise Exception()
|
||||
except:
|
||||
import sys
|
||||
return sys.exc_info()[2]
|
||||
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
d = {
|
||||
'list1': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||
'dict1': {
|
||||
'a list': [1,2,3,4,5,6, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||
'a dict': {
|
||||
'x': 1,
|
||||
'y': 2,
|
||||
'z': 'three'
|
||||
},
|
||||
'array1 (20x20)': np.ones((10,10))
|
||||
'an array': np.random.randint(10, size=(40,10)),
|
||||
'a traceback': some_func1(),
|
||||
'a function': some_func1,
|
||||
'a class': pg.DataTreeWidget,
|
||||
}
|
||||
|
||||
tree = pg.DataTreeWidget(data=d)
|
||||
|
33
examples/DateAxisItem.py
Normal file
33
examples/DateAxisItem.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
Demonstrates the usage of DateAxisItem to display properly-formatted
|
||||
timestamps on x-axis which automatically adapt to current zoom level.
|
||||
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtGui
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
# Create a plot with a date-time axis
|
||||
w = pg.PlotWidget(axisItems = {'bottom': pg.DateAxisItem()})
|
||||
w.showGrid(x=True, y=True)
|
||||
|
||||
# Plot sin(1/x^2) with timestamps in the last 100 years
|
||||
now = time.time()
|
||||
x = np.linspace(2*np.pi, 1000*2*np.pi, 8301)
|
||||
w.plot(now-(2*np.pi/x)**2*100*np.pi*1e7, np.sin(x), symbol='o')
|
||||
|
||||
w.setWindowTitle('pyqtgraph example: DateAxisItem')
|
||||
w.show()
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
app.exec_()
|
48
examples/DateAxisItem_QtDesigner.py
Normal file
48
examples/DateAxisItem_QtDesigner.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""
|
||||
Demonstrates the usage of DateAxisItem in a layout created with Qt Designer.
|
||||
|
||||
The spotlight here is on the 'setAxisItems' method, without which
|
||||
one would have to subclass plotWidget in order to attach a dateaxis to it.
|
||||
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
from PyQt5 import QtWidgets, QtCore, uic
|
||||
import pyqtgraph as pg
|
||||
|
||||
pg.setConfigOption('background', 'w')
|
||||
pg.setConfigOption('foreground', 'k')
|
||||
|
||||
BLUE = pg.mkPen('#1f77b4')
|
||||
|
||||
Design, _ = uic.loadUiType('DateAxisItem_QtDesigner.ui')
|
||||
|
||||
class ExampleApp(QtWidgets.QMainWindow, Design):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setupUi(self)
|
||||
now = time.time()
|
||||
# Plot random values with timestamps in the last 6 months
|
||||
timestamps = np.linspace(now - 6*30*24*3600, now, 100)
|
||||
self.curve = self.plotWidget.plot(x=timestamps, y=np.random.rand(100),
|
||||
symbol='o', symbolSize=5, pen=BLUE)
|
||||
# 'o' circle 't' triangle 'd' diamond '+' plus 's' square
|
||||
self.plotWidget.setAxisItems({'bottom': pg.DateAxisItem()})
|
||||
self.plotWidget.showGrid(x=True, y=True)
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
app.setStyle(QtWidgets.QStyleFactory.create('Fusion'))
|
||||
app.setPalette(QtWidgets.QApplication.style().standardPalette())
|
||||
window = ExampleApp()
|
||||
window.setWindowTitle('pyqtgraph example: DateAxisItem_QtDesigner')
|
||||
window.show()
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
app.exec_()
|
44
examples/DateAxisItem_QtDesigner.ui
Normal file
44
examples/DateAxisItem_QtDesigner.ui
Normal file
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>536</width>
|
||||
<height>381</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="PlotWidget" name="plotWidget"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>536</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>PlotWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
52
examples/DiffTreeWidget.py
Normal file
52
examples/DiffTreeWidget.py
Normal file
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Simple use of DiffTreeWidget to display differences between structures of
|
||||
nested dicts, lists, and arrays.
|
||||
"""
|
||||
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
A = {
|
||||
'a list': [1,2,2,4,5,6, {'nested1': 'aaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||
'a dict': {
|
||||
'x': 1,
|
||||
'y': 2,
|
||||
'z': 'three'
|
||||
},
|
||||
'an array': np.random.randint(10, size=(40,10)),
|
||||
#'a traceback': some_func1(),
|
||||
#'a function': some_func1,
|
||||
#'a class': pg.DataTreeWidget,
|
||||
}
|
||||
|
||||
B = {
|
||||
'a list': [1,2,3,4,5,5, {'nested1': 'aaaaa', 'nested2': 'bbbbb'}, "seven"],
|
||||
'a dict': {
|
||||
'x': 2,
|
||||
'y': 2,
|
||||
'z': 'three',
|
||||
'w': 5
|
||||
},
|
||||
'another dict': {1:2, 2:3, 3:4},
|
||||
'an array': np.random.randint(10, size=(40,10)),
|
||||
}
|
||||
|
||||
tree = pg.DiffTreeWidget()
|
||||
tree.setData(A, B)
|
||||
tree.show()
|
||||
tree.setWindowTitle('pyqtgraph example: DiffTreeWidget')
|
||||
tree.resize(1000, 800)
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
@ -26,9 +26,9 @@ data += pg.gaussianFilter(np.random.normal(size=shape), (15,15,15))*15
|
||||
|
||||
## slice out three planes, convert to RGBA for OpenGL texture
|
||||
levels = (-0.08, 0.08)
|
||||
tex1 = pg.makeRGBA(data[shape[0]/2], levels=levels)[0] # yz plane
|
||||
tex2 = pg.makeRGBA(data[:,shape[1]/2], levels=levels)[0] # xz plane
|
||||
tex3 = pg.makeRGBA(data[:,:,shape[2]/2], levels=levels)[0] # xy plane
|
||||
tex1 = pg.makeRGBA(data[shape[0]//2], levels=levels)[0] # yz plane
|
||||
tex2 = pg.makeRGBA(data[:,shape[1]//2], levels=levels)[0] # xz plane
|
||||
tex3 = pg.makeRGBA(data[:,:,shape[2]//2], levels=levels)[0] # xy plane
|
||||
#tex1[:,:,3] = 128
|
||||
#tex2[:,:,3] = 128
|
||||
#tex3[:,:,3] = 128
|
||||
|
@ -13,7 +13,7 @@ import numpy as np
|
||||
# Enable antialiasing for prettier plots
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
||||
w = pg.GraphicsWindow()
|
||||
w = pg.GraphicsLayoutWidget(show=True)
|
||||
w.setWindowTitle('pyqtgraph example: GraphItem')
|
||||
v = w.addViewBox()
|
||||
v.setAspectLocked()
|
||||
|
@ -28,19 +28,27 @@ v = pg.GraphicsView()
|
||||
vb = pg.ViewBox()
|
||||
vb.setAspectLocked()
|
||||
v.setCentralItem(vb)
|
||||
l.addWidget(v, 0, 0)
|
||||
l.addWidget(v, 0, 0, 3, 1)
|
||||
|
||||
w = pg.HistogramLUTWidget()
|
||||
l.addWidget(w, 0, 1)
|
||||
|
||||
data = pg.gaussianFilter(np.random.normal(size=(256, 256)), (20, 20))
|
||||
monoRadio = QtGui.QRadioButton('mono')
|
||||
rgbaRadio = QtGui.QRadioButton('rgba')
|
||||
l.addWidget(monoRadio, 1, 1)
|
||||
l.addWidget(rgbaRadio, 2, 1)
|
||||
monoRadio.setChecked(True)
|
||||
|
||||
def setLevelMode():
|
||||
mode = 'mono' if monoRadio.isChecked() else 'rgba'
|
||||
w.setLevelMode(mode)
|
||||
monoRadio.toggled.connect(setLevelMode)
|
||||
|
||||
data = pg.gaussianFilter(np.random.normal(size=(256, 256, 3)), (20, 20, 0))
|
||||
for i in range(32):
|
||||
for j in range(32):
|
||||
data[i*8, j*8] += .1
|
||||
img = pg.ImageItem(data)
|
||||
#data2 = np.zeros((2,) + data.shape + (2,))
|
||||
#data2[0,:,:,0] = data ## make non-contiguous array for testing purposes
|
||||
#img = pg.ImageItem(data2[0,:,:,0])
|
||||
vb.addItem(img)
|
||||
vb.autoRange()
|
||||
|
||||
|
@ -10,7 +10,7 @@ import pyqtgraph as pg
|
||||
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
win = pg.GraphicsWindow(title="Plotting items examples")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="Plotting items examples")
|
||||
win.resize(1000,600)
|
||||
|
||||
# Enable antialiasing for prettier plots
|
||||
|
@ -12,7 +12,7 @@ import pyqtgraph as pg
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
win = pg.GraphicsWindow(title="Basic plotting examples")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
|
||||
win.resize(1000,600)
|
||||
win.setWindowTitle('pyqtgraph example: LogPlotTest')
|
||||
|
||||
|
@ -12,32 +12,27 @@ from pyqtgraph.Qt import QtGui, QtCore
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.ptime import time
|
||||
#QtGui.QApplication.setGraphicsSystem('raster')
|
||||
app = QtGui.QApplication([])
|
||||
#mw = QtGui.QMainWindow()
|
||||
#mw.resize(800,800)
|
||||
|
||||
p = pg.plot()
|
||||
p.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
|
||||
#p.setRange(QtCore.QRectF(0, -10, 5000, 20))
|
||||
p.setLabel('bottom', 'Index', units='B')
|
||||
plot = pg.plot()
|
||||
plot.setWindowTitle('pyqtgraph example: MultiPlotSpeedTest')
|
||||
plot.setLabel('bottom', 'Index', units='B')
|
||||
|
||||
nPlots = 100
|
||||
nSamples = 500
|
||||
#curves = [p.plot(pen=(i,nPlots*1.3)) for i in range(nPlots)]
|
||||
curves = []
|
||||
for i in range(nPlots):
|
||||
c = pg.PlotCurveItem(pen=(i,nPlots*1.3))
|
||||
p.addItem(c)
|
||||
c.setPos(0,i*6)
|
||||
curves.append(c)
|
||||
for idx in range(nPlots):
|
||||
curve = pg.PlotCurveItem(pen=(idx,nPlots*1.3))
|
||||
plot.addItem(curve)
|
||||
curve.setPos(0,idx*6)
|
||||
curves.append(curve)
|
||||
|
||||
p.setYRange(0, nPlots*6)
|
||||
p.setXRange(0, nSamples)
|
||||
p.resize(600,900)
|
||||
plot.setYRange(0, nPlots*6)
|
||||
plot.setXRange(0, nSamples)
|
||||
plot.resize(600,900)
|
||||
|
||||
rgn = pg.LinearRegionItem([nSamples/5.,nSamples/3.])
|
||||
p.addItem(rgn)
|
||||
plot.addItem(rgn)
|
||||
|
||||
|
||||
data = np.random.normal(size=(nPlots*23,nSamples))
|
||||
@ -46,13 +41,12 @@ lastTime = time()
|
||||
fps = None
|
||||
count = 0
|
||||
def update():
|
||||
global curve, data, ptr, p, lastTime, fps, nPlots, count
|
||||
global curve, data, ptr, plot, lastTime, fps, nPlots, count
|
||||
count += 1
|
||||
#print "---------", count
|
||||
|
||||
for i in range(nPlots):
|
||||
curves[i].setData(data[(ptr+i)%data.shape[0]])
|
||||
|
||||
#print " setData done."
|
||||
ptr += nPlots
|
||||
now = time()
|
||||
dt = now - lastTime
|
||||
@ -62,14 +56,12 @@ def update():
|
||||
else:
|
||||
s = np.clip(dt*3., 0, 1)
|
||||
fps = fps * (1-s) + (1.0/dt) * s
|
||||
p.setTitle('%0.2f fps' % fps)
|
||||
plot.setTitle('%0.2f fps' % fps)
|
||||
#app.processEvents() ## force complete redraw for every plot
|
||||
timer = QtCore.QTimer()
|
||||
timer.timeout.connect(update)
|
||||
timer.start(0)
|
||||
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
@ -3,8 +3,7 @@
|
||||
## Add path to library (just for examples; you do not need this)
|
||||
import initExample
|
||||
|
||||
|
||||
from scipy import random
|
||||
import numpy as np
|
||||
from numpy import linspace
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
@ -22,7 +21,7 @@ pw = MultiPlotWidget()
|
||||
mw.setCentralWidget(pw)
|
||||
mw.show()
|
||||
|
||||
data = random.normal(size=(3, 1000)) * np.array([[0.1], [1e-5], [1]])
|
||||
data = np.random.normal(size=(3, 1000)) * np.array([[0.1], [1e-5], [1]])
|
||||
ma = MetaArray(data, info=[
|
||||
{'name': 'Signal', 'cols': [
|
||||
{'name': 'Col1', 'units': 'V'},
|
||||
|
@ -9,7 +9,7 @@ import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: PanningPlot')
|
||||
|
||||
plt = win.addPlot()
|
||||
|
@ -16,7 +16,7 @@ app = QtGui.QApplication([])
|
||||
#mw = QtGui.QMainWindow()
|
||||
#mw.resize(800,800)
|
||||
|
||||
win = pg.GraphicsWindow(title="Plot auto-range examples")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="Plot auto-range examples")
|
||||
win.resize(800,600)
|
||||
win.setWindowTitle('pyqtgraph example: PlotAutoRange')
|
||||
|
||||
|
@ -13,7 +13,7 @@ import numpy as np
|
||||
import pyqtgraph as pg
|
||||
|
||||
#QtGui.QApplication.setGraphicsSystem('raster')
|
||||
app = QtGui.QApplication([])
|
||||
app = pg.mkQApp()
|
||||
mw = QtGui.QMainWindow()
|
||||
mw.setWindowTitle('pyqtgraph example: PlotWidget')
|
||||
mw.resize(800,800)
|
||||
|
@ -17,7 +17,7 @@ app = QtGui.QApplication([])
|
||||
#mw = QtGui.QMainWindow()
|
||||
#mw.resize(800,800)
|
||||
|
||||
win = pg.GraphicsWindow(title="Basic plotting examples")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="Basic plotting examples")
|
||||
win.resize(1000,600)
|
||||
win.setWindowTitle('pyqtgraph example: Plotting')
|
||||
|
||||
|
53
examples/ProgressDialog.py
Normal file
53
examples/ProgressDialog.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Using ProgressDialog to show progress updates in a nested process.
|
||||
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
import time
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
|
||||
def runStage(i):
|
||||
"""Waste time for 2 seconds while incrementing a progress bar.
|
||||
"""
|
||||
with pg.ProgressDialog("Running stage %s.." % i, maximum=100, nested=True) as dlg:
|
||||
for j in range(100):
|
||||
time.sleep(0.02)
|
||||
dlg += 1
|
||||
if dlg.wasCanceled():
|
||||
print("Canceled stage %s" % i)
|
||||
break
|
||||
|
||||
|
||||
def runManyStages(i):
|
||||
"""Iterate over runStage() 3 times while incrementing a progress bar.
|
||||
"""
|
||||
with pg.ProgressDialog("Running stage %s.." % i, maximum=3, nested=True, wait=0) as dlg:
|
||||
for j in range(1,4):
|
||||
runStage('%d.%d' % (i, j))
|
||||
dlg += 1
|
||||
if dlg.wasCanceled():
|
||||
print("Canceled stage %s" % i)
|
||||
break
|
||||
|
||||
|
||||
with pg.ProgressDialog("Doing a multi-stage process..", maximum=5, nested=True, wait=0) as dlg1:
|
||||
for i in range(1,6):
|
||||
if i == 3:
|
||||
# this stage will have 3 nested progress bars
|
||||
runManyStages(i)
|
||||
else:
|
||||
# this stage will have 2 nested progress bars
|
||||
runStage(i)
|
||||
|
||||
dlg1 += 1
|
||||
if dlg1.wasCanceled():
|
||||
print("Canceled process")
|
||||
break
|
||||
|
||||
|
@ -33,7 +33,7 @@ arr[8:13, 44:46] = 10
|
||||
|
||||
## create GUI
|
||||
app = QtGui.QApplication([])
|
||||
w = pg.GraphicsWindow(size=(1000,800), border=True)
|
||||
w = pg.GraphicsLayoutWidget(show=True, size=(1000,800), border=True)
|
||||
w.setWindowTitle('pyqtgraph example: ROI Examples')
|
||||
|
||||
text = """Data Selection From Image.<br>\n
|
||||
@ -138,7 +138,7 @@ label4 = w4.addLabel(text, row=0, col=0)
|
||||
v4 = w4.addViewBox(row=1, col=0, lockAspect=True)
|
||||
g = pg.GridItem()
|
||||
v4.addItem(g)
|
||||
r4 = pg.ROI([0,0], [100,100], removable=True)
|
||||
r4 = pg.ROI([0,0], [100,100], resizable=False, removable=True)
|
||||
r4.addRotateHandle([1,0], [0.5, 0.5])
|
||||
r4.addRotateHandle([0,1], [0.5, 0.5])
|
||||
img4 = pg.ImageItem(arr)
|
||||
|
@ -13,7 +13,7 @@ pg.setConfigOptions(imageAxisOrder='row-major')
|
||||
## create GUI
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
w = pg.GraphicsWindow(size=(800,800), border=True)
|
||||
w = pg.GraphicsLayoutWidget(show=True, size=(800,800), border=True)
|
||||
v = w.addViewBox(colspan=2)
|
||||
v.invertY(True) ## Images usually have their Y-axis pointing downward
|
||||
v.setAspectLocked(True)
|
||||
@ -92,10 +92,10 @@ def updateRoiPlot(roi, data=None):
|
||||
rois = []
|
||||
rois.append(pg.TestROI([0, 0], [20, 20], maxBounds=QtCore.QRectF(-10, -10, 230, 140), pen=(0,9)))
|
||||
rois.append(pg.LineROI([0, 0], [20, 20], width=5, pen=(1,9)))
|
||||
rois.append(pg.MultiLineROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9)))
|
||||
rois.append(pg.MultiRectROI([[0, 50], [50, 60], [60, 30]], width=5, pen=(2,9)))
|
||||
rois.append(pg.EllipseROI([110, 10], [30, 20], pen=(3,9)))
|
||||
rois.append(pg.CircleROI([110, 50], [20, 20], pen=(4,9)))
|
||||
rois.append(pg.PolygonROI([[2,0], [2.1,0], [2,.1]], pen=(5,9)))
|
||||
rois.append(pg.PolyLineROI([[2,0], [2.1,0], [2,.1]], pen=(5,9)))
|
||||
#rois.append(SpiralROI([20,30], [1,1], pen=mkPen(0)))
|
||||
|
||||
## Add each ROI to the scene and link its data to a plot curve with the same color
|
||||
|
@ -9,7 +9,7 @@ from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
pg.mkQApp()
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: ScaleBar')
|
||||
|
||||
vb = win.addViewBox()
|
||||
|
@ -11,6 +11,7 @@ import initExample
|
||||
from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
from collections import namedtuple
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
mw = QtGui.QMainWindow()
|
||||
@ -62,10 +63,30 @@ s1.sigClicked.connect(clicked)
|
||||
## overhead and memory usage since each spot generates its own pre-rendered
|
||||
## image.
|
||||
|
||||
TextSymbol = namedtuple("TextSymbol", "label symbol scale")
|
||||
|
||||
def createLabel(label, angle):
|
||||
symbol = QtGui.QPainterPath()
|
||||
#symbol.addText(0, 0, QFont("San Serif", 10), label)
|
||||
f = QtGui.QFont()
|
||||
f.setPointSize(10)
|
||||
symbol.addText(0, 0, f, label)
|
||||
br = symbol.boundingRect()
|
||||
scale = min(1. / br.width(), 1. / br.height())
|
||||
tr = QtGui.QTransform()
|
||||
tr.scale(scale, scale)
|
||||
tr.rotate(angle)
|
||||
tr.translate(-br.x() - br.width()/2., -br.y() - br.height()/2.)
|
||||
return TextSymbol(label, tr.map(symbol), 0.1 / scale)
|
||||
|
||||
random_str = lambda : (''.join([chr(np.random.randint(ord('A'),ord('z'))) for i in range(np.random.randint(1,5))]), np.random.randint(0, 360))
|
||||
|
||||
s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True)
|
||||
pos = np.random.normal(size=(2,n), scale=1e-5)
|
||||
spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%5, 'size': 5+i/10.} for i in range(n)]
|
||||
s2.addPoints(spots)
|
||||
spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': label[1], 'size': label[2]*(5+i/10.)} for (i, label) in [(i, createLabel(*random_str())) for i in range(n)]]
|
||||
s2.addPoints(spots)
|
||||
w2.addItem(s2)
|
||||
s2.sigClicked.connect(clicked)
|
||||
|
||||
|
@ -12,7 +12,7 @@ For testing rapid updates of ScatterPlotItem under various conditions.
|
||||
import initExample
|
||||
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.ptime import time
|
||||
@ -20,9 +20,11 @@ from pyqtgraph.ptime import time
|
||||
app = QtGui.QApplication([])
|
||||
#mw = QtGui.QMainWindow()
|
||||
#mw.resize(800,800)
|
||||
if USE_PYSIDE:
|
||||
if QT_LIB == 'PySide':
|
||||
from ScatterPlotSpeedTestTemplate_pyside import Ui_Form
|
||||
elif USE_PYQT5:
|
||||
elif QT_LIB == 'PySide2':
|
||||
from ScatterPlotSpeedTestTemplate_pyside2 import Ui_Form
|
||||
elif QT_LIB == 'PyQt5':
|
||||
from ScatterPlotSpeedTestTemplate_pyqt5 import Ui_Form
|
||||
else:
|
||||
from ScatterPlotSpeedTestTemplate_pyqt import Ui_Form
|
||||
|
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string>PyQtGraph</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
|
@ -41,7 +41,7 @@ class Ui_Form(object):
|
||||
QtCore.QMetaObject.connectSlotsByName(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.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
44
examples/ScatterPlotSpeedTestTemplate_pyqt5.py
Normal file
44
examples/ScatterPlotSpeedTestTemplate_pyqt5.py
Normal file
@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'ScatterPlotSpeedTestTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 18 19:21:36 2016
|
||||
# by: pyside2-uic running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(400, 300)
|
||||
self.gridLayout = QtWidgets.QGridLayout(Form)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.sizeSpin = QtWidgets.QSpinBox(Form)
|
||||
self.sizeSpin.setProperty("value", 10)
|
||||
self.sizeSpin.setObjectName("sizeSpin")
|
||||
self.gridLayout.addWidget(self.sizeSpin, 1, 1, 1, 1)
|
||||
self.pixelModeCheck = QtWidgets.QCheckBox(Form)
|
||||
self.pixelModeCheck.setObjectName("pixelModeCheck")
|
||||
self.gridLayout.addWidget(self.pixelModeCheck, 1, 3, 1, 1)
|
||||
self.label = QtWidgets.QLabel(Form)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
|
||||
self.plot = PlotWidget(Form)
|
||||
self.plot.setObjectName("plot")
|
||||
self.gridLayout.addWidget(self.plot, 0, 0, 1, 4)
|
||||
self.randCheck = QtWidgets.QCheckBox(Form)
|
||||
self.randCheck.setObjectName("randCheck")
|
||||
self.gridLayout.addWidget(self.randCheck, 1, 2, 1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
|
||||
self.pixelModeCheck.setText(QtWidgets.QApplication.translate("Form", "pixel mode", None, -1))
|
||||
self.label.setText(QtWidgets.QApplication.translate("Form", "Size", None, -1))
|
||||
self.randCheck.setText(QtWidgets.QApplication.translate("Form", "Randomize", None, -1))
|
||||
|
||||
from pyqtgraph import PlotWidget
|
@ -36,7 +36,7 @@ class Ui_Form(object):
|
||||
QtCore.QMetaObject.connectSlotsByName(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.label.setText(QtGui.QApplication.translate("Form", "Size", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.randCheck.setText(QtGui.QApplication.translate("Form", "Randomize", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
44
examples/ScatterPlotSpeedTestTemplate_pyside2.py
Normal file
44
examples/ScatterPlotSpeedTestTemplate_pyside2.py
Normal file
@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'ScatterPlotSpeedTestTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 18 19:21:36 2016
|
||||
# by: pyside2-uic running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(400, 300)
|
||||
self.gridLayout = QtWidgets.QGridLayout(Form)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.sizeSpin = QtWidgets.QSpinBox(Form)
|
||||
self.sizeSpin.setProperty("value", 10)
|
||||
self.sizeSpin.setObjectName("sizeSpin")
|
||||
self.gridLayout.addWidget(self.sizeSpin, 1, 1, 1, 1)
|
||||
self.pixelModeCheck = QtWidgets.QCheckBox(Form)
|
||||
self.pixelModeCheck.setObjectName("pixelModeCheck")
|
||||
self.gridLayout.addWidget(self.pixelModeCheck, 1, 3, 1, 1)
|
||||
self.label = QtWidgets.QLabel(Form)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
|
||||
self.plot = PlotWidget(Form)
|
||||
self.plot.setObjectName("plot")
|
||||
self.gridLayout.addWidget(self.plot, 0, 0, 1, 4)
|
||||
self.randCheck = QtWidgets.QCheckBox(Form)
|
||||
self.randCheck.setObjectName("randCheck")
|
||||
self.gridLayout.addWidget(self.randCheck, 1, 2, 1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
|
||||
self.pixelModeCheck.setText(QtWidgets.QApplication.translate("Form", "pixel mode", None, -1))
|
||||
self.label.setText(QtWidgets.QApplication.translate("Form", "Size", None, -1))
|
||||
self.randCheck.setText(QtWidgets.QApplication.translate("Form", "Randomize", None, -1))
|
||||
|
||||
from pyqtgraph import PlotWidget
|
@ -28,7 +28,7 @@ pg.mkQApp()
|
||||
# Make up some tabular data with structure
|
||||
data = np.empty(1000, dtype=[('x_pos', float), ('y_pos', float),
|
||||
('count', int), ('amplitude', float),
|
||||
('decay', float), ('type', 'S10')])
|
||||
('decay', float), ('type', 'U10')])
|
||||
strings = ['Type-A', 'Type-B', 'Type-C', 'Type-D', 'Type-E']
|
||||
typeInds = np.random.randint(5, size=1000)
|
||||
data['type'] = np.array(strings)[typeInds]
|
||||
|
@ -26,7 +26,7 @@ spins = [
|
||||
("Float with SI-prefixed units<br>(n, u, m, k, M, etc)",
|
||||
pg.SpinBox(value=0.9, suffix='V', siPrefix=True)),
|
||||
("Float with SI-prefixed units,<br>dec step=0.1, minStep=0.1",
|
||||
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.1, minStep=0.1)),
|
||||
pg.SpinBox(value=1.0, suffix='PSI', siPrefix=True, dec=True, step=0.1, minStep=0.1)),
|
||||
("Float with SI-prefixed units,<br>dec step=0.5, minStep=0.01",
|
||||
pg.SpinBox(value=1.0, suffix='V', siPrefix=True, dec=True, step=0.5, minStep=0.01)),
|
||||
("Float with SI-prefixed units,<br>dec step=1.0, minStep=0.001",
|
||||
|
@ -11,7 +11,7 @@ from pyqtgraph.Qt import QtGui, QtCore
|
||||
import pyqtgraph as pg
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
win = pg.GraphicsWindow(title="Scatter Plot Symbols")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="Scatter Plot Symbols")
|
||||
win.resize(1000,600)
|
||||
|
||||
pg.setConfigOptions(antialias=True)
|
||||
|
@ -10,14 +10,16 @@ is used by the view widget
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
|
||||
from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE, USE_PYQT5
|
||||
from pyqtgraph.Qt import QtGui, QtCore, QT_LIB
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import pyqtgraph.ptime as ptime
|
||||
|
||||
if USE_PYSIDE:
|
||||
if QT_LIB == 'PySide':
|
||||
import VideoTemplate_pyside as VideoTemplate
|
||||
elif USE_PYQT5:
|
||||
elif QT_LIB == 'PySide2':
|
||||
import VideoTemplate_pyside2 as VideoTemplate
|
||||
elif QT_LIB == 'PyQt5':
|
||||
import VideoTemplate_pyqt5 as VideoTemplate
|
||||
else:
|
||||
import VideoTemplate_pyqt as VideoTemplate
|
||||
@ -101,6 +103,7 @@ def mkData():
|
||||
dt = np.float
|
||||
loc = 1.0
|
||||
scale = 0.1
|
||||
mx = 1.0
|
||||
|
||||
if ui.rgbCheck.isChecked():
|
||||
data = np.random.normal(size=(frames,width,height,3), loc=loc, scale=scale)
|
||||
|
207
examples/VideoTemplate_pyside2.py
Normal file
207
examples/VideoTemplate_pyside2.py
Normal file
@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'VideoTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 18 19:22:41 2016
|
||||
# by: pyside2-uic running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(695, 798)
|
||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.downsampleCheck.setObjectName("downsampleCheck")
|
||||
self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2)
|
||||
self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.scaleCheck.setObjectName("scaleCheck")
|
||||
self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1)
|
||||
self.gridLayout = QtWidgets.QGridLayout()
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.rawRadio = QtWidgets.QRadioButton(self.centralwidget)
|
||||
self.rawRadio.setObjectName("rawRadio")
|
||||
self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1)
|
||||
self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget)
|
||||
self.gfxRadio.setChecked(True)
|
||||
self.gfxRadio.setObjectName("gfxRadio")
|
||||
self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1)
|
||||
self.stack = QtWidgets.QStackedWidget(self.centralwidget)
|
||||
self.stack.setObjectName("stack")
|
||||
self.page = QtWidgets.QWidget()
|
||||
self.page.setObjectName("page")
|
||||
self.gridLayout_3 = QtWidgets.QGridLayout(self.page)
|
||||
self.gridLayout_3.setObjectName("gridLayout_3")
|
||||
self.graphicsView = GraphicsView(self.page)
|
||||
self.graphicsView.setObjectName("graphicsView")
|
||||
self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1)
|
||||
self.stack.addWidget(self.page)
|
||||
self.page_2 = QtWidgets.QWidget()
|
||||
self.page_2.setObjectName("page_2")
|
||||
self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2)
|
||||
self.gridLayout_4.setObjectName("gridLayout_4")
|
||||
self.rawImg = RawImageWidget(self.page_2)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth())
|
||||
self.rawImg.setSizePolicy(sizePolicy)
|
||||
self.rawImg.setObjectName("rawImg")
|
||||
self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1)
|
||||
self.stack.addWidget(self.page_2)
|
||||
self.page_3 = QtWidgets.QWidget()
|
||||
self.page_3.setObjectName("page_3")
|
||||
self.gridLayout_5 = QtWidgets.QGridLayout(self.page_3)
|
||||
self.gridLayout_5.setObjectName("gridLayout_5")
|
||||
self.rawGLImg = RawImageGLWidget(self.page_3)
|
||||
self.rawGLImg.setObjectName("rawGLImg")
|
||||
self.gridLayout_5.addWidget(self.rawGLImg, 0, 0, 1, 1)
|
||||
self.stack.addWidget(self.page_3)
|
||||
self.gridLayout.addWidget(self.stack, 0, 0, 1, 1)
|
||||
self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget)
|
||||
self.rawGLRadio.setObjectName("rawGLRadio")
|
||||
self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1)
|
||||
self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4)
|
||||
self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget)
|
||||
self.dtypeCombo.setObjectName("dtypeCombo")
|
||||
self.dtypeCombo.addItem("")
|
||||
self.dtypeCombo.addItem("")
|
||||
self.dtypeCombo.addItem("")
|
||||
self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1)
|
||||
self.label = QtWidgets.QLabel(self.centralwidget)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1)
|
||||
self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
|
||||
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.minSpin2 = SpinBox(self.centralwidget)
|
||||
self.minSpin2.setEnabled(False)
|
||||
self.minSpin2.setObjectName("minSpin2")
|
||||
self.horizontalLayout_2.addWidget(self.minSpin2)
|
||||
self.label_3 = QtWidgets.QLabel(self.centralwidget)
|
||||
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.horizontalLayout_2.addWidget(self.label_3)
|
||||
self.maxSpin2 = SpinBox(self.centralwidget)
|
||||
self.maxSpin2.setEnabled(False)
|
||||
self.maxSpin2.setObjectName("maxSpin2")
|
||||
self.horizontalLayout_2.addWidget(self.maxSpin2)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1)
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.minSpin1 = SpinBox(self.centralwidget)
|
||||
self.minSpin1.setObjectName("minSpin1")
|
||||
self.horizontalLayout.addWidget(self.minSpin1)
|
||||
self.label_2 = QtWidgets.QLabel(self.centralwidget)
|
||||
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.horizontalLayout.addWidget(self.label_2)
|
||||
self.maxSpin1 = SpinBox(self.centralwidget)
|
||||
self.maxSpin1.setObjectName("maxSpin1")
|
||||
self.horizontalLayout.addWidget(self.maxSpin1)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1)
|
||||
self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
|
||||
self.minSpin3 = SpinBox(self.centralwidget)
|
||||
self.minSpin3.setEnabled(False)
|
||||
self.minSpin3.setObjectName("minSpin3")
|
||||
self.horizontalLayout_3.addWidget(self.minSpin3)
|
||||
self.label_4 = QtWidgets.QLabel(self.centralwidget)
|
||||
self.label_4.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.horizontalLayout_3.addWidget(self.label_4)
|
||||
self.maxSpin3 = SpinBox(self.centralwidget)
|
||||
self.maxSpin3.setEnabled(False)
|
||||
self.maxSpin3.setObjectName("maxSpin3")
|
||||
self.horizontalLayout_3.addWidget(self.maxSpin3)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1)
|
||||
self.lutCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.lutCheck.setObjectName("lutCheck")
|
||||
self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1)
|
||||
self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.alphaCheck.setObjectName("alphaCheck")
|
||||
self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1)
|
||||
self.gradient = GradientWidget(self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth())
|
||||
self.gradient.setSizePolicy(sizePolicy)
|
||||
self.gradient.setObjectName("gradient")
|
||||
self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2)
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1)
|
||||
self.fpsLabel = QtWidgets.QLabel(self.centralwidget)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(12)
|
||||
self.fpsLabel.setFont(font)
|
||||
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.fpsLabel.setObjectName("fpsLabel")
|
||||
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
|
||||
self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.rgbCheck.setObjectName("rgbCheck")
|
||||
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
|
||||
self.label_5 = QtWidgets.QLabel(self.centralwidget)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1)
|
||||
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
|
||||
self.framesSpin = QtWidgets.QSpinBox(self.centralwidget)
|
||||
self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
||||
self.framesSpin.setProperty("value", 10)
|
||||
self.framesSpin.setObjectName("framesSpin")
|
||||
self.horizontalLayout_4.addWidget(self.framesSpin)
|
||||
self.widthSpin = QtWidgets.QSpinBox(self.centralwidget)
|
||||
self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.PlusMinus)
|
||||
self.widthSpin.setMaximum(10000)
|
||||
self.widthSpin.setProperty("value", 512)
|
||||
self.widthSpin.setObjectName("widthSpin")
|
||||
self.horizontalLayout_4.addWidget(self.widthSpin)
|
||||
self.heightSpin = QtWidgets.QSpinBox(self.centralwidget)
|
||||
self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
||||
self.heightSpin.setMaximum(10000)
|
||||
self.heightSpin.setProperty("value", 512)
|
||||
self.heightSpin.setObjectName("heightSpin")
|
||||
self.horizontalLayout_4.addWidget(self.heightSpin)
|
||||
self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2)
|
||||
self.sizeLabel = QtWidgets.QLabel(self.centralwidget)
|
||||
self.sizeLabel.setText("")
|
||||
self.sizeLabel.setObjectName("sizeLabel")
|
||||
self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1)
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
self.stack.setCurrentIndex(2)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None, -1))
|
||||
self.downsampleCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Auto downsample", None, -1))
|
||||
self.scaleCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Scale Data", None, -1))
|
||||
self.rawRadio.setText(QtWidgets.QApplication.translate("MainWindow", "RawImageWidget", None, -1))
|
||||
self.gfxRadio.setText(QtWidgets.QApplication.translate("MainWindow", "GraphicsView + ImageItem", None, -1))
|
||||
self.rawGLRadio.setText(QtWidgets.QApplication.translate("MainWindow", "RawGLImageWidget", None, -1))
|
||||
self.dtypeCombo.setItemText(0, QtWidgets.QApplication.translate("MainWindow", "uint8", None, -1))
|
||||
self.dtypeCombo.setItemText(1, QtWidgets.QApplication.translate("MainWindow", "uint16", None, -1))
|
||||
self.dtypeCombo.setItemText(2, QtWidgets.QApplication.translate("MainWindow", "float", None, -1))
|
||||
self.label.setText(QtWidgets.QApplication.translate("MainWindow", "Data type", None, -1))
|
||||
self.rgbLevelsCheck.setText(QtWidgets.QApplication.translate("MainWindow", "RGB", None, -1))
|
||||
self.label_3.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
||||
self.label_2.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
||||
self.label_4.setText(QtWidgets.QApplication.translate("MainWindow", "<--->", None, -1))
|
||||
self.lutCheck.setText(QtWidgets.QApplication.translate("MainWindow", "Use Lookup Table", None, -1))
|
||||
self.alphaCheck.setText(QtWidgets.QApplication.translate("MainWindow", "alpha", None, -1))
|
||||
self.fpsLabel.setText(QtWidgets.QApplication.translate("MainWindow", "FPS", None, -1))
|
||||
self.rgbCheck.setText(QtWidgets.QApplication.translate("MainWindow", "RGB", None, -1))
|
||||
self.label_5.setText(QtWidgets.QApplication.translate("MainWindow", "Image size", None, -1))
|
||||
|
||||
from pyqtgraph.widgets.RawImageWidget import RawImageGLWidget, RawImageWidget
|
||||
from pyqtgraph import GradientWidget, SpinBox, GraphicsView
|
@ -16,7 +16,7 @@ x = np.arange(1000, dtype=float)
|
||||
y = np.random.normal(size=1000)
|
||||
y += 5 * np.sin(x/100)
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: ____')
|
||||
win.resize(1000, 800)
|
||||
win.ci.setBorder((50, 50, 100))
|
||||
|
@ -7,18 +7,31 @@ if __name__ == "__main__" and (__package__ is None or __package__==''):
|
||||
import pyqtgraph as pg
|
||||
import subprocess
|
||||
from pyqtgraph.python2_3 import basestring
|
||||
from pyqtgraph.Qt import QtGui, USE_PYSIDE, USE_PYQT5
|
||||
from pyqtgraph.Qt import QtGui, QT_LIB
|
||||
|
||||
from .utils import buildFileList, path, examples
|
||||
from .syntax import PythonHighlighter
|
||||
|
||||
|
||||
from .utils import buildFileList, testFile, path, examples
|
||||
|
||||
if USE_PYSIDE:
|
||||
if QT_LIB == 'PySide':
|
||||
from .exampleLoaderTemplate_pyside import Ui_Form
|
||||
elif USE_PYQT5:
|
||||
elif QT_LIB == 'PySide2':
|
||||
from .exampleLoaderTemplate_pyside2 import Ui_Form
|
||||
elif QT_LIB == 'PyQt5':
|
||||
from .exampleLoaderTemplate_pyqt5 import Ui_Form
|
||||
else:
|
||||
from .exampleLoaderTemplate_pyqt import Ui_Form
|
||||
|
||||
class App(QtGui.QApplication):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.paletteChanged.connect(self.onPaletteChange)
|
||||
self.onPaletteChange(self.palette())
|
||||
|
||||
def onPaletteChange(self, palette):
|
||||
self.dark_mode = palette.base().color().name().lower() != "#ffffff"
|
||||
|
||||
class ExampleLoader(QtGui.QMainWindow):
|
||||
def __init__(self):
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
@ -26,10 +39,14 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||
self.cw = QtGui.QWidget()
|
||||
self.setCentralWidget(self.cw)
|
||||
self.ui.setupUi(self.cw)
|
||||
self.setWindowTitle("PyQtGraph Examples")
|
||||
|
||||
self.codeBtn = QtGui.QPushButton('Run Edited Code')
|
||||
self.codeLayout = QtGui.QGridLayout()
|
||||
self.ui.codeView.setLayout(self.codeLayout)
|
||||
self.hl = PythonHighlighter(self.ui.codeView.document())
|
||||
app = QtGui.QApplication.instance()
|
||||
app.paletteChanged.connect(self.updateTheme)
|
||||
self.codeLayout.addItem(QtGui.QSpacerItem(100,100,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding), 0, 0)
|
||||
self.codeLayout.addWidget(self.codeBtn, 1, 1)
|
||||
self.codeBtn.hide()
|
||||
@ -48,6 +65,28 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||
self.ui.codeView.textChanged.connect(self.codeEdited)
|
||||
self.codeBtn.clicked.connect(self.runEditedCode)
|
||||
|
||||
def simulate_black_mode(self):
|
||||
"""
|
||||
used to simulate MacOS "black mode" on other platforms
|
||||
intended for debug only, as it manage only the QPlainTextEdit
|
||||
"""
|
||||
# first, a dark background
|
||||
c = QtGui.QColor('#171717')
|
||||
p = self.ui.codeView.palette()
|
||||
p.setColor(QtGui.QPalette.Active, QtGui.QPalette.Base, c)
|
||||
p.setColor(QtGui.QPalette.Inactive, QtGui.QPalette.Base, c)
|
||||
self.ui.codeView.setPalette(p)
|
||||
# then, a light font
|
||||
f = QtGui.QTextCharFormat()
|
||||
f.setForeground(QtGui.QColor('white'))
|
||||
self.ui.codeView.setCurrentCharFormat(f)
|
||||
# finally, override application automatic detection
|
||||
app = QtGui.QApplication.instance()
|
||||
app.dark_mode = True
|
||||
|
||||
def updateTheme(self):
|
||||
self.hl = PythonHighlighter(self.ui.codeView.document())
|
||||
|
||||
def populateTree(self, root, examples):
|
||||
for key, val in examples.items():
|
||||
item = QtGui.QTreeWidgetItem([key])
|
||||
@ -112,32 +151,9 @@ class ExampleLoader(QtGui.QMainWindow):
|
||||
self.loadFile(edited=True)
|
||||
|
||||
def run():
|
||||
app = QtGui.QApplication([])
|
||||
app = App([])
|
||||
loader = ExampleLoader()
|
||||
|
||||
app.exec_()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
args = sys.argv[1:]
|
||||
|
||||
if '--test' in args:
|
||||
# get rid of orphaned cache files first
|
||||
pg.renamePyc(path)
|
||||
|
||||
files = buildFileList(examples)
|
||||
if '--pyside' in args:
|
||||
lib = 'PySide'
|
||||
elif '--pyqt' in args or '--pyqt4' in args:
|
||||
lib = 'PyQt4'
|
||||
elif '--pyqt5' in args:
|
||||
lib = 'PyQt5'
|
||||
else:
|
||||
lib = ''
|
||||
|
||||
exe = sys.executable
|
||||
print("Running tests:", lib, sys.executable)
|
||||
for f in files:
|
||||
testFile(f[0], f[1], exe, lib)
|
||||
else:
|
||||
run()
|
||||
|
@ -14,7 +14,7 @@ import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: context menu')
|
||||
|
||||
|
||||
|
@ -13,7 +13,7 @@ from pyqtgraph.Point import Point
|
||||
|
||||
#generate layout
|
||||
app = QtGui.QApplication([])
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: crosshair')
|
||||
label = pg.LabelItem(justify='right')
|
||||
win.addItem(label)
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This example demonstrates the creation of a plot with a customized
|
||||
AxisItem and ViewBox.
|
||||
This example demonstrates the creation of a plot with
|
||||
DateAxisItem and a customized ViewBox.
|
||||
"""
|
||||
|
||||
|
||||
@ -12,40 +12,6 @@ from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
class DateAxis(pg.AxisItem):
|
||||
def tickStrings(self, values, scale, spacing):
|
||||
strns = []
|
||||
rng = max(values)-min(values)
|
||||
#if rng < 120:
|
||||
# return pg.AxisItem.tickStrings(self, values, scale, spacing)
|
||||
if rng < 3600*24:
|
||||
string = '%H:%M:%S'
|
||||
label1 = '%b %d -'
|
||||
label2 = ' %b %d, %Y'
|
||||
elif rng >= 3600*24 and rng < 3600*24*30:
|
||||
string = '%d'
|
||||
label1 = '%b - '
|
||||
label2 = '%b, %Y'
|
||||
elif rng >= 3600*24*30 and rng < 3600*24*30*24:
|
||||
string = '%b'
|
||||
label1 = '%Y -'
|
||||
label2 = ' %Y'
|
||||
elif rng >=3600*24*30*24:
|
||||
string = '%Y'
|
||||
label1 = ''
|
||||
label2 = ''
|
||||
for x in values:
|
||||
try:
|
||||
strns.append(time.strftime(string, time.localtime(x)))
|
||||
except ValueError: ## Windows can't handle dates before 1970
|
||||
strns.append('')
|
||||
try:
|
||||
label = time.strftime(label1, time.localtime(min(values)))+time.strftime(label2, time.localtime(max(values)))
|
||||
except ValueError:
|
||||
label = ''
|
||||
#self.setLabel(text=label)
|
||||
return strns
|
||||
|
||||
class CustomViewBox(pg.ViewBox):
|
||||
def __init__(self, *args, **kwds):
|
||||
pg.ViewBox.__init__(self, *args, **kwds)
|
||||
@ -65,10 +31,10 @@ class CustomViewBox(pg.ViewBox):
|
||||
|
||||
app = pg.mkQApp()
|
||||
|
||||
axis = DateAxis(orientation='bottom')
|
||||
axis = pg.DateAxisItem(orientation='bottom')
|
||||
vb = CustomViewBox()
|
||||
|
||||
pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with custom axis and ViewBox<br>Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
|
||||
pw = pg.PlotWidget(viewBox=vb, axisItems={'bottom': axis}, enableMenu=False, title="PlotItem with DateAxisItem and custom ViewBox<br>Menu disabled, mouse behavior changed: left-drag to zoom, right-click to reset zoom")
|
||||
dates = np.arange(8) * (3600*24*356)
|
||||
pw.plot(x=dates, y=[1,6,2,4,3,5,6,8], symbol='o')
|
||||
pw.show()
|
||||
|
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string>PyQtGraph</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
|
32
examples/designerExample_pyside2.py
Normal file
32
examples/designerExample_pyside2.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'examples/designerExample.ui'
|
||||
#
|
||||
# Created: Fri Feb 16 20:31:04 2018
|
||||
# by: pyside2-uic 2.0.0 running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(400, 300)
|
||||
self.gridLayout = QtWidgets.QGridLayout(Form)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.plotBtn = QtWidgets.QPushButton(Form)
|
||||
self.plotBtn.setObjectName("plotBtn")
|
||||
self.gridLayout.addWidget(self.plotBtn, 0, 0, 1, 1)
|
||||
self.plot = PlotWidget(Form)
|
||||
self.plot.setObjectName("plot")
|
||||
self.gridLayout.addWidget(self.plot, 1, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
|
||||
self.plotBtn.setText(QtWidgets.QApplication.translate("Form", "Plot!", None, -1))
|
||||
|
||||
from pyqtgraph import PlotWidget
|
@ -11,7 +11,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
<string>PyQtGraph</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
@ -79,6 +79,11 @@
|
||||
<string>PyQt5</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>PySide2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
@ -1,9 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
|
||||
# Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Sat Feb 28 10:30:29 2015
|
||||
# by: PyQt4 UI code generator 4.10.4
|
||||
# Created by: PyQt4 UI code generator 4.11.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@ -35,7 +34,6 @@ class Ui_Form(object):
|
||||
self.widget = QtGui.QWidget(self.splitter)
|
||||
self.widget.setObjectName(_fromUtf8("widget"))
|
||||
self.gridLayout = QtGui.QGridLayout(self.widget)
|
||||
self.gridLayout.setMargin(0)
|
||||
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
|
||||
self.exampleTree = QtGui.QTreeWidget(self.widget)
|
||||
self.exampleTree.setObjectName(_fromUtf8("exampleTree"))
|
||||
@ -55,6 +53,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.addItem(_fromUtf8(""))
|
||||
self.qtLibCombo.addItem(_fromUtf8(""))
|
||||
self.qtLibCombo.addItem(_fromUtf8(""))
|
||||
self.qtLibCombo.addItem(_fromUtf8(""))
|
||||
self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
|
||||
self.label_2 = QtGui.QLabel(self.widget)
|
||||
self.label_2.setObjectName(_fromUtf8("label_2"))
|
||||
@ -68,7 +67,6 @@ class Ui_Form(object):
|
||||
self.widget1 = QtGui.QWidget(self.splitter)
|
||||
self.widget1.setObjectName(_fromUtf8("widget1"))
|
||||
self.verticalLayout = QtGui.QVBoxLayout(self.widget1)
|
||||
self.verticalLayout.setMargin(0)
|
||||
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
|
||||
self.loadedFileLabel = QtGui.QLabel(self.widget1)
|
||||
font = QtGui.QFont()
|
||||
@ -91,7 +89,7 @@ class Ui_Form(object):
|
||||
QtCore.QMetaObject.connectSlotsByName(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(1, _translate("Form", "native", None))
|
||||
self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster", None))
|
||||
@ -100,6 +98,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.setItemText(1, _translate("Form", "PyQt4", None))
|
||||
self.qtLibCombo.setItemText(2, _translate("Form", "PySide", None))
|
||||
self.qtLibCombo.setItemText(3, _translate("Form", "PyQt5", None))
|
||||
self.qtLibCombo.setItemText(4, _translate("Form", "PySide2", None))
|
||||
self.label_2.setText(_translate("Form", "Graphics System:", None))
|
||||
self.label.setText(_translate("Form", "Qt Library:", None))
|
||||
self.loadBtn.setText(_translate("Form", "Run Example", None))
|
||||
|
@ -1,9 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
|
||||
# Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Sat Feb 28 10:28:50 2015
|
||||
# by: PyQt5 UI code generator 5.2.1
|
||||
# Created by: PyQt5 UI code generator 5.6
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@ -41,6 +40,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(self.widget)
|
||||
self.label_2.setObjectName("label_2")
|
||||
@ -78,7 +78,7 @@ class Ui_Form(object):
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Form.setWindowTitle(_translate("Form", "Form"))
|
||||
Form.setWindowTitle(_translate("Form", "PyQtGraph"))
|
||||
self.graphicsSystemCombo.setItemText(0, _translate("Form", "default"))
|
||||
self.graphicsSystemCombo.setItemText(1, _translate("Form", "native"))
|
||||
self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster"))
|
||||
@ -87,6 +87,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.setItemText(1, _translate("Form", "PyQt4"))
|
||||
self.qtLibCombo.setItemText(2, _translate("Form", "PySide"))
|
||||
self.qtLibCombo.setItemText(3, _translate("Form", "PyQt5"))
|
||||
self.qtLibCombo.setItemText(4, _translate("Form", "PySide2"))
|
||||
self.label_2.setText(_translate("Form", "Graphics System:"))
|
||||
self.label.setText(_translate("Form", "Qt Library:"))
|
||||
self.loadBtn.setText(_translate("Form", "Run Example"))
|
||||
|
@ -1,9 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'exampleLoaderTemplate.ui'
|
||||
# Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Sat Feb 28 10:31:57 2015
|
||||
# by: pyside-uic 0.2.15 running on PySide 1.2.1
|
||||
# Created: Fri Feb 16 20:29:46 2018
|
||||
# by: pyside-uic 0.2.15 running on PySide 1.2.4
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@ -41,6 +41,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
|
||||
self.label_2 = QtGui.QLabel(self.widget)
|
||||
self.label_2.setObjectName("label_2")
|
||||
@ -77,7 +78,7 @@ class Ui_Form(object):
|
||||
QtCore.QMetaObject.connectSlotsByName(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(1, QtGui.QApplication.translate("Form", "native", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.graphicsSystemCombo.setItemText(2, QtGui.QApplication.translate("Form", "raster", None, QtGui.QApplication.UnicodeUTF8))
|
||||
@ -86,6 +87,7 @@ class Ui_Form(object):
|
||||
self.qtLibCombo.setItemText(1, QtGui.QApplication.translate("Form", "PyQt4", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.qtLibCombo.setItemText(2, QtGui.QApplication.translate("Form", "PySide", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.qtLibCombo.setItemText(3, QtGui.QApplication.translate("Form", "PyQt5", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.qtLibCombo.setItemText(4, QtGui.QApplication.translate("Form", "PySide2", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.label_2.setText(QtGui.QApplication.translate("Form", "Graphics System:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.label.setText(QtGui.QApplication.translate("Form", "Qt Library:", None, QtGui.QApplication.UnicodeUTF8))
|
||||
self.loadBtn.setText(QtGui.QApplication.translate("Form", "Run Example", None, QtGui.QApplication.UnicodeUTF8))
|
||||
|
94
examples/exampleLoaderTemplate_pyside2.py
Normal file
94
examples/exampleLoaderTemplate_pyside2.py
Normal file
@ -0,0 +1,94 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'examples/exampleLoaderTemplate.ui'
|
||||
#
|
||||
# Created: Fri Feb 16 20:30:37 2018
|
||||
# by: pyside2-uic 2.0.0 running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(846, 552)
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout(Form)
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
self.splitter = QtWidgets.QSplitter(Form)
|
||||
self.splitter.setOrientation(QtCore.Qt.Horizontal)
|
||||
self.splitter.setObjectName("splitter")
|
||||
self.widget = QtWidgets.QWidget(self.splitter)
|
||||
self.widget.setObjectName("widget")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.widget)
|
||||
self.gridLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.exampleTree = QtWidgets.QTreeWidget(self.widget)
|
||||
self.exampleTree.setObjectName("exampleTree")
|
||||
self.exampleTree.headerItem().setText(0, "1")
|
||||
self.exampleTree.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2)
|
||||
self.graphicsSystemCombo = QtWidgets.QComboBox(self.widget)
|
||||
self.graphicsSystemCombo.setObjectName("graphicsSystemCombo")
|
||||
self.graphicsSystemCombo.addItem("")
|
||||
self.graphicsSystemCombo.addItem("")
|
||||
self.graphicsSystemCombo.addItem("")
|
||||
self.graphicsSystemCombo.addItem("")
|
||||
self.gridLayout.addWidget(self.graphicsSystemCombo, 2, 1, 1, 1)
|
||||
self.qtLibCombo = QtWidgets.QComboBox(self.widget)
|
||||
self.qtLibCombo.setObjectName("qtLibCombo")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.qtLibCombo.addItem("")
|
||||
self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1)
|
||||
self.label_2 = QtWidgets.QLabel(self.widget)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
|
||||
self.label = QtWidgets.QLabel(self.widget)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
|
||||
self.loadBtn = QtWidgets.QPushButton(self.widget)
|
||||
self.loadBtn.setObjectName("loadBtn")
|
||||
self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1)
|
||||
self.widget1 = QtWidgets.QWidget(self.splitter)
|
||||
self.widget1.setObjectName("widget1")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1)
|
||||
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.loadedFileLabel = QtWidgets.QLabel(self.widget1)
|
||||
font = QtGui.QFont()
|
||||
font.setWeight(75)
|
||||
font.setBold(True)
|
||||
self.loadedFileLabel.setFont(font)
|
||||
self.loadedFileLabel.setText("")
|
||||
self.loadedFileLabel.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.loadedFileLabel.setObjectName("loadedFileLabel")
|
||||
self.verticalLayout.addWidget(self.loadedFileLabel)
|
||||
self.codeView = QtWidgets.QPlainTextEdit(self.widget1)
|
||||
font = QtGui.QFont()
|
||||
font.setFamily("FreeMono")
|
||||
self.codeView.setFont(font)
|
||||
self.codeView.setObjectName("codeView")
|
||||
self.verticalLayout.addWidget(self.codeView)
|
||||
self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Form", None, -1))
|
||||
self.graphicsSystemCombo.setItemText(0, QtWidgets.QApplication.translate("Form", "default", None, -1))
|
||||
self.graphicsSystemCombo.setItemText(1, QtWidgets.QApplication.translate("Form", "native", None, -1))
|
||||
self.graphicsSystemCombo.setItemText(2, QtWidgets.QApplication.translate("Form", "raster", None, -1))
|
||||
self.graphicsSystemCombo.setItemText(3, QtWidgets.QApplication.translate("Form", "opengl", None, -1))
|
||||
self.qtLibCombo.setItemText(0, QtWidgets.QApplication.translate("Form", "default", None, -1))
|
||||
self.qtLibCombo.setItemText(1, QtWidgets.QApplication.translate("Form", "PyQt4", None, -1))
|
||||
self.qtLibCombo.setItemText(2, QtWidgets.QApplication.translate("Form", "PySide", None, -1))
|
||||
self.qtLibCombo.setItemText(3, QtWidgets.QApplication.translate("Form", "PyQt5", None, -1))
|
||||
self.qtLibCombo.setItemText(4, QtWidgets.QApplication.translate("Form", "PySide2", None, -1))
|
||||
self.label_2.setText(QtWidgets.QApplication.translate("Form", "Graphics System:", None, -1))
|
||||
self.label.setText(QtWidgets.QApplication.translate("Form", "Qt Library:", None, -1))
|
||||
self.loadBtn.setText(QtWidgets.QApplication.translate("Form", "Run Example", None, -1))
|
||||
|
115
examples/fractal.py
Normal file
115
examples/fractal.py
Normal file
@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Displays an interactive Koch fractal
|
||||
"""
|
||||
import initExample ## Add path to library (just for examples; you do not need this)
|
||||
|
||||
from functools import reduce
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
# Set up UI widgets
|
||||
win = pg.QtGui.QWidget()
|
||||
win.setWindowTitle('pyqtgraph example: fractal demo')
|
||||
layout = pg.QtGui.QGridLayout()
|
||||
win.setLayout(layout)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
depthLabel = pg.QtGui.QLabel('fractal depth:')
|
||||
layout.addWidget(depthLabel, 0, 0)
|
||||
depthSpin = pg.SpinBox(value=5, step=1, bounds=[1, 10], delay=0, int=True)
|
||||
depthSpin.resize(100, 20)
|
||||
layout.addWidget(depthSpin, 0, 1)
|
||||
w = pg.GraphicsLayoutWidget()
|
||||
layout.addWidget(w, 1, 0, 1, 2)
|
||||
win.show()
|
||||
|
||||
# Set up graphics
|
||||
v = w.addViewBox()
|
||||
v.setAspectLocked()
|
||||
baseLine = pg.PolyLineROI([[0, 0], [1, 0], [1.5, 1], [2, 0], [3, 0]], pen=(0, 255, 0, 100), movable=False)
|
||||
v.addItem(baseLine)
|
||||
fc = pg.PlotCurveItem(pen=(255, 255, 255, 200), antialias=True)
|
||||
v.addItem(fc)
|
||||
v.autoRange()
|
||||
|
||||
|
||||
transformMap = [0, 0, None]
|
||||
|
||||
|
||||
def update():
|
||||
# recalculate and redraw the fractal curve
|
||||
|
||||
depth = depthSpin.value()
|
||||
pts = baseLine.getState()['points']
|
||||
nbseg = len(pts) - 1
|
||||
nseg = nbseg**depth
|
||||
|
||||
# Get a transformation matrix for each base segment
|
||||
trs = []
|
||||
v1 = pts[-1] - pts[0]
|
||||
l1 = v1.length()
|
||||
for i in range(len(pts)-1):
|
||||
p1 = pts[i]
|
||||
p2 = pts[i+1]
|
||||
v2 = p2 - p1
|
||||
t = p1 - pts[0]
|
||||
r = v2.angle(v1)
|
||||
s = v2.length() / l1
|
||||
trs.append(pg.SRTTransform({'pos': t, 'scale': (s, s), 'angle': r}))
|
||||
|
||||
basePts = [np.array(list(pt) + [1]) for pt in baseLine.getState()['points']]
|
||||
baseMats = np.dstack([tr.matrix().T for tr in trs]).transpose(2, 0, 1)
|
||||
|
||||
# Generate an array of matrices to transform base points
|
||||
global transformMap
|
||||
if transformMap[:2] != [depth, nbseg]:
|
||||
# we can cache the transform index to save a little time..
|
||||
nseg = nbseg**depth
|
||||
matInds = np.empty((depth, nseg), dtype=int)
|
||||
for i in range(depth):
|
||||
matInds[i] = np.tile(np.repeat(np.arange(nbseg), nbseg**(depth-1-i)), nbseg**i)
|
||||
transformMap = [depth, nbseg, matInds]
|
||||
|
||||
# Each column in matInds contains the indices referring to the base transform
|
||||
# matrices that must be multiplied together to generate the final transform
|
||||
# for each segment of the fractal
|
||||
matInds = transformMap[2]
|
||||
|
||||
# Collect all matrices needed for generating fractal curve
|
||||
mats = baseMats[matInds]
|
||||
|
||||
# Magic-multiply stacks of matrices together
|
||||
def matmul(a, b):
|
||||
return np.sum(np.transpose(a,(0,2,1))[..., None] * b[..., None, :], axis=-3)
|
||||
mats = reduce(matmul, mats)
|
||||
|
||||
# Transform base points through matrix array
|
||||
pts = np.empty((nseg * nbseg + 1, 2))
|
||||
for l in range(len(trs)):
|
||||
bp = basePts[l]
|
||||
pts[l:-1:len(trs)] = np.dot(mats, bp)[:, :2]
|
||||
|
||||
# Finish the curve with the last base point
|
||||
pts[-1] = basePts[-1][:2]
|
||||
|
||||
# update fractal curve with new points
|
||||
fc.setData(pts[:,0], pts[:,1])
|
||||
|
||||
|
||||
# Update the fractal whenever the base shape or depth has changed
|
||||
baseLine.sigRegionChanged.connect(update)
|
||||
depthSpin.valueChanged.connect(update)
|
||||
|
||||
# Initialize
|
||||
update()
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
|
||||
QtGui.QApplication.instance().exec_()
|
||||
|
@ -8,7 +8,7 @@ import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.resize(800,350)
|
||||
win.setWindowTitle('pyqtgraph example: Histogram')
|
||||
plt1 = win.addPlot()
|
||||
@ -22,7 +22,7 @@ y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
|
||||
|
||||
## Using stepMode=True causes the plot to draw two lines for each sample.
|
||||
## notice that len(x) == len(y)+1
|
||||
plt1.plot(x, y, stepMode=True, fillLevel=0, brush=(0,0,255,150))
|
||||
plt1.plot(x, y, stepMode=True, fillLevel=0, fillOutline=True, brush=(0,0,255,150))
|
||||
|
||||
## Now draw all points as a nicely-spaced scatter plot
|
||||
y = pg.pseudoScatter(vals, spacing=0.15)
|
||||
|
@ -21,7 +21,7 @@ win = pg.GraphicsLayoutWidget()
|
||||
win.setWindowTitle('pyqtgraph example: Image Analysis')
|
||||
|
||||
# A plot area (ViewBox + axes) for displaying the image
|
||||
p1 = win.addPlot()
|
||||
p1 = win.addPlot(title="")
|
||||
|
||||
# Item for displaying image data
|
||||
img = pg.ImageItem()
|
||||
@ -93,6 +93,26 @@ def updateIsocurve():
|
||||
|
||||
isoLine.sigDragged.connect(updateIsocurve)
|
||||
|
||||
def imageHoverEvent(event):
|
||||
"""Show the position, pixel, and value under the mouse cursor.
|
||||
"""
|
||||
if event.isExit():
|
||||
p1.setTitle("")
|
||||
return
|
||||
pos = event.pos()
|
||||
i, j = pos.y(), pos.x()
|
||||
i = int(np.clip(i, 0, data.shape[0] - 1))
|
||||
j = int(np.clip(j, 0, data.shape[1] - 1))
|
||||
val = data[i, j]
|
||||
ppos = img.mapToParent(pos)
|
||||
x, y = ppos.x(), ppos.y()
|
||||
p1.setTitle("pos: (%0.1f, %0.1f) pixel: (%d, %d) value: %g" % (x, y, i, j, val))
|
||||
|
||||
# Monkey-patch the image to use our custom hover function.
|
||||
# This is generally discouraged (you should subclass ImageItem instead),
|
||||
# but it works for a very simple use like this.
|
||||
img.hoverEvent = imageHoverEvent
|
||||
|
||||
|
||||
## Start Qt event loop unless running in interactive mode or using pyside.
|
||||
if __name__ == '__main__':
|
||||
|
@ -26,6 +26,8 @@ elif 'pyqt' in sys.argv:
|
||||
from PyQt4 import QtGui
|
||||
elif 'pyqt5' in sys.argv:
|
||||
from PyQt5 import QtGui
|
||||
elif 'pyside2' in sys.argv:
|
||||
from PySide2 import QtGui
|
||||
else:
|
||||
from pyqtgraph.Qt import QtGui
|
||||
|
||||
|
@ -17,10 +17,10 @@ app = QtGui.QApplication([])
|
||||
frames = 200
|
||||
data = np.random.normal(size=(frames,30,30), loc=0, scale=100)
|
||||
data = np.concatenate([data, data], axis=0)
|
||||
data = pg.gaussianFilter(data, (10, 10, 10))[frames/2:frames + frames/2]
|
||||
data = pg.gaussianFilter(data, (10, 10, 10))[frames//2:frames + frames//2]
|
||||
data[:, 15:16, 15:17] += 1
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: Isocurve')
|
||||
vb = win.addViewBox()
|
||||
img = pg.ImageItem(data[0])
|
||||
|
@ -20,7 +20,7 @@ app = QtGui.QApplication([])
|
||||
x = np.linspace(-50, 50, 1000)
|
||||
y = np.sin(x) / x
|
||||
|
||||
win = pg.GraphicsWindow(title="pyqtgraph example: Linked Views")
|
||||
win = pg.GraphicsLayoutWidget(show=True, title="pyqtgraph example: Linked Views")
|
||||
win.resize(800,600)
|
||||
|
||||
win.addLabel("Linked Views", colspan=2)
|
||||
|
@ -11,7 +11,7 @@ import pyqtgraph as pg
|
||||
|
||||
app = QtGui.QApplication([])
|
||||
|
||||
w = pg.GraphicsWindow()
|
||||
w = pg.GraphicsLayoutWidget(show=True)
|
||||
w.setWindowTitle('pyqtgraph example: logAxis')
|
||||
p1 = w.addPlot(0,0, title="X Semilog")
|
||||
p2 = w.addPlot(1,0, title="Y Semilog")
|
||||
|
@ -110,7 +110,11 @@ class ParamObj(object):
|
||||
|
||||
def __getitem__(self, item):
|
||||
# bug in pyside 1.2.2 causes getitem to be called inside QGraphicsObject.parentItem:
|
||||
return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-441
|
||||
return self.getParam(item) # PySide bug: https://bugreports.qt.io/browse/PYSIDE-671
|
||||
|
||||
def __len__(self):
|
||||
# Workaround for PySide bug: https://bugreports.qt.io/browse/PYSIDE-671
|
||||
return 0
|
||||
|
||||
def getParam(self, param):
|
||||
return self.__params[param]
|
||||
@ -235,35 +239,21 @@ class Lens(Optic):
|
||||
N must already be normalized in order to achieve the
|
||||
desired result.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
iors = [self.ior(ray['wl']), 1.0]
|
||||
for i in [0,1]:
|
||||
surface = self.surfaces[i]
|
||||
ior = iors[i]
|
||||
p1, ai = surface.intersectRay(ray)
|
||||
#print "surface intersection:", p1, ai*180/3.14159
|
||||
#trans = self.sceneTransform().inverted()[0] * surface.sceneTransform()
|
||||
#p1 = trans.map(p1)
|
||||
if p1 is None:
|
||||
ray.setEnd(None)
|
||||
break
|
||||
p1 = surface.mapToItem(ray, p1)
|
||||
|
||||
#print "adjusted position:", p1
|
||||
#ior = self.ior(ray['wl'])
|
||||
rd = ray['dir']
|
||||
a1 = np.arctan2(rd[1], rd[0])
|
||||
ar = a1 - ai + np.arcsin((np.sin(ai) * ray['ior'] / ior))
|
||||
#print [x for x in [a1, ai, (np.sin(ai) * ray['ior'] / ior), ar]]
|
||||
#print ai, np.sin(ai), ray['ior'], ior
|
||||
ray.setEnd(p1)
|
||||
dp = Point(np.cos(ar), np.sin(ar))
|
||||
#p2 = p1+dp
|
||||
#p1p = self.mapToScene(p1)
|
||||
#p2p = self.mapToScene(p2)
|
||||
#dpp = Point(p2p-p1p)
|
||||
ray = Ray(parent=ray, ior=ior, dir=dp)
|
||||
return [ray]
|
||||
|
||||
@ -384,20 +374,12 @@ class CircleSurface(pg.GraphicsObject):
|
||||
else:
|
||||
## half-height of surface can't be larger than radius
|
||||
h2 = min(h2, abs(r))
|
||||
|
||||
#dx = abs(r) - (abs(r)**2 - abs(h2)**2)**0.5
|
||||
#p.moveTo(-d*w/2.+ d*dx, d*h2)
|
||||
arc = QtCore.QRectF(0, -r, r*2, r*2)
|
||||
#self.surfaces.append((arc.center(), r, h2))
|
||||
a1 = np.arcsin(h2/r) * 180. / np.pi
|
||||
a2 = -2*a1
|
||||
a1 += 180.
|
||||
self.path.arcMoveTo(arc, a1)
|
||||
self.path.arcTo(arc, a1, a2)
|
||||
#if d == -1:
|
||||
#p1 = QtGui.QPainterPath()
|
||||
#p1.addRect(arc)
|
||||
#self.paths.append(p1)
|
||||
self.h2 = h2
|
||||
|
||||
def boundingRect(self):
|
||||
@ -405,8 +387,6 @@ class CircleSurface(pg.GraphicsObject):
|
||||
|
||||
def paint(self, p, *args):
|
||||
return ## usually we let the optic draw.
|
||||
#p.setPen(pg.mkPen('r'))
|
||||
#p.drawPath(self.path)
|
||||
|
||||
def intersectRay(self, ray):
|
||||
## return the point of intersection and the angle of incidence
|
||||
@ -527,7 +507,6 @@ class Ray(pg.GraphicsObject, ParamObj):
|
||||
p2 = trans.map(pos + dir)
|
||||
return Point(p1), Point(p2-p1)
|
||||
|
||||
|
||||
def setEnd(self, end):
|
||||
self['end'] = end
|
||||
self.mkPath()
|
||||
@ -561,6 +540,7 @@ def trace(rays, optics):
|
||||
r2 = o.propagateRay(r)
|
||||
trace(r2, optics[1:])
|
||||
|
||||
|
||||
class Tracer(QtCore.QObject):
|
||||
"""
|
||||
Simple ray tracer.
|
||||
|
@ -17,7 +17,7 @@ from pyqtgraph import Point
|
||||
|
||||
app = pg.QtGui.QApplication([])
|
||||
|
||||
w = pg.GraphicsWindow(border=0.5)
|
||||
w = pg.GraphicsLayoutWidget(show=True, border=0.5)
|
||||
w.resize(1000, 900)
|
||||
w.show()
|
||||
|
||||
|
@ -96,6 +96,16 @@ params = [
|
||||
{'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': '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)'),
|
||||
ScalableGroup(name="Expandable Parameter Group", children=[
|
||||
{'name': 'ScalableParam 1', 'type': 'str', 'value': "default param 1"},
|
||||
|
@ -159,17 +159,21 @@ class RelativityGUI(QtGui.QWidget):
|
||||
self.setAnimation(self.params['Animate'])
|
||||
|
||||
def save(self):
|
||||
fn = str(pg.QtGui.QFileDialog.getSaveFileName(self, "Save State..", "untitled.cfg", "Config Files (*.cfg)"))
|
||||
if fn == '':
|
||||
filename = pg.QtGui.QFileDialog.getSaveFileName(self, "Save State..", "untitled.cfg", "Config Files (*.cfg)")
|
||||
if isinstance(filename, tuple):
|
||||
filename = filename[0] # Qt4/5 API difference
|
||||
if filename == '':
|
||||
return
|
||||
state = self.params.saveState()
|
||||
pg.configfile.writeConfigFile(state, fn)
|
||||
pg.configfile.writeConfigFile(state, str(filename))
|
||||
|
||||
def load(self):
|
||||
fn = str(pg.QtGui.QFileDialog.getOpenFileName(self, "Save State..", "", "Config Files (*.cfg)"))
|
||||
if fn == '':
|
||||
filename = pg.QtGui.QFileDialog.getOpenFileName(self, "Save State..", "", "Config Files (*.cfg)")
|
||||
if isinstance(filename, tuple):
|
||||
filename = filename[0] # Qt4/5 API difference
|
||||
if filename == '':
|
||||
return
|
||||
state = pg.configfile.readConfigFile(fn)
|
||||
state = pg.configfile.readConfigFile(str(filename))
|
||||
self.loadState(state)
|
||||
|
||||
def loadPreset(self, param, preset):
|
||||
|
@ -8,7 +8,7 @@ import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
import numpy as np
|
||||
|
||||
win = pg.GraphicsWindow()
|
||||
win = pg.GraphicsLayoutWidget(show=True)
|
||||
win.setWindowTitle('pyqtgraph example: Scrolling Plots')
|
||||
|
||||
|
||||
|
250
examples/syntax.py
Normal file
250
examples/syntax.py
Normal file
@ -0,0 +1,250 @@
|
||||
# based on https://github.com/art1415926535/PyQt5-syntax-highlighting
|
||||
|
||||
from pyqtgraph.Qt import QtCore, QtGui
|
||||
|
||||
QRegExp = QtCore.QRegExp
|
||||
|
||||
QFont = QtGui.QFont
|
||||
QColor = QtGui.QColor
|
||||
QTextCharFormat = QtGui.QTextCharFormat
|
||||
QSyntaxHighlighter = QtGui.QSyntaxHighlighter
|
||||
|
||||
|
||||
def format(color, style=''):
|
||||
"""
|
||||
Return a QTextCharFormat with the given attributes.
|
||||
"""
|
||||
_color = QColor()
|
||||
if type(color) is not str:
|
||||
_color.setRgb(color[0], color[1], color[2])
|
||||
else:
|
||||
_color.setNamedColor(color)
|
||||
|
||||
_format = QTextCharFormat()
|
||||
_format.setForeground(_color)
|
||||
if 'bold' in style:
|
||||
_format.setFontWeight(QFont.Bold)
|
||||
if 'italic' in style:
|
||||
_format.setFontItalic(True)
|
||||
|
||||
return _format
|
||||
|
||||
|
||||
class LightThemeColors:
|
||||
|
||||
Red = "#B71C1C"
|
||||
Pink = "#FCE4EC"
|
||||
Purple = "#4A148C"
|
||||
DeepPurple = "#311B92"
|
||||
Indigo = "#1A237E"
|
||||
Blue = "#0D47A1"
|
||||
LightBlue = "#01579B"
|
||||
Cyan = "#006064"
|
||||
Teal = "#004D40"
|
||||
Green = "#1B5E20"
|
||||
LightGreen = "#33691E"
|
||||
Lime = "#827717"
|
||||
Yellow = "#F57F17"
|
||||
Amber = "#FF6F00"
|
||||
Orange = "#E65100"
|
||||
DeepOrange = "#BF360C"
|
||||
Brown = "#3E2723"
|
||||
Grey = "#212121"
|
||||
BlueGrey = "#263238"
|
||||
|
||||
|
||||
class DarkThemeColors:
|
||||
|
||||
Red = "#F44336"
|
||||
Pink = "#F48FB1"
|
||||
Purple = "#CE93D8"
|
||||
DeepPurple = "#B39DDB"
|
||||
Indigo = "#9FA8DA"
|
||||
Blue = "#90CAF9"
|
||||
LightBlue = "#81D4FA"
|
||||
Cyan = "#80DEEA"
|
||||
Teal = "#80CBC4"
|
||||
Green = "#A5D6A7"
|
||||
LightGreen = "#C5E1A5"
|
||||
Lime = "#E6EE9C"
|
||||
Yellow = "#FFF59D"
|
||||
Amber = "#FFE082"
|
||||
Orange = "#FFCC80"
|
||||
DeepOrange = "#FFAB91"
|
||||
Brown = "#BCAAA4"
|
||||
Grey = "#EEEEEE"
|
||||
BlueGrey = "#B0BEC5"
|
||||
|
||||
|
||||
LIGHT_STYLES = {
|
||||
'keyword': format(LightThemeColors.Blue, 'bold'),
|
||||
'operator': format(LightThemeColors.Red, 'bold'),
|
||||
'brace': format(LightThemeColors.Purple),
|
||||
'defclass': format(LightThemeColors.Indigo, 'bold'),
|
||||
'string': format(LightThemeColors.Amber),
|
||||
'string2': format(LightThemeColors.DeepPurple),
|
||||
'comment': format(LightThemeColors.Green, 'italic'),
|
||||
'self': format(LightThemeColors.Blue, 'bold'),
|
||||
'numbers': format(LightThemeColors.Teal),
|
||||
}
|
||||
|
||||
|
||||
DARK_STYLES = {
|
||||
'keyword': format(DarkThemeColors.Blue, 'bold'),
|
||||
'operator': format(DarkThemeColors.Red, 'bold'),
|
||||
'brace': format(DarkThemeColors.Purple),
|
||||
'defclass': format(DarkThemeColors.Indigo, 'bold'),
|
||||
'string': format(DarkThemeColors.Amber),
|
||||
'string2': format(DarkThemeColors.DeepPurple),
|
||||
'comment': format(DarkThemeColors.Green, 'italic'),
|
||||
'self': format(DarkThemeColors.Blue, 'bold'),
|
||||
'numbers': format(DarkThemeColors.Teal),
|
||||
}
|
||||
|
||||
|
||||
class PythonHighlighter(QSyntaxHighlighter):
|
||||
"""Syntax highlighter for the Python language.
|
||||
"""
|
||||
# Python keywords
|
||||
keywords = [
|
||||
'and', 'assert', 'break', 'class', 'continue', 'def',
|
||||
'del', 'elif', 'else', 'except', 'exec', 'finally',
|
||||
'for', 'from', 'global', 'if', 'import', 'in',
|
||||
'is', 'lambda', 'not', 'or', 'pass', 'print',
|
||||
'raise', 'return', 'try', 'while', 'yield',
|
||||
'None', 'True', 'False', 'async', 'await',
|
||||
]
|
||||
|
||||
# Python operators
|
||||
operators = [
|
||||
'=',
|
||||
# Comparison
|
||||
'==', '!=', '<', '<=', '>', '>=',
|
||||
# Arithmetic
|
||||
'\+', '-', '\*', '/', '//', '\%', '\*\*',
|
||||
# In-place
|
||||
'\+=', '-=', '\*=', '/=', '\%=',
|
||||
# Bitwise
|
||||
'\^', '\|', '\&', '\~', '>>', '<<',
|
||||
]
|
||||
|
||||
# Python braces
|
||||
braces = [
|
||||
'\{', '\}', '\(', '\)', '\[', '\]',
|
||||
]
|
||||
|
||||
def __init__(self, document):
|
||||
QSyntaxHighlighter.__init__(self, document)
|
||||
|
||||
# Multi-line strings (expression, flag, style)
|
||||
# FIXME: The triple-quotes in these two lines will mess up the
|
||||
# syntax highlighting from this point onward
|
||||
self.tri_single = (QRegExp("'''"), 1, 'string2')
|
||||
self.tri_double = (QRegExp('"""'), 2, 'string2')
|
||||
|
||||
rules = []
|
||||
|
||||
# Keyword, operator, and brace rules
|
||||
rules += [(r'\b%s\b' % w, 0, 'keyword')
|
||||
for w in PythonHighlighter.keywords]
|
||||
rules += [(r'%s' % o, 0, 'operator')
|
||||
for o in PythonHighlighter.operators]
|
||||
rules += [(r'%s' % b, 0, 'brace')
|
||||
for b in PythonHighlighter.braces]
|
||||
|
||||
# All other rules
|
||||
rules += [
|
||||
|
||||
# 'self'
|
||||
(r'\bself\b', 0, 'self'),
|
||||
|
||||
# 'def' followed by an identifier
|
||||
(r'\bdef\b\s*(\w+)', 1, 'defclass'),
|
||||
# 'class' followed by an identifier
|
||||
(r'\bclass\b\s*(\w+)', 1, 'defclass'),
|
||||
|
||||
# Numeric literals
|
||||
(r'\b[+-]?[0-9]+[lL]?\b', 0, 'numbers'),
|
||||
(r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b', 0, 'numbers'),
|
||||
(r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, 'numbers'),
|
||||
|
||||
# Double-quoted string, possibly containing escape sequences
|
||||
(r'"[^"\\]*(\\.[^"\\]*)*"', 0, 'string'),
|
||||
# Single-quoted string, possibly containing escape sequences
|
||||
(r"'[^'\\]*(\\.[^'\\]*)*'", 0, 'string'),
|
||||
|
||||
# From '#' until a newline
|
||||
(r'#[^\n]*', 0, 'comment'),
|
||||
|
||||
]
|
||||
|
||||
# Build a QRegExp for each pattern
|
||||
self.rules = [(QRegExp(pat), index, fmt)
|
||||
for (pat, index, fmt) in rules]
|
||||
|
||||
@property
|
||||
def styles(self):
|
||||
app = QtGui.QApplication.instance()
|
||||
return DARK_STYLES if app.dark_mode else LIGHT_STYLES
|
||||
|
||||
def highlightBlock(self, text):
|
||||
"""Apply syntax highlighting to the given block of text.
|
||||
"""
|
||||
# Do other syntax formatting
|
||||
for expression, nth, format in self.rules:
|
||||
index = expression.indexIn(text, 0)
|
||||
format = self.styles[format]
|
||||
|
||||
while index >= 0:
|
||||
# We actually want the index of the nth match
|
||||
index = expression.pos(nth)
|
||||
length = len(expression.cap(nth))
|
||||
self.setFormat(index, length, format)
|
||||
index = expression.indexIn(text, index + length)
|
||||
|
||||
self.setCurrentBlockState(0)
|
||||
|
||||
# Do multi-line strings
|
||||
in_multiline = self.match_multiline(text, *self.tri_single)
|
||||
if not in_multiline:
|
||||
in_multiline = self.match_multiline(text, *self.tri_double)
|
||||
|
||||
def match_multiline(self, text, delimiter, in_state, style):
|
||||
"""Do highlighting of multi-line strings. ``delimiter`` should be a
|
||||
``QRegExp`` for triple-single-quotes or triple-double-quotes, and
|
||||
``in_state`` should be a unique integer to represent the corresponding
|
||||
state changes when inside those strings. Returns True if we're still
|
||||
inside a multi-line string when this function is finished.
|
||||
"""
|
||||
# If inside triple-single quotes, start at 0
|
||||
if self.previousBlockState() == in_state:
|
||||
start = 0
|
||||
add = 0
|
||||
# Otherwise, look for the delimiter on this line
|
||||
else:
|
||||
start = delimiter.indexIn(text)
|
||||
# Move past this match
|
||||
add = delimiter.matchedLength()
|
||||
|
||||
# As long as there's a delimiter match on this line...
|
||||
while start >= 0:
|
||||
# Look for the ending delimiter
|
||||
end = delimiter.indexIn(text, start + add)
|
||||
# Ending delimiter on this line?
|
||||
if end >= add:
|
||||
length = end - start + add + delimiter.matchedLength()
|
||||
self.setCurrentBlockState(0)
|
||||
# No; multi-line string
|
||||
else:
|
||||
self.setCurrentBlockState(in_state)
|
||||
length = len(text) - start + add
|
||||
# Apply formatting
|
||||
self.setFormat(start, length, self.styles[style])
|
||||
# Look for the next match
|
||||
start = delimiter.indexIn(text, start + length)
|
||||
|
||||
# Return True if still inside a multi-line string, False otherwise
|
||||
if self.currentBlockState() == in_state:
|
||||
return True
|
||||
else:
|
||||
return False
|
@ -1,11 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function, division, absolute_import
|
||||
from pyqtgraph import Qt
|
||||
from . import utils
|
||||
from collections import namedtuple
|
||||
from pyqtgraph import Qt
|
||||
import errno
|
||||
import importlib
|
||||
import itertools
|
||||
import pytest
|
||||
import os, sys
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
|
||||
path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# printing on travis ci frequently leads to "interrupted system call" errors.
|
||||
# as a workaround, we overwrite the built-in print function (bleh)
|
||||
if os.getenv('TRAVIS') is not None:
|
||||
@ -32,17 +40,13 @@ if os.getenv('TRAVIS') is not None:
|
||||
print("Installed wrapper for flaky print.")
|
||||
|
||||
|
||||
# apparently importlib does not exist in python 2.6...
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
# we are on python 2.6
|
||||
print("If you want to test the examples, please install importlib from "
|
||||
"pypi\n\npip install importlib\n\n")
|
||||
pass
|
||||
|
||||
files = utils.buildFileList(utils.examples)
|
||||
frontends = {Qt.PYQT4: False, Qt.PYSIDE: False}
|
||||
files = sorted(set(utils.buildFileList(utils.examples)))
|
||||
frontends = {
|
||||
Qt.PYQT4: False,
|
||||
Qt.PYQT5: False,
|
||||
Qt.PYSIDE: False,
|
||||
Qt.PYSIDE2: False
|
||||
}
|
||||
# sort out which of the front ends are available
|
||||
for frontend in frontends.keys():
|
||||
try:
|
||||
@ -51,15 +55,204 @@ for frontend in frontends.keys():
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
installedFrontends = sorted([
|
||||
frontend for frontend, isPresent in frontends.items() if isPresent
|
||||
])
|
||||
|
||||
exceptionCondition = namedtuple("exceptionCondition", ["condition", "reason"])
|
||||
conditionalExamples = {
|
||||
"hdf5.py": exceptionCondition(
|
||||
False,
|
||||
reason="Example requires user interaction"
|
||||
),
|
||||
"RemoteSpeedTest.py": exceptionCondition(
|
||||
False,
|
||||
reason="Test is being problematic on CI machines"
|
||||
),
|
||||
"optics_demos.py": exceptionCondition(
|
||||
not frontends[Qt.PYSIDE],
|
||||
reason=(
|
||||
"Test fails due to PySide bug: ",
|
||||
"https://bugreports.qt.io/browse/PYSIDE-671"
|
||||
)
|
||||
),
|
||||
'GLVolumeItem.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLIsosurface.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLSurfacePlot.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLScatterPlotItem.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLshaders.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLLinePlotItem.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLMeshItem.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
),
|
||||
'GLImageItem.py': exceptionCondition(
|
||||
not(sys.platform == "darwin" and
|
||||
sys.version_info[0] == 2 and
|
||||
(frontends[Qt.PYQT4] or frontends[Qt.PYSIDE])),
|
||||
reason=(
|
||||
"glClear does not work on macOS + Python2.7 + Qt4: ",
|
||||
"https://github.com/pyqtgraph/pyqtgraph/issues/939"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Qt.QT_LIB == "PySide2"
|
||||
and tuple(map(int, Qt.PySide2.__version__.split("."))) >= (5, 14)
|
||||
and tuple(map(int, Qt.PySide2.__version__.split("."))) < (5, 14, 2, 2),
|
||||
reason="new PySide2 doesn't have loadUi functionality"
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"frontend, f", itertools.product(sorted(list(frontends.keys())), files))
|
||||
def test_examples(frontend, f):
|
||||
# Test the examples with all available front-ends
|
||||
print('frontend = %s. f = %s' % (frontend, f))
|
||||
if not frontends[frontend]:
|
||||
pytest.skip('%s is not installed. Skipping tests' % frontend)
|
||||
utils.testFile(f[0], f[1], utils.sys.executable, frontend)
|
||||
"frontend, f",
|
||||
[
|
||||
pytest.param(
|
||||
frontend,
|
||||
f,
|
||||
marks=pytest.mark.skipif(
|
||||
conditionalExamples[f[1]].condition is False,
|
||||
reason=conditionalExamples[f[1]].reason
|
||||
) if f[1] in conditionalExamples.keys() else (),
|
||||
)
|
||||
for frontend, f, in itertools.product(installedFrontends, files)
|
||||
],
|
||||
ids = [
|
||||
" {} - {} ".format(f[1], frontend)
|
||||
for frontend, f in itertools.product(
|
||||
installedFrontends,
|
||||
files
|
||||
)
|
||||
]
|
||||
)
|
||||
def testExamples(frontend, f, graphicsSystem=None):
|
||||
# runExampleFile(f[0], f[1], sys.executable, frontend)
|
||||
|
||||
name, file = f
|
||||
global path
|
||||
fn = os.path.join(path, file)
|
||||
os.chdir(path)
|
||||
sys.stdout.write("{} ".format(name))
|
||||
sys.stdout.flush()
|
||||
import1 = "import %s" % frontend if frontend != '' else ''
|
||||
import2 = os.path.splitext(os.path.split(fn)[1])[0]
|
||||
graphicsSystem = (
|
||||
'' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem
|
||||
)
|
||||
code = """
|
||||
try:
|
||||
%s
|
||||
import initExample
|
||||
import pyqtgraph as pg
|
||||
%s
|
||||
import %s
|
||||
import sys
|
||||
print("test complete")
|
||||
sys.stdout.flush()
|
||||
import time
|
||||
while True: ## run a little event loop
|
||||
pg.QtGui.QApplication.processEvents()
|
||||
time.sleep(0.01)
|
||||
except:
|
||||
print("test failed")
|
||||
raise
|
||||
|
||||
""" % (import1, graphicsSystem, import2)
|
||||
if sys.platform.startswith('win'):
|
||||
process = subprocess.Popen([sys.executable],
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
else:
|
||||
process = subprocess.Popen(['exec %s -i' % (sys.executable)],
|
||||
shell=True,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
process.stdin.write(code.encode('UTF-8'))
|
||||
process.stdin.close()
|
||||
output = ''
|
||||
fail = False
|
||||
while True:
|
||||
try:
|
||||
c = process.stdout.read(1).decode()
|
||||
except IOError as err:
|
||||
if err.errno == errno.EINTR:
|
||||
# Interrupted system call; just try again.
|
||||
c = ''
|
||||
else:
|
||||
raise
|
||||
output += c
|
||||
|
||||
if output.endswith('test complete'):
|
||||
break
|
||||
if output.endswith('test failed'):
|
||||
fail = True
|
||||
break
|
||||
time.sleep(1)
|
||||
process.kill()
|
||||
#res = process.communicate()
|
||||
res = (process.stdout.read(), process.stderr.read())
|
||||
if (fail or
|
||||
'exception' in res[1].decode().lower() or
|
||||
'error' in res[1].decode().lower()):
|
||||
print(res[0].decode())
|
||||
print(res[1].decode())
|
||||
pytest.fail("{}\n{}\nFailed {} Example Test Located in {} "
|
||||
.format(res[0].decode(), res[1].decode(), name, file),
|
||||
pytrace=False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.cmdline.main()
|
||||
|
@ -1,8 +1,5 @@
|
||||
from __future__ import division, print_function, absolute_import
|
||||
import subprocess
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from pyqtgraph.pgcollections import OrderedDict
|
||||
from pyqtgraph.python2_3 import basestring
|
||||
|
||||
@ -17,7 +14,9 @@ examples = OrderedDict([
|
||||
('Crosshair / Mouse interaction', 'crosshair.py'),
|
||||
('Data Slicing', 'DataSlicing.py'),
|
||||
('Plot Customization', 'customPlot.py'),
|
||||
('Timestamps on x axis', 'DateAxisItem.py'),
|
||||
('Image Analysis', 'imageAnalysis.py'),
|
||||
('ViewBox Features', 'ViewBoxFeatures.py'),
|
||||
('Dock widgets', 'dockarea.py'),
|
||||
('Console', 'ConsoleWidget.py'),
|
||||
('Histograms', 'histogram.py'),
|
||||
@ -31,6 +30,7 @@ examples = OrderedDict([
|
||||
('Optics', 'optics_demos.py'),
|
||||
('Special relativity', 'relativity_demo.py'),
|
||||
('Verlet chain', 'verlet_chain_demo.py'),
|
||||
('Koch Fractal', 'fractal.py'),
|
||||
])),
|
||||
('GraphicsItems', OrderedDict([
|
||||
('Scatter Plot', 'ScatterPlot.py'),
|
||||
@ -48,7 +48,7 @@ examples = OrderedDict([
|
||||
('Text Item', 'text.py'),
|
||||
('Linked Views', 'linkedViews.py'),
|
||||
('Arrow', 'Arrow.py'),
|
||||
('ViewBox', 'ViewBox.py'),
|
||||
('ViewBox', 'ViewBoxFeatures.py'),
|
||||
('Custom Graphics', 'customGraphicsItem.py'),
|
||||
('Labeled Graph', 'CustomGraphItem.py'),
|
||||
])),
|
||||
@ -83,7 +83,6 @@ examples = OrderedDict([
|
||||
#('VerticalLabel', '../widgets/VerticalLabel.py'),
|
||||
('JoystickButton', 'JoystickButton.py'),
|
||||
])),
|
||||
|
||||
('Flowcharts', 'Flowchart.py'),
|
||||
('Custom Flowchart Nodes', 'FlowchartCustomNode.py'),
|
||||
])
|
||||
@ -100,66 +99,3 @@ def buildFileList(examples, files=None):
|
||||
else:
|
||||
buildFileList(val, files)
|
||||
return files
|
||||
|
||||
def testFile(name, f, exe, lib, graphicsSystem=None):
|
||||
global path
|
||||
fn = os.path.join(path,f)
|
||||
#print "starting process: ", fn
|
||||
os.chdir(path)
|
||||
sys.stdout.write(name)
|
||||
sys.stdout.flush()
|
||||
|
||||
import1 = "import %s" % lib if lib != '' else ''
|
||||
import2 = os.path.splitext(os.path.split(fn)[1])[0]
|
||||
graphicsSystem = '' if graphicsSystem is None else "pg.QtGui.QApplication.setGraphicsSystem('%s')" % graphicsSystem
|
||||
code = """
|
||||
try:
|
||||
%s
|
||||
import initExample
|
||||
import pyqtgraph as pg
|
||||
%s
|
||||
import %s
|
||||
import sys
|
||||
print("test complete")
|
||||
sys.stdout.flush()
|
||||
import time
|
||||
while True: ## run a little event loop
|
||||
pg.QtGui.QApplication.processEvents()
|
||||
time.sleep(0.01)
|
||||
except:
|
||||
print("test failed")
|
||||
raise
|
||||
|
||||
""" % (import1, graphicsSystem, import2)
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
process = subprocess.Popen([exe], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
process.stdin.write(code.encode('UTF-8'))
|
||||
process.stdin.close()
|
||||
else:
|
||||
process = subprocess.Popen(['exec %s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
process.stdin.write(code.encode('UTF-8'))
|
||||
process.stdin.close() ##?
|
||||
output = ''
|
||||
fail = False
|
||||
while True:
|
||||
c = process.stdout.read(1).decode()
|
||||
output += c
|
||||
#sys.stdout.write(c)
|
||||
#sys.stdout.flush()
|
||||
if output.endswith('test complete'):
|
||||
break
|
||||
if output.endswith('test failed'):
|
||||
fail = True
|
||||
break
|
||||
time.sleep(1)
|
||||
process.kill()
|
||||
#res = process.communicate()
|
||||
res = (process.stdout.read(), process.stderr.read())
|
||||
|
||||
if fail or 'exception' in res[1].decode().lower() or 'error' in res[1].decode().lower():
|
||||
print('.' * (50-len(name)) + 'FAILED')
|
||||
print(res[0].decode())
|
||||
print(res[1].decode())
|
||||
else:
|
||||
print('.' * (50-len(name)) + 'passed')
|
||||
|
@ -32,8 +32,6 @@ class ChainSim(pg.QtCore.QObject):
|
||||
if self.initialized:
|
||||
return
|
||||
|
||||
assert None not in [self.pos, self.mass, self.links, self.lengths]
|
||||
|
||||
if self.fixed is None:
|
||||
self.fixed = np.zeros(self.pos.shape[0], dtype=bool)
|
||||
if self.push is None:
|
||||
|
@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import weakref
|
||||
import warnings
|
||||
|
||||
from ..Qt import QtCore, QtGui
|
||||
from ..python2_3 import sortList, cmp
|
||||
from ..Point import Point
|
||||
from .. import functions as fn
|
||||
from .. import ptime as ptime
|
||||
@ -37,6 +39,18 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
* Eats mouseMove events that occur too soon after a mouse press.
|
||||
* Reimplements items() and itemAt() to circumvent PyQt bug
|
||||
|
||||
====================== ==================================================================
|
||||
**Signals**
|
||||
sigMouseClicked(event) Emitted when the mouse is clicked over the scene. Use ev.pos() to
|
||||
get the click position relative to the item that was clicked on,
|
||||
or ev.scenePos() to get the click position in scene coordinates.
|
||||
See :class:`pyqtgraph.GraphicsScene.MouseClickEvent`.
|
||||
sigMouseMoved(pos) Emitted when the mouse cursor moves over the scene. The position
|
||||
is given in scene coordinates.
|
||||
sigMouseHover(items) Emitted when the mouse is moved over the scene. Items is a list
|
||||
of items under the cursor.
|
||||
====================== ==================================================================
|
||||
|
||||
Mouse interaction is as follows:
|
||||
|
||||
1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents
|
||||
@ -76,14 +90,10 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
|
||||
@classmethod
|
||||
def registerObject(cls, obj):
|
||||
"""
|
||||
Workaround for PyQt bug in qgraphicsscene.items()
|
||||
All subclasses of QGraphicsObject must register themselves with this function.
|
||||
(otherwise, mouse interaction with those objects will likely fail)
|
||||
"""
|
||||
if HAVE_SIP and isinstance(obj, sip.wrapper):
|
||||
cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj
|
||||
|
||||
warnings.warn(
|
||||
"'registerObject' is deprecated and does nothing.",
|
||||
DeprecationWarning, stacklevel=2
|
||||
)
|
||||
|
||||
def __init__(self, clickRadius=2, moveDistance=5, parent=None):
|
||||
QtGui.QGraphicsScene.__init__(self, parent)
|
||||
@ -171,7 +181,9 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
if int(ev.buttons() & btn) == 0:
|
||||
continue
|
||||
if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet
|
||||
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0]
|
||||
cev = [e for e in self.clickEvents if int(e.button()) == int(btn)]
|
||||
if cev:
|
||||
cev = cev[0]
|
||||
dist = Point(ev.scenePos() - cev.scenePos()).length()
|
||||
if dist == 0 or (dist < self._moveDistance and now - cev.time() < self.minDragTime):
|
||||
continue
|
||||
@ -196,6 +208,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
self.dragButtons.remove(ev.button())
|
||||
else:
|
||||
cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())]
|
||||
if cev:
|
||||
if self.sendClickEvent(cev[0]):
|
||||
#print "sent click event"
|
||||
ev.accept()
|
||||
@ -251,6 +264,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
for item in prevItems:
|
||||
event.currentItem = item
|
||||
try:
|
||||
if item.scene() is self:
|
||||
item.hoverEvent(event)
|
||||
except:
|
||||
debug.printExc("Error sending hover exit event:")
|
||||
@ -276,7 +290,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
else:
|
||||
acceptedItem = None
|
||||
|
||||
if acceptedItem is not None:
|
||||
if acceptedItem is not None and acceptedItem.scene() is self:
|
||||
#print "Drag -> pre-selected item:", acceptedItem
|
||||
self.dragItem = acceptedItem
|
||||
event.currentItem = self.dragItem
|
||||
@ -352,46 +366,15 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
return ev.isAccepted()
|
||||
|
||||
def items(self, *args):
|
||||
#print 'args:', args
|
||||
items = QtGui.QGraphicsScene.items(self, *args)
|
||||
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
|
||||
## then the object returned will be different than the actual item that was originally added to the scene
|
||||
items2 = list(map(self.translateGraphicsItem, items))
|
||||
#if HAVE_SIP and isinstance(self, sip.wrapper):
|
||||
#items2 = []
|
||||
#for i in items:
|
||||
#addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem))
|
||||
#i2 = GraphicsScene._addressCache.get(addr, i)
|
||||
##print i, "==>", i2
|
||||
#items2.append(i2)
|
||||
#print 'items:', items
|
||||
return items2
|
||||
return self.translateGraphicsItems(items)
|
||||
|
||||
def selectedItems(self, *args):
|
||||
items = QtGui.QGraphicsScene.selectedItems(self, *args)
|
||||
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
|
||||
## then the object returned will be different than the actual item that was originally added to the scene
|
||||
#if HAVE_SIP and isinstance(self, sip.wrapper):
|
||||
#items2 = []
|
||||
#for i in items:
|
||||
#addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem))
|
||||
#i2 = GraphicsScene._addressCache.get(addr, i)
|
||||
##print i, "==>", i2
|
||||
#items2.append(i2)
|
||||
items2 = list(map(self.translateGraphicsItem, items))
|
||||
|
||||
#print 'items:', items
|
||||
return items2
|
||||
return self.translateGraphicsItems(items)
|
||||
|
||||
def itemAt(self, *args):
|
||||
item = QtGui.QGraphicsScene.itemAt(self, *args)
|
||||
|
||||
## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject,
|
||||
## then the object returned will be different than the actual item that was originally added to the scene
|
||||
#if HAVE_SIP and isinstance(self, sip.wrapper):
|
||||
#addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem))
|
||||
#item = GraphicsScene._addressCache.get(addr, item)
|
||||
#return item
|
||||
return self.translateGraphicsItem(item)
|
||||
|
||||
def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder, hoverable=False):
|
||||
@ -423,6 +406,8 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
for item in items:
|
||||
if hoverable and not hasattr(item, 'hoverEvent'):
|
||||
continue
|
||||
if item.scene() is not self:
|
||||
continue
|
||||
shape = item.shape() # Note: default shape() returns boundingRect()
|
||||
if shape is None:
|
||||
continue
|
||||
@ -436,7 +421,7 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
return 0
|
||||
return item.zValue() + absZValue(item.parentItem())
|
||||
|
||||
sortList(items2, lambda a,b: cmp(absZValue(b), absZValue(a)))
|
||||
items2.sort(key=absZValue, reverse=True)
|
||||
|
||||
return items2
|
||||
|
||||
@ -536,15 +521,16 @@ class GraphicsScene(QtGui.QGraphicsScene):
|
||||
|
||||
@staticmethod
|
||||
def translateGraphicsItem(item):
|
||||
## for fixing pyqt bugs where the wrong item is returned
|
||||
# This function is intended as a workaround for a problem with older
|
||||
# versions of PyQt (< 4.9?), where methods returning 'QGraphicsItem *'
|
||||
# lose the type of the QGraphicsObject subclasses and instead return
|
||||
# generic QGraphicsItem wrappers.
|
||||
if HAVE_SIP and isinstance(item, sip.wrapper):
|
||||
addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem))
|
||||
item = GraphicsScene._addressCache.get(addr, item)
|
||||
obj = item.toGraphicsObject()
|
||||
if obj is not None:
|
||||
item = obj
|
||||
return item
|
||||
|
||||
@staticmethod
|
||||
def translateGraphicsItems(items):
|
||||
return list(map(GraphicsScene.translateGraphicsItem, items))
|
||||
|
||||
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
from ..Qt import QtCore, QtGui, USE_PYSIDE, USE_PYQT5
|
||||
from ..Qt import QtCore, QtGui, QT_LIB
|
||||
from .. import exporters as exporters
|
||||
from .. import functions as fn
|
||||
from ..graphicsItems.ViewBox import ViewBox
|
||||
from ..graphicsItems.PlotItem import PlotItem
|
||||
|
||||
if USE_PYSIDE:
|
||||
if QT_LIB == 'PySide':
|
||||
from . import exportDialogTemplate_pyside as exportDialogTemplate
|
||||
elif USE_PYQT5:
|
||||
elif QT_LIB == 'PySide2':
|
||||
from . import exportDialogTemplate_pyside2 as exportDialogTemplate
|
||||
elif QT_LIB == 'PyQt5':
|
||||
from . import exportDialogTemplate_pyqt5 as exportDialogTemplate
|
||||
else:
|
||||
from . import exportDialogTemplate_pyqt as exportDialogTemplate
|
||||
@ -119,7 +121,9 @@ class ExportDialog(QtGui.QWidget):
|
||||
return
|
||||
expClass = self.exporterClasses[str(item.text())]
|
||||
exp = expClass(item=self.ui.itemTree.currentItem().gitem)
|
||||
|
||||
params = exp.parameters()
|
||||
|
||||
if params is None:
|
||||
self.ui.paramTree.clear()
|
||||
else:
|
||||
|
63
pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py
Normal file
63
pyqtgraph/GraphicsScene/exportDialogTemplate_pyside2.py
Normal file
@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'exportDialogTemplate.ui'
|
||||
#
|
||||
# Created: Sun Sep 18 19:19:58 2016
|
||||
# by: pyside2-uic running on PySide2 2.0.0~alpha0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore, QtGui, QtWidgets
|
||||
|
||||
class Ui_Form(object):
|
||||
def setupUi(self, Form):
|
||||
Form.setObjectName("Form")
|
||||
Form.resize(241, 367)
|
||||
self.gridLayout = QtWidgets.QGridLayout(Form)
|
||||
self.gridLayout.setSpacing(0)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.label = QtWidgets.QLabel(Form)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 0, 0, 1, 3)
|
||||
self.itemTree = QtWidgets.QTreeWidget(Form)
|
||||
self.itemTree.setObjectName("itemTree")
|
||||
self.itemTree.headerItem().setText(0, "1")
|
||||
self.itemTree.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3)
|
||||
self.label_2 = QtWidgets.QLabel(Form)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3)
|
||||
self.formatList = QtWidgets.QListWidget(Form)
|
||||
self.formatList.setObjectName("formatList")
|
||||
self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3)
|
||||
self.exportBtn = QtWidgets.QPushButton(Form)
|
||||
self.exportBtn.setObjectName("exportBtn")
|
||||
self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1)
|
||||
self.closeBtn = QtWidgets.QPushButton(Form)
|
||||
self.closeBtn.setObjectName("closeBtn")
|
||||
self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1)
|
||||
self.paramTree = ParameterTree(Form)
|
||||
self.paramTree.setObjectName("paramTree")
|
||||
self.paramTree.headerItem().setText(0, "1")
|
||||
self.paramTree.header().setVisible(False)
|
||||
self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3)
|
||||
self.label_3 = QtWidgets.QLabel(Form)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3)
|
||||
self.copyBtn = QtWidgets.QPushButton(Form)
|
||||
self.copyBtn.setObjectName("copyBtn")
|
||||
self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(Form)
|
||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||
|
||||
def retranslateUi(self, Form):
|
||||
Form.setWindowTitle(QtWidgets.QApplication.translate("Form", "Export", None, -1))
|
||||
self.label.setText(QtWidgets.QApplication.translate("Form", "Item to export:", None, -1))
|
||||
self.label_2.setText(QtWidgets.QApplication.translate("Form", "Export format", None, -1))
|
||||
self.exportBtn.setText(QtWidgets.QApplication.translate("Form", "Export", None, -1))
|
||||
self.closeBtn.setText(QtWidgets.QApplication.translate("Form", "Close", None, -1))
|
||||
self.label_3.setText(QtWidgets.QApplication.translate("Form", "Export options", None, -1))
|
||||
self.copyBtn.setText(QtWidgets.QApplication.translate("Form", "Copy", None, -1))
|
||||
|
||||
from ..parametertree import ParameterTree
|
@ -1,3 +1,4 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
class PlotData(object):
|
||||
@ -50,7 +51,3 @@ class PlotData(object):
|
||||
mn = np.min(self[field])
|
||||
self.minVals[field] = mn
|
||||
return mn
|
||||
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
"""
|
||||
Point.py - Extension of QPointF which adds a few missing methods.
|
||||
Copyright 2010 Luke Campagnola
|
||||
Distributed under MIT/X11 license. See license.txt for more infomation.
|
||||
Distributed under MIT/X11 license. See license.txt for more information.
|
||||
"""
|
||||
|
||||
from .Qt import QtCore
|
||||
@ -105,7 +105,13 @@ class Point(QtCore.QPointF):
|
||||
|
||||
def length(self):
|
||||
"""Returns the vector length of this Point."""
|
||||
try:
|
||||
return (self[0]**2 + self[1]**2) ** 0.5
|
||||
except OverflowError:
|
||||
try:
|
||||
return self[1] / np.sin(np.arctan2(self[1], self[0]))
|
||||
except OverflowError:
|
||||
return np.inf
|
||||
|
||||
def norm(self):
|
||||
"""Returns a vector in the same direction with unit length."""
|
||||
|
245
pyqtgraph/Qt.py
245
pyqtgraph/Qt.py
@ -1,3 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
This module exists to smooth out some of the differences between PySide and PyQt4:
|
||||
|
||||
@ -9,22 +10,23 @@ This module exists to smooth out some of the differences between PySide and PyQt
|
||||
|
||||
"""
|
||||
|
||||
import os, sys, re, time
|
||||
import os, sys, re, time, subprocess, warnings
|
||||
|
||||
from .python2_3 import asUnicode
|
||||
|
||||
PYSIDE = 'PySide'
|
||||
PYSIDE2 = 'PySide2'
|
||||
PYQT4 = 'PyQt4'
|
||||
PYQT5 = 'PyQt5'
|
||||
|
||||
QT_LIB = os.getenv('PYQTGRAPH_QT_LIB')
|
||||
|
||||
## Automatically determine whether to use PyQt or PySide (unless specified by
|
||||
## Automatically determine which Qt package to use (unless specified by
|
||||
## environment variable).
|
||||
## This is done by first checking to see whether one of the libraries
|
||||
## is already imported. If not, then attempt to import PyQt4, then PySide.
|
||||
if QT_LIB is None:
|
||||
libOrder = [PYQT4, PYSIDE, PYQT5]
|
||||
libOrder = [PYQT4, PYSIDE, PYQT5, PYSIDE2]
|
||||
|
||||
for lib in libOrder:
|
||||
if lib in sys.modules:
|
||||
@ -41,29 +43,22 @@ if QT_LIB is None:
|
||||
pass
|
||||
|
||||
if QT_LIB is None:
|
||||
raise Exception("PyQtGraph requires one of PyQt4, PyQt5 or PySide; none of these packages could be imported.")
|
||||
raise Exception("PyQtGraph requires one of PyQt4, PyQt5, PySide or PySide2; none of these packages could be imported.")
|
||||
|
||||
if QT_LIB == PYSIDE:
|
||||
from PySide import QtGui, QtCore, QtOpenGL, QtSvg
|
||||
try:
|
||||
from PySide import QtTest
|
||||
if not hasattr(QtTest.QTest, 'qWait'):
|
||||
@staticmethod
|
||||
def qWait(msec):
|
||||
start = time.time()
|
||||
QtGui.QApplication.processEvents()
|
||||
while time.time() < start + msec * 0.001:
|
||||
QtGui.QApplication.processEvents()
|
||||
QtTest.QTest.qWait = qWait
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
import PySide
|
||||
try:
|
||||
from PySide import shiboken
|
||||
isQObjectAlive = shiboken.isValid
|
||||
except ImportError:
|
||||
def isQObjectAlive(obj):
|
||||
class FailedImport(object):
|
||||
"""Used to defer ImportErrors until we are sure the module is needed.
|
||||
"""
|
||||
def __init__(self, err):
|
||||
self.err = err
|
||||
|
||||
def __getattr__(self, attr):
|
||||
raise self.err
|
||||
|
||||
|
||||
def _isQObjectAlive(obj):
|
||||
"""An approximation of PyQt's isQObjectAlive().
|
||||
"""
|
||||
try:
|
||||
if hasattr(obj, 'parent'):
|
||||
obj.parent()
|
||||
@ -76,14 +71,13 @@ if QT_LIB == PYSIDE:
|
||||
else:
|
||||
return True
|
||||
|
||||
VERSION_INFO = 'PySide ' + PySide.__version__
|
||||
|
||||
# Make a loadUiType function like PyQt has
|
||||
# Make a loadUiType function like PyQt has
|
||||
|
||||
# Credit:
|
||||
# http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313
|
||||
# Credit:
|
||||
# http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313
|
||||
|
||||
class StringIO(object):
|
||||
class _StringIO(object):
|
||||
"""Alternative to built-in StringIO needed to circumvent unicode/ascii issues"""
|
||||
def __init__(self):
|
||||
self.data = []
|
||||
@ -94,9 +88,10 @@ if QT_LIB == PYSIDE:
|
||||
def getvalue(self):
|
||||
return ''.join(map(asUnicode, self.data)).encode('utf8')
|
||||
|
||||
def loadUiType(uiFile):
|
||||
|
||||
def _loadUiType(uiFile):
|
||||
"""
|
||||
Pyside "loadUiType" command like PyQt4 has one, so we have to convert
|
||||
PySide lacks a "loadUiType" command like PyQt4's, so we have to convert
|
||||
the ui file to py code in-memory first and then execute it in a
|
||||
special frame to retrieve the form_class.
|
||||
|
||||
@ -106,66 +101,156 @@ if QT_LIB == PYSIDE:
|
||||
how to make PyQt4 and pyside look the same...
|
||||
http://stackoverflow.com/a/8717832
|
||||
"""
|
||||
import pysideuic
|
||||
import xml.etree.ElementTree as xml
|
||||
#from io import StringIO
|
||||
|
||||
if QT_LIB == "PYSIDE":
|
||||
import pysideuic
|
||||
else:
|
||||
try:
|
||||
import pyside2uic as pysideuic
|
||||
except ImportError:
|
||||
# later vserions of pyside2 have dropped pysideuic; use the uic binary instead.
|
||||
pysideuic = None
|
||||
|
||||
# get class names from ui file
|
||||
import xml.etree.ElementTree as xml
|
||||
parsed = xml.parse(uiFile)
|
||||
widget_class = parsed.find('widget').get('class')
|
||||
form_class = parsed.find('class').text
|
||||
|
||||
# convert ui file to python code
|
||||
if pysideuic is None:
|
||||
pyside2version = tuple(map(int, PySide2.__version__.split(".")))
|
||||
if pyside2version >= (5, 14) and pyside2version < (5, 14, 2, 2):
|
||||
warnings.warn('For UI compilation, it is recommended to upgrade to PySide >= 5.15')
|
||||
uipy = subprocess.check_output(['pyside2-uic', uiFile])
|
||||
else:
|
||||
o = _StringIO()
|
||||
with open(uiFile, 'r') as f:
|
||||
o = StringIO()
|
||||
frame = {}
|
||||
|
||||
pysideuic.compileUi(f, o, indent=0)
|
||||
pyc = compile(o.getvalue(), '<string>', 'exec')
|
||||
uipy = o.getvalue()
|
||||
|
||||
# exceute python code
|
||||
pyc = compile(uipy, '<string>', 'exec')
|
||||
frame = {}
|
||||
exec(pyc, frame)
|
||||
|
||||
#Fetch the base_class and form class based on their type in the xml from designer
|
||||
# fetch the base_class and form class based on their type in the xml from designer
|
||||
form_class = frame['Ui_%s'%form_class]
|
||||
base_class = eval('QtGui.%s'%widget_class)
|
||||
|
||||
return form_class, base_class
|
||||
|
||||
elif QT_LIB == PYQT4:
|
||||
|
||||
if QT_LIB == PYSIDE:
|
||||
from PySide import QtGui, QtCore
|
||||
|
||||
try:
|
||||
from PySide import QtOpenGL
|
||||
except ImportError as err:
|
||||
QtOpenGL = FailedImport(err)
|
||||
try:
|
||||
from PySide import QtSvg
|
||||
except ImportError as err:
|
||||
QtSvg = FailedImport(err)
|
||||
|
||||
try:
|
||||
from PySide import QtTest
|
||||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
try:
|
||||
from PySide import shiboken
|
||||
isQObjectAlive = shiboken.isValid
|
||||
except ImportError:
|
||||
# use approximate version
|
||||
isQObjectAlive = _isQObjectAlive
|
||||
|
||||
import PySide
|
||||
VERSION_INFO = 'PySide ' + PySide.__version__ + ' Qt ' + QtCore.__version__
|
||||
|
||||
elif QT_LIB == PYQT4:
|
||||
from PyQt4 import QtGui, QtCore, uic
|
||||
try:
|
||||
from PyQt4 import QtSvg
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtSvg = FailedImport(err)
|
||||
try:
|
||||
from PyQt4 import QtOpenGL
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtOpenGL = FailedImport(err)
|
||||
try:
|
||||
from PyQt4 import QtTest
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
|
||||
|
||||
elif QT_LIB == PYQT5:
|
||||
|
||||
# We're using PyQt5 which has a different structure so we're going to use a shim to
|
||||
# recreate the Qt4 structure for Qt5
|
||||
from PyQt5 import QtGui, QtCore, QtWidgets, uic
|
||||
|
||||
# PyQt5, starting in v5.5, calls qAbort when an exception is raised inside
|
||||
# a slot. To maintain backward compatibility (and sanity for interactive
|
||||
# users), we install a global exception hook to override this behavior.
|
||||
ver = QtCore.PYQT_VERSION_STR.split('.')
|
||||
if int(ver[1]) >= 5:
|
||||
if sys.excepthook == sys.__excepthook__:
|
||||
sys_excepthook = sys.excepthook
|
||||
def pyqt5_qabort_override(*args, **kwds):
|
||||
return sys_excepthook(*args, **kwds)
|
||||
sys.excepthook = pyqt5_qabort_override
|
||||
|
||||
try:
|
||||
from PyQt5 import QtSvg
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtSvg = FailedImport(err)
|
||||
try:
|
||||
from PyQt5 import QtOpenGL
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtOpenGL = FailedImport(err)
|
||||
try:
|
||||
from PyQt5 import QtTest
|
||||
QtTest.QTest.qWaitForWindowShown = QtTest.QTest.qWaitForWindowExposed
|
||||
except ImportError:
|
||||
pass
|
||||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
# Re-implement deprecated APIs
|
||||
VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
|
||||
|
||||
elif QT_LIB == PYSIDE2:
|
||||
from PySide2 import QtGui, QtCore, QtWidgets
|
||||
|
||||
try:
|
||||
from PySide2 import QtSvg
|
||||
except ImportError as err:
|
||||
QtSvg = FailedImport(err)
|
||||
try:
|
||||
from PySide2 import QtOpenGL
|
||||
except ImportError as err:
|
||||
QtOpenGL = FailedImport(err)
|
||||
try:
|
||||
from PySide2 import QtTest
|
||||
QtTest.QTest.qWaitForWindowShown = QtTest.QTest.qWaitForWindowExposed
|
||||
except ImportError as err:
|
||||
QtTest = FailedImport(err)
|
||||
|
||||
try:
|
||||
import shiboken2
|
||||
isQObjectAlive = shiboken2.isValid
|
||||
except ImportError:
|
||||
# use approximate version
|
||||
isQObjectAlive = _isQObjectAlive
|
||||
import PySide2
|
||||
VERSION_INFO = 'PySide2 ' + PySide2.__version__ + ' Qt ' + QtCore.__version__
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid Qt lib '%s'" % QT_LIB)
|
||||
|
||||
|
||||
# common to PyQt5 and PySide2
|
||||
if QT_LIB in [PYQT5, PYSIDE2]:
|
||||
# We're using Qt5 which has a different structure so we're going to use a shim to
|
||||
# recreate the Qt4 structure
|
||||
|
||||
__QGraphicsItem_scale = QtWidgets.QGraphicsItem.scale
|
||||
|
||||
@ -213,29 +298,65 @@ elif QT_LIB == PYQT5:
|
||||
if o.startswith('Q'):
|
||||
setattr(QtGui, o, getattr(QtWidgets,o) )
|
||||
|
||||
VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
|
||||
|
||||
else:
|
||||
raise ValueError("Invalid Qt lib '%s'" % QT_LIB)
|
||||
# Common to PySide and PySide2
|
||||
if QT_LIB in [PYSIDE, PYSIDE2]:
|
||||
QtVersion = QtCore.__version__
|
||||
loadUiType = _loadUiType
|
||||
|
||||
# PySide does not implement qWait
|
||||
if not isinstance(QtTest, FailedImport):
|
||||
if not hasattr(QtTest.QTest, 'qWait'):
|
||||
@staticmethod
|
||||
def qWait(msec):
|
||||
start = time.time()
|
||||
QtGui.QApplication.processEvents()
|
||||
while time.time() < start + msec * 0.001:
|
||||
QtGui.QApplication.processEvents()
|
||||
QtTest.QTest.qWait = qWait
|
||||
|
||||
|
||||
# Common to PyQt4 and 5
|
||||
if QT_LIB.startswith('PyQt'):
|
||||
if QT_LIB in [PYQT4, PYQT5]:
|
||||
QtVersion = QtCore.QT_VERSION_STR
|
||||
|
||||
import sip
|
||||
def isQObjectAlive(obj):
|
||||
return not sip.isdeleted(obj)
|
||||
|
||||
loadUiType = uic.loadUiType
|
||||
|
||||
QtCore.Signal = QtCore.pyqtSignal
|
||||
|
||||
|
||||
|
||||
## Make sure we have Qt >= 4.7
|
||||
versionReq = [4, 7]
|
||||
# USE_XXX variables are deprecated
|
||||
USE_PYSIDE = QT_LIB == PYSIDE
|
||||
USE_PYQT4 = QT_LIB == PYQT4
|
||||
USE_PYQT5 = QT_LIB == PYQT5
|
||||
QtVersion = PySide.QtCore.__version__ if QT_LIB == PYSIDE else QtCore.QT_VERSION_STR
|
||||
|
||||
|
||||
## Make sure we have Qt >= 4.7
|
||||
versionReq = [4, 7]
|
||||
m = re.match(r'(\d+)\.(\d+).*', QtVersion)
|
||||
if m is not None and list(map(int, m.groups())) < versionReq:
|
||||
print(list(map(int, m.groups())))
|
||||
raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))
|
||||
|
||||
|
||||
QAPP = None
|
||||
def mkQApp(name=None):
|
||||
"""
|
||||
Creates new QApplication or returns current instance if existing.
|
||||
|
||||
============== ========================================================
|
||||
**Arguments:**
|
||||
name (str) Application name, passed to Qt
|
||||
============== ========================================================
|
||||
"""
|
||||
global QAPP
|
||||
QAPP = QtGui.QApplication.instance()
|
||||
if QAPP is None:
|
||||
QAPP = QtGui.QApplication(sys.argv or ["pyqtgraph"])
|
||||
if name is not None:
|
||||
QAPP.setApplicationName(name)
|
||||
return QAPP
|
||||
|
@ -113,7 +113,7 @@ class SRTTransform3D(Transform3D):
|
||||
|
||||
def setFromMatrix(self, m):
|
||||
"""
|
||||
Set this transform mased on the elements of *m*
|
||||
Set this transform based on the elements of *m*
|
||||
The input matrix must be affine AND have no shear,
|
||||
otherwise the conversion will most likely fail.
|
||||
"""
|
||||
|
@ -67,11 +67,11 @@ class SignalProxy(QtCore.QObject):
|
||||
"""If there is a signal queued up, send it now."""
|
||||
if self.args is None or self.block:
|
||||
return False
|
||||
#self.emit(self.signal, *self.args)
|
||||
self.sigDelayed.emit(self.args)
|
||||
self.args = None
|
||||
args, self.args = self.args, None
|
||||
self.timer.stop()
|
||||
self.lastFlushTime = time()
|
||||
#self.emit(self.signal, *self.args)
|
||||
self.sigDelayed.emit(args)
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
@ -81,7 +81,7 @@ class SignalProxy(QtCore.QObject):
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
self.sigDelayed.disconnect(self.slot())
|
||||
self.sigDelayed.disconnect(self.slot)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
@ -1,13 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .Qt import QtCore, QtGui
|
||||
from . import functions as fn
|
||||
from .Vector import Vector
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Transform3D(QtGui.QMatrix4x4):
|
||||
"""
|
||||
Extension of QMatrix4x4 with some helpful methods added.
|
||||
"""
|
||||
def __init__(self, *args):
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], (list, tuple, np.ndarray)):
|
||||
args = [x for y in args[0] for x in y]
|
||||
if len(args) != 16:
|
||||
raise TypeError("Single argument to Transform3D must have 16 elements.")
|
||||
elif isinstance(args[0], QtGui.QMatrix4x4):
|
||||
args = list(args[0].copyDataTo())
|
||||
|
||||
QtGui.QMatrix4x4.__init__(self, *args)
|
||||
|
||||
def matrix(self, nd=3):
|
||||
@ -25,8 +35,15 @@ class Transform3D(QtGui.QMatrix4x4):
|
||||
"""
|
||||
Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
|
||||
"""
|
||||
if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3):
|
||||
if isinstance(obj, np.ndarray) and obj.shape[0] in (2,3):
|
||||
if obj.ndim >= 2:
|
||||
return fn.transformCoordinates(self, obj)
|
||||
elif obj.ndim == 1:
|
||||
v = QtGui.QMatrix4x4.map(self, Vector(obj))
|
||||
return np.array([v.x(), v.y(), v.z()])[:obj.shape[0]]
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
v = QtGui.QMatrix4x4.map(self, Vector(obj))
|
||||
return type(obj)([v.x(), v.y(), v.z()])[:len(obj)]
|
||||
else:
|
||||
return QtGui.QMatrix4x4.map(self, obj)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user