70 Commits

Author SHA1 Message Date
Kristjan Komlosi ec8d3ca94c rename init to app change camel case to snake case, added __name__ conditional 2020-08-27 14:22:35 +02:00
Kristjan Komlosi 88687124f2 fixing packaging #1 2020-08-27 13:52:24 +02:00
Kristjan Komlosi 97233f27c3 fixing packaging #1 2020-08-27 11:14:01 +02:00
Kristjan Komlosi b35aacad3f add travis config 2020-07-22 11:54:46 +02:00
Kristjan Komlosi 2136406a28 changed JS to match 1c49490 2020-07-14 11:12:43 +02:00
Kristjan Komlosi ff13d8eeeb Add a semistandard badge to readme 2020-07-14 10:54:06 +02:00
Kristjan Komlosi fece181c6f added an ESLint config file 2020-07-14 10:38:17 +02:00
Kristjan Komlosi 85d7be4117 refactor updateData to use simpler $.get method 2020-07-14 10:37:57 +02:00
Kristjan Komlosi 56e8282a82 specter --> spectrum 2020-07-14 10:02:18 +02:00
Kristjan Komlosi 1c4949020b change element IDs for irradiance values and table rows 2020-07-14 10:01:13 +02:00
Kristjan Komlosi a71d8565c4 Change HTML indent to 4 2020-07-14 09:59:04 +02:00
Kristjan Komlosi 7e09a1e51f modify index.html into Jinja2 rendering template 2020-07-13 14:28:09 +02:00
Kristjan Komlosi 92fac02a9e formatting 2020-07-13 14:01:19 +02:00
Kristjan Komlosi 320bcae78e Merge branch 'development' of github.com:cls-02/TeraHz into developmen 2020-07-13 13:56:30 +02:00
Kristjan Komlosi d6a0efd69b Merge branch 'development' of github.com:cls-02/TeraHz into development 2020-07-13 13:55:44 +02:00
Kristjan Komlosi 40a79af9af moved static files to conform with Flask 2020-07-13 13:54:30 +02:00
Kristjan Komlosi 229719cc5c moved static files to conform with Flask 2020-07-13 13:47:13 +02:00
Kristjan Komlosi ac6345c5b5 modify gitignore 2020-07-13 13:42:08 +02:00
Kristjan Komlosi 7773788e20 Merge branch 'development' of github.com:cls-02/TeraHz into development 2020-07-13 13:28:36 +02:00
Kristjan Komlosi ac154e6770 moved templates 2020-07-13 13:28:32 +02:00
Kristjan Komlosi a4ec93f412 moved templates 2020-07-13 13:23:24 +02:00
Kristjan Komlosi 16d7624154 update copyright and add a root route 2020-07-13 13:13:01 +02:00
Kristjan Komlosi 54b93cb05c cleaning up 2020-07-13 13:11:10 +02:00
Kristjan Komlosi 3d3254c4a7 formatting 2020-07-13 13:01:34 +02:00
Kristjan Komlosi cfe6e3c286 remove old-unused files 2020-07-13 12:13:31 +02:00
Kristjan Komlosi 7a3877b34b remove unused FastCGI configuration file 2020-07-13 11:55:21 +02:00
Kristjan Komlosi f20dd30292 remove unused config file 2020-07-13 11:54:35 +02:00
Kristjan Komlosi ef5c87d8d2 removed janky and unused storage module 2020-07-13 11:54:20 +02:00
Kristjan Komlosi 4878df9ca8 Shortened error messages 2020-07-13 11:52:22 +02:00
Kristjan Komlosi 89ea9564c8 fixed onclick binding 2020-06-02 10:45:16 +02:00
Kristjan Komlosi 00c619ce34 fixed typo 2020-06-02 09:53:09 +02:00
Kristjan Komlosi 8f22a6baee removed lighttpd configuration as the defaults work fine 2020-06-02 09:52:40 +02:00
Kristjan Komlosi fa2c1cccb1 removed old pipfiles 2020-06-02 09:52:08 +02:00
Kristjan Komlosi 7058512ee0 tidied comments, condensed UV calculation, removed redundant getA/B/I() 2020-04-24 16:49:09 +02:00
Kristjan Komlosi 325f1176c3 tidied translation tables 2020-04-24 16:47:43 +02:00
Kristjan Komlosi 0a9e610dd7 pruned import 2020-04-24 16:46:53 +02:00
Kristjan Komlosi 43b5277923 pipenv update 2020-04-24 16:46:27 +02:00
Kristjan Komlosi 5265ba9ae9 hotfix #2 2020-02-15 22:02:36 +01:00
Kristjan Komlosi 16e9c52194 rc.local hotfix 2020-02-15 21:59:41 +01:00
Kristjan Komlosi c5db357cc0 fixed lighttpd root directory 2020-02-15 21:50:03 +01:00
Kristjan Komlosi b7eb29f78b made rc.local executable 2020-02-15 21:46:12 +01:00
Kristjan Komlosi 1457655f16 GUI redesign 2020-02-15 15:56:28 +01:00
Kristjan Komlosi a3f3558f7c fixed wavelength order in spectrogram 2020-02-14 14:18:38 +01:00
Kristjan Komlosi bc72207187 fixed broken git headers 2019-11-18 17:19:18 +01:00
Kristjan Komlosi 7837f605cf Merge branch 'master' of github.com:cls-02/TeraHz 2019-11-17 22:23:28 +01:00
Kristjan Komlosi 3d1a925ba6 added school logo 2019-11-17 22:23:09 +01:00
Kristjan Komlosi 682fe20db2 added school logo 2019-11-17 22:21:54 +01:00
Kristjan Komlosi 949dd0bef2 Updated the main readme 2019-11-17 22:04:57 +01:00
Kristjan Komlosi 58cb777935 Documentation updates 2019-11-17 19:07:47 +01:00
Kristjan Komlosi 6ffc4f8aa2 Updated documentation pages titles 2019-11-12 21:30:48 +01:00
Kristjan Komlosi 87bd420d31 fixed a broken markdown list 2019-11-12 21:30:00 +01:00
Kristjan Komlosi 3e1a43c10f updated documentation 2019-11-12 21:28:27 +01:00
Kristjan Komlosi ed0b21a496 Shortened the build guide significantly 2019-11-12 21:20:28 +01:00
Kristjan Komlosi b93358dc32 made install.sh directory independent 2019-11-12 20:29:34 +01:00
Kristjan Komlosi 7f8e69fcf3 fixed gitignore to reinclude wrongly ignored frontend libraries 2019-11-12 20:23:24 +01:00
Kristjan Komlosi 751095a5a1 removed old sensor documentation 2019-11-12 20:03:03 +01:00
Kristjan Komlosi 665cf6050b removed old utils virtualenv 2019-11-12 19:56:22 +01:00
Kristjan Komlosi 8e7435ff8f cleared previously tracked gitignored files 2019-11-12 19:43:50 +01:00
Kristjan Komlosi 08310e074d .gitignore update 2019-11-12 19:40:02 +01:00
Kristjan Komlosi 598352f526 fixed install.sh to make edit_ssid executable 2019-11-12 19:29:32 +01:00
Kristjan Komlosi e88171b31c fixed configuration files 2019-11-12 19:23:57 +01:00
Kristjan Komlosi a5b393a879 made edit_ssid.sh executable 2019-11-12 19:13:07 +01:00
Kristjan Komlosi 9b4e733209 added interfaces file 2019-11-11 19:06:46 +01:00
Kristjan Komlosi 4cf102946e update ssid at each boot 2019-11-11 17:54:41 +01:00
Kristjan Komlosi c72c0800fc made edit_ssid.sh path-independent 2019-11-11 17:41:55 +01:00
Kristjan Komlosi 095a95c1eb fixed relative path problems in run.sh 2019-11-11 17:34:18 +01:00
Kristjan Komlosi fd5e482de9 made backend startup script executable 2019-11-11 17:14:16 +01:00
Kristjan Komlosi ccbeb20f70 Merge branch 'buster' of github.com:cls-02/TeraHz into buster 2019-11-11 16:59:06 +01:00
Kristjan Komlosi a4dca60892 removed lighttpd config files, as the frontend now uses defaults 2019-11-11 16:58:58 +01:00
Kristjan Komlosi 95e22b5d1f added gunicorn to pip3 dependencies 2019-11-10 18:37:55 +01:00
6074 changed files with 669 additions and 1354500 deletions
BIN
View File
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
parserOptions:
ecmaVersion: 2019
env:
browser: true
jquery: true
+132 -1
View File
@@ -1 +1,132 @@
site/ # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Include frontend ibraries though
!terahz/templates/lib
+1
View File
@@ -0,0 +1 @@
language: python
View File
+15
View File
@@ -0,0 +1,15 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
flask = "*"
smbus2 = "*"
pyserial = "*"
pandas = "*"
[requires]
python_version = "3.8"
Generated
+184
View File
@@ -0,0 +1,184 @@
{
"_meta": {
"hash": {
"sha256": "7fb3ae0afbe0d0c2af92f618aa1c54442566daeb561480eee2c2a299f1c2bee7"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.8"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"version": "==7.1.2"
},
"flask": {
"hashes": [
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
],
"index": "pypi",
"version": "==1.1.2"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0",
"sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"
],
"version": "==2.11.2"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"
],
"version": "==1.1.1"
},
"numpy": {
"hashes": [
"sha256:082f8d4dd69b6b688f64f509b91d482362124986d98dc7dc5f5e9f9b9c3bb983",
"sha256:1bc0145999e8cb8aed9d4e65dd8b139adf1919e521177f198529687dbf613065",
"sha256:309cbcfaa103fc9a33ec16d2d62569d541b79f828c382556ff072442226d1968",
"sha256:3673c8b2b29077f1b7b3a848794f8e11f401ba0b71c49fbd26fb40b71788b132",
"sha256:480fdd4dbda4dd6b638d3863da3be82873bba6d32d1fc12ea1b8486ac7b8d129",
"sha256:56ef7f56470c24bb67fb43dae442e946a6ce172f97c69f8d067ff8550cf782ff",
"sha256:5a936fd51049541d86ccdeef2833cc89a18e4d3808fe58a8abeb802665c5af93",
"sha256:5b6885c12784a27e957294b60f97e8b5b4174c7504665333c5e94fbf41ae5d6a",
"sha256:667c07063940e934287993366ad5f56766bc009017b4a0fe91dbd07960d0aba7",
"sha256:7ed448ff4eaffeb01094959b19cbaf998ecdee9ef9932381420d514e446601cd",
"sha256:8343bf67c72e09cfabfab55ad4a43ce3f6bf6e6ced7acf70f45ded9ebb425055",
"sha256:92feb989b47f83ebef246adabc7ff3b9a59ac30601c3f6819f8913458610bdcc",
"sha256:935c27ae2760c21cd7354402546f6be21d3d0c806fffe967f745d5f2de5005a7",
"sha256:aaf42a04b472d12515debc621c31cf16c215e332242e7a9f56403d814c744624",
"sha256:b12e639378c741add21fbffd16ba5ad25c0a1a17cf2b6fe4288feeb65144f35b",
"sha256:b1cca51512299841bf69add3b75361779962f9cee7d9ee3bb446d5982e925b69",
"sha256:b8456987b637232602ceb4d663cb34106f7eb780e247d51a260b84760fd8f491",
"sha256:b9792b0ac0130b277536ab8944e7b754c69560dac0415dd4b2dbd16b902c8954",
"sha256:c9591886fc9cbe5532d5df85cb8e0cc3b44ba8ce4367bd4cf1b93dc19713da72",
"sha256:cf1347450c0b7644ea142712619533553f02ef23f92f781312f6a3553d031fc7",
"sha256:de8b4a9b56255797cbddb93281ed92acbc510fb7b15df3f01bd28f46ebc4edae",
"sha256:e1b1dc0372f530f26a03578ac75d5e51b3868b9b76cd2facba4c9ee0eb252ab1",
"sha256:e45f8e981a0ab47103181773cc0a54e650b2aef8c7b6cd07405d0fa8d869444a",
"sha256:e4f6d3c53911a9d103d8ec9518190e52a8b945bab021745af4939cfc7c0d4a9e",
"sha256:ed8a311493cf5480a2ebc597d1e177231984c818a86875126cfd004241a73c3e",
"sha256:ef71a1d4fd4858596ae80ad1ec76404ad29701f8ca7cdcebc50300178db14dfc"
],
"version": "==1.19.1"
},
"pandas": {
"hashes": [
"sha256:01b1e536eb960822c5e6b58357cad8c4b492a336f4a5630bf0b598566462a578",
"sha256:0246c67cbaaaac8d25fed8d4cf2d8897bd858f0e540e8528a75281cee9ac516d",
"sha256:0366150fe8ee37ef89a45d3093e05026b5f895e42bbce3902ce3b6427f1b8471",
"sha256:16ae070c47474008769fc443ac765ffd88c3506b4a82966e7a605592978896f9",
"sha256:1acc2bd7fc95e5408a4456897c2c2a1ae7c6acefe108d90479ab6d98d34fcc3d",
"sha256:391db82ebeb886143b96b9c6c6166686c9a272d00020e4e39ad63b792542d9e2",
"sha256:41675323d4fcdd15abde068607cad150dfe17f7d32290ee128e5fea98442bd09",
"sha256:53328284a7bb046e2e885fd1b8c078bd896d7fc4575b915d4936f54984a2ba67",
"sha256:57c5f6be49259cde8e6f71c2bf240a26b071569cabc04c751358495d09419e56",
"sha256:84c101d0f7bbf0d9f1be9a2f29f6fcc12415442558d067164e50a56edfb732b4",
"sha256:88930c74f69e97b17703600233c0eaf1f4f4dd10c14633d522724c5c1b963ec4",
"sha256:8c9ec12c480c4d915e23ee9c8a2d8eba8509986f35f307771045c1294a2e5b73",
"sha256:a81c4bf9c59010aa3efddbb6b9fc84a9b76dc0b4da2c2c2d50f06a9ef6ac0004",
"sha256:d9644ac996149b2a51325d48d77e25c911e01aa6d39dc1b64be679cd71f683ec",
"sha256:e4b6c98f45695799990da328e6fd7d6187be32752ed64c2f22326ad66762d179",
"sha256:fe6f1623376b616e03d51f0dd95afd862cf9a33c18cf55ce0ed4bbe1c4444391"
],
"index": "pypi",
"version": "==1.1.1"
},
"pyserial": {
"hashes": [
"sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627",
"sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"
],
"index": "pypi",
"version": "==3.4"
},
"python-dateutil": {
"hashes": [
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"version": "==2.8.1"
},
"pytz": {
"hashes": [
"sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
"sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
"version": "==2020.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"version": "==1.15.0"
},
"smbus2": {
"hashes": [
"sha256:210e66eebe4d0b1fe836b3ec2751841942e1c4918c0b429b20a0e20a222228b4"
],
"index": "pypi",
"version": "==0.3.0"
},
"werkzeug": {
"hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
],
"version": "==1.0.1"
}
},
"develop": {}
}
+32 -17
View File
@@ -1,23 +1,38 @@
# TeraHz
[![Documentation Status](https://readthedocs.org/projects/terahz/badge/?version=latest)](https://terahz.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/terahz/badge/?version=latest)](https://terahz.readthedocs.io/en/latest/?badge=latest)
[![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/standard/semistandard)
# <img alt="TeraHz logo" src="docs/imgs/logo-sq.png" width="200px"> TeraHz
TeraHz is a low-cost spectrometer based on a Raspberry Pi 3 or 3 B+ and three sensors: *Za slovensko različico se odpravite na <http://git.sckr-lab.tk/kristjank/TeraHz>*
+ [__AS7265x__](https://www.tindie.com/products/onehorse/compact-as7265x-spectrometer/)
is a 18 channel spectrometer chipset that provides the device with spectral data
+ [__VEML6075__](https://www.sparkfun.com/products/15089) is an
UVA/UVB sensor
+ [__APDS-9301__](https://www.sparkfun.com/products/14350) is a calibrated illuminance (lux) meter that provides the device with reliable readings
## Why? TeraHz is a low-cost portable spectrometer based on Raspberry Pi and a few
Because people and institutions could use an affordable and accurate light-analysing device that is also portable, easy to use and simple to assemble. TeraHz was started as an answer to our high school not being able to afford a commercially available solution. One TeraHz spectrometer costs around 150$ in parts, which makes it a competitive alternative to other solutions on the market today. commonly available sensor breakout boards. It's designed to bring low-cost
scientific exploration of the light spectrum to educational institutions that
cannot afford the options available on the current market. It costs less than
200€ with money to spare and uses only free, libre and open-source software
(FLOSS). It is free to use under the ISC license, a spiritual successor to the
classic 3-clause BSD license.
## How to install
Stable releases are available under the releases tab and can be installed either
by flashing a **preinstalled** ready to boot image with a stable version of
TeraHz preinstalled or by flashing a **preconfigured** image of DietPi and
installing TeraHz manually, which is useful if you want to test bleeding edge
releases. For more information, check out the [Build
guide](https://terahz.readthedocs.io/en/latest/build/).
## Docs
TeraHz usually works out of the box. A wireless network named `TeraHz_XXXXXX`
(XXXXXX = the last half of the MAC address) will appear. Password is
`terahertz`. After connection, open a web browser and visit `terahz.site`.
The UI will appear. To fetch data from the sensors, press the 'Get Data' button.
The readings are then plotted and written into the tables below the graph.
More documentation is available at <https://terahz.readthedocs.io>
## Development team ## Development team
Copyright 2018, 2019 - Kristjan Komloši (cls-02) - Project leader and main programmer
- Jakob Kosec (D3m1j4ck) - Frontend designer
- Juš Dolžan (ANormalPerson) - Math double-checker
- Kristjan "cls-02" Komloši (electronics, sensor drivers, backend) <img alt="TeraHz logo" src="http://www.sckr.si/documents/upload/konektor/logo/_SC.gif" width="200px">
- Jakob "D3m1j4ck" Kosec (frontend) TeraHz has been developed under guidance and financial support of Šolski Center Kranj (Kranj School Centre). Without their support, this project might have never been possible.
I would also like to thank Juš "ANormalPerson" Dolžan, who decided to leave the
team, but helped me a lot with backend development.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-17
View File
@@ -1,17 +0,0 @@
# app.py - main backend program
'''Main TeraHz backend program'''
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
from flask import Flask
import flask
import sensors
app = Flask(__name__)
@app.route('/data')
def sendData():
'''Responder function for /data route'''
s = sensors.Spectrometer(path='/dev/serial0', baudrate=115200, tout=1)
u = sensors.UVSensor()
l = sensors.LxMeter()
response = flask.jsonify([s.getData(), l.getData(), u.getABI()])
response.headers.add('Access-Control-Allow-Origin', '*')
return response
BIN
View File
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
#!/bin/bash
# run.sh - run the backend server
sudo gunicorn app:app -b 0.0.0.0:5000 &
disown
BIN
View File
Binary file not shown.
-37
View File
@@ -1,37 +0,0 @@
# storage.py - storage backend for TeraHz
'''TeraHz storage backend'''
# Copyright Kristjan Komloši 2019
# All code in this file is licensed under the ISC license,
# provided in LICENSE.txt
import sqlite3
class jsonStorage:
'''Class for simple sqlite3 database of JSON entries'''
def __init__(self, dbFile):
'''Storage object constructor. Argument is filename'''
self.db = sqlite3.connect(dbFile)
def listJSONs(self):
'''Returns a list of all existing entries.'''
c = self.db.cursor()
c.execute('SELECT * FROM storage')
result = c.fetchall()
c.close()
return result
def storeJSON(self, jsonString, comment):
'''Stores a JSON entry along with a timestamp and a comment.'''
c = self.db.cursor()
c.execute(('INSERT INTO storage VALUES (datetime'
'(\'now\', \'localtime\'), ?, ?)'), (comment, jsonString))
c.close()
self.db.commit()
def retrieveJSON(self, timestamp):
'''Retrieves a JSON entry. Takes a timestamp string'''
c = self.db.cursor()
c.execute('SELECT * FROM storage WHERE timestamp = ?', (timestamp,))
result = c.fetchall()
c.close()
return result
View File
-7
View File
@@ -1,7 +0,0 @@
#!/usr/bin/python3
# Minimal flup configuration for Flask
from flup.server.fcgi import WSGIServer
from app import app
if __name__ == '__main__':
WSGIServer(app, bindAddress='/var/www/api/terahz.sock').run()
+29 -159
View File
@@ -1,169 +1,39 @@
# TeraHz build guide # TeraHz build guide
This document describes the process of building/installing TeraHz from the Git In its early development phase, TeraHz was hard and time-consuming to compile and install.
repository. This is not case now, as the more optimized DietPi Linux distribution allows
better performance and simpler configuration than formerly used Raspbian.
## Warning ## Downloading the complete image
The recommended way of getting TeraHz is the official Raspberry Pi SD card image The easiest way to get TeraHz is to download the premade complete image and
provided under the releases tab in the GitHub repository. Installing TeraHz from write it to an SD card. It already has TeraHz installed and **will work out of
source is a time consuming and painful process, even more so if you don't know the box** with the correct hardware. The image is designed to run from a 16 GB
what you're doing, and whatever you end up building __will not be officially micro SD card, class 10 or higher is recommended for snappy performance. The
supported__ (unless you're a core developer). recommended image writer is Etcher
With this warning out of the way, let's begin. Please note that while this installation process is the easiest to perform, it
does not guarantee the most recent TeraHz software. Complete images take time to
prepare and despite the developer's best efforts aren't guaranteed to be always
up-to-date.
## Getting the latest sources ## Downloading the clean DietPi image and installing TeraHz manually
The most reliable way to get working source code is by cloning the official This process is a bit more involved, but the version of TeraHz installed is
GitHub repository and checking out the `development-stable` tag. This tag marks guaranteed to be the latest one available from the repository.
the latest confirmed working commit. Building from the master branch is somewhat
risky, and building from development branches is straight up stupid if you're
not a developer.
Make sure that the repository is cloned into `/home/pi/TeraHz`, as Lighttpd The SD card image used in this case also contains some pre-configuration, but no
expects to find frontend files inside this directory. TeraHz code is installed. To install TeraHz, you'll need a console access to the
Raspberry Pi, preferably an SSH console over a wired network. DietPi is configured
to get an IP over DHCP. A tool such as arp-scan on linux is very helpful at determining
the device's IP address.
After cloning and checking out, check the documentation for module dependencies After connecting to the Raspberry Pi with username `root` and password `terahz`,
and the required version of python in the `docs/dependencies.md` file. TeraHz can be installed by cloning the Git repository and running the `etcs/install.sh`
script.
## Installing Python
This step depends a lot on the platform you're using. TeraHz was developed with
Raspberry Pi and Raspbian in mind. If you're familiar with Raspbian enough,
you'll know that the latest version of Python available is `3.5`, which is too
obsolete to run TeraHz and the required modules consistently. This leaves us
with compiling Python from source. __This step is guaranteed to be slow,
overnight compiling with something like tmux is recommended.__
### Pre-requirements
Installing python without most C libraries will lead to a rather minimalistic
Python install, missing a lot of important modules. To prevent this, update
the system packages. After that, reboot.
``` ```
sudo apt update git clone https://github.com/cls-02/TeraHz.git
sudo apt full-upgrade cd TeraHz/etcs
sudo reboot ./install.sh
``` ```
Install the required build tools, libraries and their headers. When the installation completes, reboot the Raspberry Pi and it will
automatically boot into TeraHz.
```
sudo apt-get install build-essential tk-dev libncurses5-dev libncursesw5-dev \
libreadline6-dev libdb5.3-dev libgdbm-dev libsqlite3-dev libssl-dev libbz2-dev \
libexpat1-dev liblzma-dev zlib1g-dev
```
### Compiling
Compiling Python from source is, in fact pretty easy, just time-consuming. To combat
that, using tmux to detach and later reattach the session is advised.
Python is packaged in many forms, but you'll be using the most basic
of them all: a gzipped tarball. Download and decompress it, then cd into its
directory.
```
wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tgz
tar -xzf Python-3.6.8.tgz
cd Python-3.6.8
```
Python's build process is pretty classic, a `.configure` script and a Makefile.
Using the `-j` option with Make can reduce the compile time significantly. Go
with as many threads as you have cores: `-j 4` works great on the Pi 3 B/B+.
```
./configure
make -j4
```
When the compilation ends, install your freshly built version of python.
```
sudo make altinstall
```
"Altinstall" means that the new version of Python will be installed beside the
existing version, and all related commands will use the full naming scheme:
think `python3.6` or `pip3.6` instead of the shorter `python3` or `pip3`.
### Modules
Another painfully slow part is the installation of all the required modules
needed by TeraHz. Luckily, `pip3.6` takes care of the entire installation
process. As before, using tmux is advised.
```
pip3.6 install smbus pyserial flask pandas
```
## Raspi-config
For some law-obeying reason, Raspbian locks down the Wi-Fi interface card until
the Wi-Fi country is set. This means that we will need to set it manually through
the `raspi-config` program. It requires superuser privileges.
```
sudo raspi-config
```
![1](imgs/raspi-config/1.png)
Configure the network hostname to something specific. If setting up multiple
TeraHz machines, make their hostnames unique so you can tell them apart.
![](imgs/raspi-config/3.png)
![](imgs/raspi-config/4.png)
Enable SSH and I2C interfaces.
![](imgs/raspi-config/15.png)
![](imgs/raspi-config/5.png)
![](imgs/raspi-config/6.png)
![](imgs/raspi-config/7.png)
![](imgs/raspi-config/19.png)
Expand the root filesystem along the entire SD card.
![](imgs/raspi-config/17.png)
![](imgs/raspi-config/8.png)
![](imgs/raspi-config/9.png)
Set the Wi-Fi country to the country you'll be using TeraHz in.
![](imgs/raspi-config/10.png)
![](imgs/raspi-config/11.png)
![](imgs/raspi-config/12.png)
![](imgs/raspi-config/13.png)
![](imgs/raspi-config/14.png)
Save and reboot to enable Wi-Fi
## Installing packages
In addition to what's already installed, TeraHz requires the following daemons
to run:
- Lighttpd - Frontend HTTP server
- Dnsmasq - DNS and DHCP server, used to redirect the `terahz.site` domain
- Hostapd - Wi-Fi access point
They are available from the Raspbian repository. Install it via `apt`.
```
apt install hostapd dnsmasq hostapd
```
## Configuring daemons
By default, the daemons we installed are disabled and start only manually. To
change that, enable them through systemctl. Hostapd conflicts with
wpa_supplicant, the solution is to disable wpa_supplicant (this will break your
wireless connections, so use wired ethernet).
```
sudo systemctl unmask hostapd
sudo systemctl stop wpa_supplicant
sudo systemctl disable wpa_supplicant
sudo systemctl enable dnsmasq hostapd lighttpd
```
## Copying configuration files
To simplify the process of configuring Raspbian to run TeraHz, sample
configuration file are provided in the `etcs` subdirectory of the Git
repository.
These files have been verified to work, but it's not a brilliant idea to just
copy them into your `/etc` directory. Use them carefully and more as a template
for your own configuration rather than as a _de facto_ way of configuring
TeraHz.
+22 -24
View File
@@ -1,26 +1,24 @@
# Development-stable dependencies # 1.0.0-rc1 Dependencies
The current development version of TeraHz has been verified to work with: The 1.0.0-rc1 version of TeraHz is confirmed to work with:
- DietPi 6.26.3
- Raspbian Stretch (9) - Python 3.7.3
- Python 3.6.8 (built from source code and altinstall'd) - Pip 18.1
- Module versions (direct `pip3.6 list` output): - Following versions of pip3 packages:
``` ```
Package Version Click 7.0
--------------- --------- Flask 1.1.1
Click 7.0 gunicorn 20.0.0
Flask 1.0.3 itsdangerous 1.1.0
itsdangerous 1.1.0 Jinja2 2.10.3
Jinja2 2.10.1 MarkupSafe 1.1.1
MarkupSafe 1.1.1 numpy 1.17.4
numpy 1.16.4 pandas 0.25.3
pandas 0.24.2 pip 18.1
pip 18.1 pyserial 3.4
pyserial 3.4 python-dateutil 2.8.1
python-dateutil 2.8.0 pytz 2019.3
pytz 2019.1 setuptools 41.6.0
setuptools 40.6.2 six 1.13.0
six 1.12.0 smbus2 0.3.0
smbus 1.1.post2 Werkzeug 0.16.0
Werkzeug 0.15.4
``` ```
+5 -10
View File
@@ -7,17 +7,12 @@ TeraHz was developed on and for the Raspberry Pi 3 Model B+. Compatibility with
other Raspberries can probably be achieved by tweaking the device paths in the other Raspberries can probably be achieved by tweaking the device paths in the
`app.py` file, but isn't confirmed at this point. Theoretically, 3 Model B and `app.py` file, but isn't confirmed at this point. Theoretically, 3 Model B and
Zero W should work out of the box, but models without Wi-Fi will need an Zero W should work out of the box, but models without Wi-Fi will need an
external Wi-Fi adapter if Wi-Fi functionality is desired. The practicality of external Wi-Fi adapter if Wi-Fi functionality is desired.
compiling Python on the first generation of Raspberry Pis is also very
questionable.
Sensors required for operation are: TeraHz depends on three separate sensor boards:
+ AS7265x - AS7265x spectral chipset
+ VEML6075 - VEML6075 UV sensor
+ APDS-9301 - APDS-9301 lux-meter
They provide the spectrometry data, UV data and illuminance data, respectively.
They all support I2C, AS7265x supports UART in addition.
The sensors leech power from the GPIO connector, thus eliminating the need for a The sensors leech power from the GPIO connector, thus eliminating the need for a
separate power supply. The necessary power for the whole system is delivered through separate power supply. The necessary power for the whole system is delivered through
+2 -9
View File
@@ -21,13 +21,9 @@ project when basic testing will be done.
GPIO can be routed to the PCB with a standard old IDE disk cable, and terminated GPIO can be routed to the PCB with a standard old IDE disk cable, and terminated
with another 40-pin connector at the PCB. Sensor breakouts should be mounted with another 40-pin connector at the PCB. Sensor breakouts should be mounted
<<<<<<< HEAD
through standard 0.1" connectors, male on the sensor breakout and female on the through standard 0.1" connectors, male on the sensor breakout and female on the
PCB. A shitty add-on header and a shitty add-on header v1.69bis can't hurt, either. PCB. A shitty add-on header and a shitty add-on header v1.69bis can't hurt, either.
=======
through standard 0.1" connectors, male on the sensor brakout and female on the
PCB. A shitty addon header and a shitty addon header v1.69bis can't hurt, either.
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
## SMBus sensors ## SMBus sensors
SMBus is a well-defined version of the well-known I2C bus, widely used SMBus is a well-defined version of the well-known I2C bus, widely used
@@ -39,11 +35,8 @@ Pins are familiarly marked as SDA and SCL, the same as with classic I2C. They
connect to the SDA and SCL pins on the VEML6075 and APDS-9301 sensor. connect to the SDA and SCL pins on the VEML6075 and APDS-9301 sensor.
## UART sensor ## UART sensor
<<<<<<< HEAD
Spectral sensor attaches through the UART port on the Raspberry pi (see picture). Spectral sensor attaches through the UART port on the Raspberry pi (see picture).
=======
Spectrometry sensor attaches through the UART port on the Raspberry pi (see picture).
>>>>>>> fd1f07d40dace3e003e49377d4771de53f8bdeb8
The Tx and Rx lines must cross over, connecting the sensor's Tx line to the The Tx and Rx lines must cross over, connecting the sensor's Tx line to the
computer's Rx line and vice versa. computer's Rx line and vice versa.
-3
View File
@@ -1,3 +0,0 @@
interface=wlan0
dhcp-range=192.168.1.10,192.168.1.100,255.255.255.0,24h
address=/terahz.site/192.168.1.1
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
# edit_ssid.sh - edits hostapd.conf and sets a MAC address-based SSID
ssid=`ip link | awk '/wlan0/ {getline; print $2}' | awk -v FS=':' '{printf("TeraHz_%s%s%s\n", $4, $5, $6)}'`
sed "/ssid=.*/s/ssid=.*/ssid=$ssid/" hostapd.conf > newconf
mv newconf hostapd.conf
-9
View File
@@ -1,9 +0,0 @@
interface=wlan0
hw_mode=g
channel=8
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
ssid=TeraHz
wpa_passphrase=terahertz
-19
View File
@@ -1,19 +0,0 @@
# install.sh - install TeraHz onto a Raspbian or DietPi installation
apt -y update
apt -y full-upgrade
apt install -y python3 python3-pip lighttpd dnsmasq hostapd libatlas-base-dev
pip3 install numpy pandas flask smbus2 pyserial
cp -R hostapd/ /etc
cp -R lighttpd/ /etc
cp dnsmasq.conf /etc
cp rc.local /etc
cp -R ../frontend/* /var/www/html
mkdir -p /usr/local/lib/terahz
cp -R ../backend/* /usr/local/lib/terahz
cd /etc/hostapd
chmod +x edit_ssid.sh
./edit_ssid.sh
systemctl unmask dnsmasq hostapd lighttpd
systemctl enable dnsmasq hostapd lighttpd
-27
View File
@@ -1,27 +0,0 @@
server.modules = (
"mod_access",
"mod_alias",
"mod_compress",
"mod_redirect"
)
server.document-root = "/var/www/html"
server.upload-dirs = ( "/var/cache/lighttpd/uploads" )
server.errorlog = "/var/log/lighttpd/error.log"
server.pid-file = "/var/run/lighttpd.pid"
server.username = "www-data"
server.groupname = "www-data"
server.port = 80
index-file.names = ( "index.php", "index.html", "index.lighttpd.html" )
url.access-deny = ( "~", ".inc" )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
compress.cache-dir = "/var/cache/lighttpd/compress/"
compress.filetype = ( "application/javascript", "text/css", "text/html", "text/plain" )
# default listening port for IPv6 falls back to the IPv4 port
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port
include_shell "/usr/share/lighttpd/create-mime.assign.pl"
include_shell "/usr/share/lighttpd/include-conf-enabled.pl"
-14
View File
@@ -1,14 +0,0 @@
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
/usr/local/lib/terahz/run.sh &
disown
-46
View File
@@ -1,46 +0,0 @@
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
$('#update').click(function () {
updateData();
});
// jQuery event binder
function updateData () {
const url = 'http://' + window.location.hostname + ':5000/data';
$.ajax({ // spawn an AJAX request
url: url,
success: function (data, status) {
console.log(data);
graphSpectralData(data[0], 0);
fillTableData(data);
},
timeout: 2500 // this should be a pretty sane timeout
});
}
function graphSpectralData (obj, dom) {
// graph spectral data in obj into dom
var graphPoints = [];
var graphXTicks = [];
Object.keys(obj).forEach((element, index) => {
graphPoints.push([index, obj[element]]); // build array of points
graphXTicks.push([index, element]); // build array of axis labels
});
// console.log(graphPoints);
const options = {
grid: {color: 'white'},
xaxis: {ticks: graphXTicks}
};
$.plot('#graph', [graphPoints], options);
// flot expects an array of arrays (lines) of 2-element arrays (points)
}
function fillTableData (obj) {
// fill the obj data into HTML tables
Object.keys(obj[0])
.forEach((element) => { $('#' + element).text(obj[0][element]); });
$('#lx').text(obj[1]);
$('#uva').text(obj[2][0]);
$('#uvb').text(obj[2][1]);
$('#uvi').text(obj[2][2]);
}
-162
View File
@@ -1,162 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<link rel="stylesheet" href="lib/bootstrap.min.css">
<link rel="stylesheet" href="stylesheet.css">
<title>TeraHz</title>
</head>
<body>
<div class="container text-center">
<h1><img src="lib/logo-sq.png" height="64px">TeraHz</h1>
</div>
<div class="container">
<button id="update" class="btn btn-primary m-1 float-right">Get data</button>
<p id="debug">
</p>
<h3>Spectrogram</h3>
<div id="graph" style="height:480px;width:720px"></div>
<h3>Spectral readings</h3>
<table class="table table-dark table-sm" id="specter">
<thead class="thead-dark">
<tr>
<th>Band</th>
<th>Wavelength [nm]</th>
<th>Irradiance [μW/cm²]</th>
</tr>
</thead>
<tr>
<td>A</td>
<td>410 nm</td>
<td id="A">---</td>
</tr>
<tr>
<td>B</td>
<td>435 nm</td>
<td id="B">---</td>
</tr>
<tr>
<td>C</td>
<td>460 nm</td>
<td id="C">---</td>
</tr>
<tr>
<td>D</td>
<td>485 nm</td>
<td id="D">---</td>
</tr>
<tr>
<td>E</td>
<td>510 nm</td>
<td id="E">---</td>
</tr>
<tr>
<td>F</td>
<td>535 nm</td>
<td id="F">---</td>
</tr>
<tr>
<td>G</td>
<td>560 nm</td>
<td id="G">---</td>
</tr>
<tr>
<td>H</td>
<td>585 nm</td>
<td id="H">---</td>
</tr>
<tr>
<td>R</td>
<td>610 nm</td>
<td id="R">---</td>
</tr>
<tr>
<td>I</td>
<td>645 nm</td>
<td id="I">---</td>
</tr>
<tr>
<td>S</td>
<td>680 nm</td>
<td id="S">---</td>
</tr>
<tr>
<td>J</td>
<td>705 nm</td>
<td id="J">---</td>
</tr>
<tr>
<td>T</td>
<td>730 nm</td>
<td id="T">---</td>
</tr>
<tr>
<td>U</td>
<td>760 nm</td>
<td id="U">---</td>
</tr>
<tr class="table-secondary">
<td>V</td>
<td>810 nm</td>
<td id="V">---</td>
</tr>
<tr class="table-secondary">
<td>W</td>
<td>860 nm</td>
<td id="W">---</td>
</tr>
<tr class="table-secondary">
<td>K</td>
<td>900 nm</td>
<td id="K">---</td>
</tr>
<tr class="table-secondary">
<td>L</td>
<td>940 nm</td>
<td id="L">---</td>
</tr>
</table>
<br>
<h3>Lux and UV readings</h3>
<table class="table-dark table" id="luxuv">
<thead class="thead-dark">
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Illuminance [lx]</td>
<td id="lx">---</td>
</tr>
<tr>
<td>UVA irradiance [μW/cm²]</td>
<td id="uva">---</td>
</tr>
<tr>
<td>UVB irradiance [μW/cm²]</td>
<td id="uvb">---</td>
</tr>
<tr>
<td>UVA/UVB average [μW/cm²]</td>
<td id="uvi">---</td>
</tr>
</table>
</div>
<script src="lib/bootstrap.bundle.min.js"></script>
<script src="lib/jquery-3.4.1.min.js"></script>
<script src="lib/flot/jquery.flot.js"></script>
<script src="frontend.js"></script>
<script src="lib/flot/jquery.canvaswrapper.js"></script>
<script src="lib/flot/jquery.colorhelpers.js"></script>
<script src="lib/flot/jquery.flot.js"></script>
<script src="lib/flot/jquery.flot.saturated.js"></script>
<script src="lib/flot/jquery.flot.browser.js"></script>
<script src="lib/flot/jquery.flot.drawSeries.js"></script>
<script src="lib/flot/jquery.flot.uiConstants.js"></script>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,5 +5,5 @@ nav:
- Advanced guides: - Advanced guides:
- Build guide: 'build.md' - Build guide: 'build.md'
- Developer's guide: 'dev-guide.md' - Developer's guide: 'dev-guide.md'
- Electrical connections: 'electrical.md' - Electrical guide: 'electrical.md'
- Latest dependencies: 'dependencies.md' - Latest dependencies: 'dependencies.md'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-131
View File
@@ -1,131 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:p="http://www.evolus.vn/Namespace/Pencil" xmlns:pencil="http://www.evolus.vn/Namespace/Pencil" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:rdfa="http://docs.oasis-open.org/opendocument/meta/rdfa#" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:em="http://exslt.org/math" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="1385" height="852" id="exportedSVG" version="1.1" pencil:version="1.2.2" sodipodi:docname="demo"><g inkscape:label="Untitled Page" inkscape:groupmode="layer" id="layer_untitled_page"><g><rect x="0" y="0" width="1385" height="852" fill="none"/><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="316ea25d69264185809ddf96933f1b9d" transform="matrix(1,0,0,1,479,239)"><p:metadata><p:property name="box">379,359</p:property><p:property name="textPadding">18.95,23.933333333333334</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent"/><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="9f2cd04595654a2daa5c78cc57fc3ad1" transform="translate(0.5,0.5)" d="M 0 0 C 126 -1 253 -1 379 0 C 381 120 381 239 379 359 C 253 360 126 360 0 359 C -2 239 -2 120 0 0 z"/>
<text p:name="text" id="22d3cf61288a4e6f8aea91fd9f11d090" transform="translate(190,180)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"/>
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="f2992ac1faff43ef857d97b6097a3377" transform="matrix(1,0,0,1,545,295)"><p:metadata><p:property name="box">248,248</p:property><p:property name="imageData">1600,1600,ref://c549d0e3af32443d81f8de3a764ef050.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
<defs>
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="47f2743a5502445daeea6763b1824d99">
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
</filter>
<g p:name="container" id="c807f25ef7cd4046b815700f4e8dd837">
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="442171111cd644568d10f9aaa8367c03" transform="translate(0,0)" width="248" height="248"/>
<g p:name="imageContainer" id="149b425645654fd99d93a63c39be92cd" transform="scale(0.155,0.155)">
<image x="0" y="0" p:name="image" id="6f19a34b96bf4773969f6d58610f56bd" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/c549d0e3af32443d81f8de3a764ef050.png?token=1549194154000" width="1600" height="1600"/>
</g>
</g>
</defs>
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837" transform="translate(1, 1)" p:filter="url(#47f2743a5502445daeea6763b1824d99)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="f490b3b5a7e340a89dd6f4e992753bb7"/>
<use xlink:href="#c807f25ef7cd4046b815700f4e8dd837"/>
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="d874a4977f474fdeb875d7e6bf5944ed" transform="matrix(1,0,0,1,596,289)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Raspberry Pi 3 B+</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|18px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="de7b105d7eec4d18b5eb971d7883baaa" width="147.640625" height="25.09375"/>
<text xml:space="preserve" p:name="text" id="949a08b7fd4044b794cc5b69b6f83422" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Comic Sans MS&quot;; font-size: 18px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Raspberry Pi 3 B+</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="4e6d928174bb44b2b165b10e439c8069" transform="matrix(1,0,0,1,215,144)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">AS7265X
Spektrometerski senzor za IR in vidno svetlobo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="ba226d4a59e9445cb033b5f6cda2407c" transform="translate(0.5,0.5)" d="M 0 0 C 67 1 133 1 200 0 C 200 50 200 100 200 150 C 133 148 67 148 0 150 C -1 100 -1 50 0 0 z"/>
<text p:name="text" id="2f20051133e249f78ca5e893035376d7" transform="translate(10,64)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="62.4609375" y="0">AS7265X</tspan><tspan x="5.109375" y="16">Spektrometerski senzor za IR</tspan><tspan x="43.7109375" y="32.390625">in vidno svetlobo</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="0788d286529c4856817fe87c11c8c576" transform="matrix(1,0,0,1,113,331)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">VEML6075
Senzor UV svetlobe</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fb336ddc2a0e4285ab16ca92def9a93f" transform="translate(0.5,0.5)" d="M 0 0 C 67 2 133 2 200 0 C 199 50 199 100 200 150 C 133 151 67 151 0 150 C -1 100 -1 50 0 0 z"/>
<text p:name="text" id="5a27b79034a142a182fbba178737c976" transform="translate(10,72)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="59.109375" y="0">VEML6075</tspan><tspan x="34.640625" y="16">Senzor UV svetlobe</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="fdd2989c419f44e49052af8a9433398b" transform="matrix(1,0,0,1,112,499)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">APDS-9301
Merilec osvetljenosti
(lux-meter)</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="fd47ea2f4fea4465b5145099e062a50f" transform="translate(0.5,0.5)" d="M 0 0 C 67 0 133 0 200 0 C 198 50 198 100 200 150 C 133 151 67 151 0 150 C 1 100 1 50 0 0 z"/>
<text p:name="text" id="3a1e6ef59bfe4e32a29f2d83e8a0eda4" transform="translate(10,64)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="57.8125" y="0">APDS-9301</tspan><tspan x="30.8671875" y="16">Merilec osvetljenosti</tspan><tspan x="57.859375" y="32.484375">(lux-meter)</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="6a62643b2a9e40ad92de2807a3b4d499" transform="matrix(1,0,0,1,898,448)"><p:metadata><p:property name="box">200,150</p:property><p:property name="textPadding">10,10</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">2600 mAh Powerbank
Napaja spektrometer</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="0e72ea3ae7374eae96f6d39616ad398b" transform="translate(0.5,0.5)" d="M 0 0 C 67 -1 133 -1 200 0 C 200 50 200 100 200 150 C 133 150 67 150 0 150 C 0 100 0 50 0 0 z"/>
<text p:name="text" id="e73d9b50f41c4e3eb2efffdb7a5f58d9" transform="translate(10,72)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="29.6875" y="0">2600 mAh Powerbank</tspan><tspan x="30.125" y="16">Napaja spektrometer</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="bc03848225fa44a3a7c3339794d1aa4e" transform="matrix(1,0,0,1,479,127)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="92" ns9889235:viax="-54" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="4e6d928174bb44b2b165b10e439c8069">-64,92</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="102" ns9889235:viax="189.5" ns9889235:connectedOutletId="top-center" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d">189.5,112</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="590e362dc0a941558c8729fc4369cced" d="M -58 98 L -64 92 L -58 86 M -64 92 C -4 92 190 52 189.5 112 M 195 106 L 189.5 112 L 184 106"/>
</defs>
<use xlink:href="#590e362dc0a941558c8729fc4369cced" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#590e362dc0a941558c8729fc4369cced" p:name="outArrow1" id="9c47d44f84ff4e8c9c60f2427e5649e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="2027c38e59a7415ea356985839c3cb6a" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#590e362dc0a941558c8729fc4369cced" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="7e3a712874e84610942b4fa041b7a43b" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Sketchy.GUI:box" id="3f86fad121f34239a0d9a0381b8e5293" transform="matrix(1,0,0,1,339,331)"><p:metadata><p:property name="box">76,316</p:property><p:property name="textPadding">3.8,21.066666666666663</p:property><p:property name="fillColor">#FFFFFFFF</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">1|</p:property><p:property name="textContent">I2C
Vodilo</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<path style="stroke-linejoin: round; fill: rgb(255, 255, 255); fill-opacity: 1; stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 1;" p:name="line1" id="38738b385aac4e4f882b7957ae3baf95" transform="translate(0.5,0.5)" d="M 0 0 C 25 -1 51 -1 76 0 C 76 105 76 211 76 316 C 51 315 25 315 0 316 C -1 211 -1 105 0 0 z"/>
<text p:name="text" id="d829667556ac42128e839eeb4432aec3" transform="translate(4,155)" style="font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none; fill: rgb(0, 0, 0); fill-opacity: 1;"><tspan x="23.645312500000003" y="0">I2C</tspan><tspan x="17.145312500000003" y="16">Vodilo</tspan></text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="e6e45b080a4640e2af58d063d5abe1ef" transform="matrix(1,0,0,1,562,696)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="0788d286529c4856817fe87c11c8c576" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-239" ns9889235:viay="-290">-249,-290</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="-233" ns9889235:viay="-207">-223,-207</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="564ef2543bfd4d4f8bf59146b4632c7c" d="M -243 -284 L -249 -290 L -243 -296 M -249 -290 C -206 -290 -266 -207 -223 -207 M -229 -213 L -223 -207 L -229 -201"/>
</defs>
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" p:name="outArrow1" id="9fd757e4b8d042a09378b6031cfe99e2" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="c73cdd327696416eab5fa6971dba53ef" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#564ef2543bfd4d4f8bf59146b4632c7c" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="221213c9b3fc487facaa478640fecf0f" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="540bab7bb3f3442f8429144581700e47" transform="matrix(1,0,0,1,324,705)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:viay="-131" ns9889235:viax="-2" ns9889235:connectedOutletId="middle-right" ns9889235:connectedShapeId="fdd2989c419f44e49052af8a9433398b">-12,-131</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5" ns9889235:viay="-216">15,-216</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b08f88ad83b84d5fad883f5b2d7152d9" d="M -6 -125 L -12 -131 L -6 -137 M -12 -131 C 33 -131 -30 -216 15 -216 M 9 -222 L 15 -216 L 9 -210"/>
</defs>
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" p:name="outArrow1" id="81869e15f13e41609130f9b556eef9d3" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="2fa0348639be49eda6fa12a65623e9b6" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#b08f88ad83b84d5fad883f5b2d7152d9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="c544ec23fdcd4df998935ca8bb683723" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="0bc4ad3e83614786a0f2348a47ec40f2" transform="matrix(1,0,0,1,463,627)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="3f86fad121f34239a0d9a0381b8e5293" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-38" ns9889235:viay="-138">-48,-138</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-left" ns9889235:viax="5.999999999999972" ns9889235:viay="-208.5">16,-208</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="6c567c02fe2642e19f2b63f417f770a9" d="M -42 -132 L -48 -138 L -42 -144 M -48 -138 C -1 -138 -31 -210 16 -208 M 11 -214 L 16 -208 L 10 -203"/>
</defs>
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#6c567c02fe2642e19f2b63f417f770a9" p:name="outArrow1" id="cad90b7ac097400b8a9febcd1059b193" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="7256ea09df6f4b7ebf33202f1ced446d" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#6c567c02fe2642e19f2b63f417f770a9" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="a3e251b119324498b6079771d87cf26b" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:RichTextBoxV2" id="f9704a7a815d4cdba445660a10c7ddc8" transform="matrix(1,0,0,1,479,601)"><p:metadata><p:property name="width">200,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="textContent">&lt;span xmlns="http://www.w3.org/1999/xhtml" style="font-family: &amp;quot;Comic Sans MS&amp;quot;;"&gt;Računalnik na tiskanem vezju (SBC = single board computer)&lt;br xmlns="http://www.w3.org/1999/xhtml" /&gt;Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter&lt;/span&gt;</p:property><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="customStyle">
</p:property></p:metadata>
<foreignObject x="0" y="0" width="585" height="38" p:name="htmlObject" id="54de4aab4c144b8ead414b2e22841369" style="color: rgb(0, 0, 0); font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<div xmlns="http://www.w3.org/1999/xhtml" p:name="textDiv" id="0cbc982da2c54d4aa9a65d4f6e9727c6" style="display: inline-block; white-space: nowrap; text-decoration: none;"><div><span style="font-family: &quot;Comic Sans MS&quot;;">Računalnik na tiskanem vezju (SBC = single board computer)<br />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></div></div>
</foreignObject>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="c884a2093fdc477bb3a74e719a1c7c77" transform="matrix(1,0,0,1,951,527)"><p:metadata><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="startPin" ns9889235:connectedShapeId="316ea25d69264185809ddf96933f1b9d" ns9889235:connectedOutletId="middle-right" ns9889235:viax="-83" ns9889235:viay="-108.5">-93,-108</p:property><p:property xmlns:ns9889235="http://www.evolus.vn/Namespace/Pencil" name="endPin" ns9889235:viay="-4" ns9889235:viax="-63.000000000000014" ns9889235:connectedOutletId="middle-left" ns9889235:connectedShapeId="6a62643b2a9e40ad92de2807a3b4d499">-53,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">2|</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="384134701047435fac995fc6eaee5932" d="M -87 -102 L -93 -108 L -87 -114 M -93 -108 C -37 -108 -109 -4 -53 -4 M -59 -10 L -53 -4 L -59 2"/>
</defs>
<use xlink:href="#384134701047435fac995fc6eaee5932" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#384134701047435fac995fc6eaee5932" p:name="outArrow1" id="14d921eb06d44f7d9acd062d2b01551a" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 2;"/>
<text p:name="text" id="5485420a3edb40669c089909c03dc1f8" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#384134701047435fac995fc6eaee5932" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="428895865f0145dca4a5ff61843e80ef" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:Bitmap" id="b6ed13921fdd40f9a3eb0e1e3d0ecbe6" transform="matrix(1,0,0,1,1103,146)"><p:metadata><p:property name="box">195,276</p:property><p:property name="imageData">318,450,ref://7214557d80314d2ea0269f3a449d0000.png</p:property><p:property name="withBlur">false</p:property><p:property name="fillColor">#FFFFFF00</p:property><p:property name="strokeColor">#000000FF</p:property><p:property name="strokeStyle">0|</p:property></p:metadata>
<defs>
<filter height="1.2558399" y="-0.12792" width="1.06396" x="-0.03198" p:name="imageShading" id="45b9efdc21654623a49f9d3d92738b31">
<feGaussianBlur stdDeviation="1.3325" in="SourceAlpha"/>
</filter>
<g p:name="container" id="dc93c38161c8442faea0e63e5cbf5843">
<rect style="fill: rgb(255, 255, 255); stroke: rgb(0, 0, 0); stroke-opacity: 1; stroke-width: 0; fill-opacity: 0;" p:name="bgRect" id="62e29bf3081e4449ae9fc80c6c967b71" transform="translate(0,0)" width="195" height="276"/>
<g p:name="imageContainer" id="6f7191c994c644f183d02acf013570d0" transform="scale(0.6132075471698113,0.6133333333333333)">
<image x="0" y="0" p:name="image" id="a6b907023988404f8a2a4c4daeb1b81e" xlink:href="file:///tmp/tmp-27399CzGZUH8OkSfx/refs/7214557d80314d2ea0269f3a449d0000.png?token=1549194986000" width="318" height="450"/>
</g>
</g>
</defs>
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843" transform="translate(1, 1)" p:filter="url(#45b9efdc21654623a49f9d3d92738b31)" style="opacity: 0.6; visibility: hidden; display: none;" p:name="bgCopy" id="02792f50e6104ccc99ad1f691c8d3a5e"/>
<use xlink:href="#dc93c38161c8442faea0e63e5cbf5843"/>
</g><g p:type="Shape" p:def="Evolus.Common:arrow" id="7b94aab3e1cd494696d3b5cf838dfde6" transform="matrix(1,0,0,1,858,286)"><p:metadata><p:property name="startPin">0,0</p:property><p:property name="endPin">282,-4</p:property><p:property name="withStartArrow">true</p:property><p:property name="withEndArrow">true</p:property><p:property name="mode">curvy</p:property><p:property name="detached">false</p:property><p:property name="strokeColor">#1B3280FF</p:property><p:property name="strokeStyle">4|3,3</p:property><p:property name="textContent"/><p:property name="textFont">"Liberation Sans",Arial,sans-serif|normal|normal|13px|none</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textAlign">1,1</p:property></p:metadata>
<defs>
<path style="stroke-linejoin: round; fill: none;" p:name="path" id="b8c0a2b9697f46288c408540e8af57fa" d="M 11 11 L 0 0 L 11 -11 M 0 0 C 60 0 222 -3 282 -4 M 271 -15 L 282 -4 L 271 7"/>
</defs>
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" stroke-width="10" stroke-opacity="0" stroke="#FF0000"/>
<use xlink:href="#b8c0a2b9697f46288c408540e8af57fa" p:name="outArrow1" id="97846f435e74416e996116a08ef664e4" style="stroke: rgb(27, 50, 128); stroke-opacity: 1; stroke-width: 4; stroke-dasharray: 3, 3;"/>
<text p:name="text" id="987b10b79cfa4658917b6bb36a6005fb" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Liberation Sans&quot;, Arial, sans-serif; font-size: 13px; font-weight: normal; font-style: normal; text-decoration: none;">
<textPath xlink:href="#b8c0a2b9697f46288c408540e8af57fa" startOffset="50%" text-anchor="middle" alignment-baseline="middle">
<tspan dy="-4.333333333333333" p:name="textSpan" id="667515745b224bb79dbd2e134284f6d2" dx="0"/>
</textPath>
</text>
</g><g p:type="Shape" p:def="Evolus.Common:PlainTextV2" p:sc="Label" id="bb91f7fb7faf445a834363edc2cb287a" transform="matrix(1,0,0,1,1034,157)"><p:metadata><p:property name="disabled">false</p:property><p:property name="width">100,0</p:property><p:property name="fixedWidth">false</p:property><p:property name="label">Mobilni telefon preko WiFi povezave upravlja spektrometer</p:property><p:property name="textColor">#000000FF</p:property><p:property name="textFont">'Comic Sans MS'|normal|normal|12px|none</p:property><p:property name="textAlign">0,0</p:property></p:metadata>
<rect x="0" y="0" style="fill: none; stroke: none; visibility: hidden; display: none;" p:name="bgRect" id="c2e343a9ad3246cdb047e5e01ee1fb42" width="332.390625" height="16.484375"/>
<text xml:space="preserve" p:name="text" id="357a8a3e3c4044bb8518698b7d607b60" style="fill: rgb(0, 0, 0); fill-opacity: 1; font-family: &quot;Comic Sans MS&quot;; font-size: 12px; font-weight: normal; font-style: normal; text-decoration: none;"><tspan x="0" y="0">Mobilni telefon preko WiFi povezave upravlja spektrometer</tspan></text>
</g></g></g></svg>

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
# sysprep - How to prepare a Raspberry Pi 3 B+ to run TeraHz
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

+22
View File
@@ -0,0 +1,22 @@
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="terahz", # Replace with your own username
version="1.0.0",
author="Kristjan Komloši",
author_email="kristjan.komlosi@gmail.com",
description="Low cost spectrometry webapp",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/cls-02/TeraHz",
packages=setuptools.find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)
+46
View File
@@ -0,0 +1,46 @@
# app.py - main backend program
'''Main TeraHz backend program'''
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
from flask import Flask, jsonify, render_template
from . import terahz
def start_flaskapp():
'''Initialize global variables'''
global app, s, u, l
app = Flask(__name__)
s = terahz.Spectrometer(path='/dev/serial0')
u = terahz.UVSensor()
l = terahz.LxMeter()
@app.route('/data')
def sendData():
'''Responder function for /data route'''
response = jsonify([s.getData(), l.getData(), u.getABI()])
response.headers.add('Access-Control-Allow-Origin', '*')
return response
@app.route('/')
def renderTable():
''''Main page renderer'''
wavelengthDict = {
'A': '410 nm',
'B': '435 nm',
'C': '460 nm',
'D': '485 nm',
'E': '510 nm',
'F': '535 nm',
'G': '560 nm',
'H': '585 nm',
'R': '610 nm',
'I': '645 nm',
'S': '680 nm',
'J': '705 nm',
'T': '730 nm',
'U': '760 nm',
'V': '810 nm',
'W': '860 nm',
'K': '900 nm',
'L': '940 nm'}
data = [s.getData(), l.getData(), u.getABI()]
return render_template('index.html', data=data, wavelengths=wavelengthDict)
+42
View File
@@ -0,0 +1,42 @@
// All code in this file is licensed under the ISC license, provided in LICENSE.txt
$('#update').click(updateData);
// jQuery event binder
function updateData () {
const url = '/data';
$.get(url, (data, status) => {
if (status === '200') {
graphSpectralData(data[0], 0);
fillTableData(data);
} else alert('Data request failed, please refresh page.');
});
}
function graphSpectralData (obj, dom) {
// graph spectral data in obj into dom
var graphPoints = [];
var graphXTicks = [];
const spectrum = 'ABCDEFGHRISJTUVWKL';
for (var i = 0; i < spectrum.length; i++) {
graphPoints.push([i, obj[spectrum[i]]]);
graphXTicks.push([i, spectrum[i]]);
}
const options = {
grid: { color: 'white' },
xaxis: { ticks: graphXTicks }
};
$.plot('#graph', [graphPoints], options);
// flot expects an array of arrays (lines) of 2-element arrays (points)
}
function fillTableData (obj) {
// fill the obj data into HTML tables
Object.keys(obj[0]).forEach((element) =>
{ $('#' + element + '_value').text(obj[0][element]); });
$('#lx').text(obj[1]);
$('#uva').text(obj[2][0]);
$('#uvb').text(obj[2][1]);
$('#uvi').text(obj[2][2]);
}

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

+98
View File
@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<link rel="stylesheet" href="static/bootstrap.min.css">
<link rel="stylesheet" href="static/stylesheet.css">
<title>TeraHz</title>
<!-- script imports -->
<script src="static/bootstrap.bundle.min.js"></script>
<script src="static/jquery-3.4.1.min.js"></script>
<script src="static/flot/jquery.flot.js"></script>
<script src="static/frontend.js"></script>
<script src="static/flot/jquery.canvaswrapper.js"></script>
<script src="static/flot/jquery.colorhelpers.js"></script>
<script src="static/flot/jquery.flot.js"></script>
<script src="static/flot/jquery.flot.saturated.js"></script>
<script src="static/flot/jquery.flot.browser.js"></script>
<script src="static/flot/jquery.flot.drawSeries.js"></script>
<script src="static/flot/jquery.flot.uiConstants.js"></script>
</head>
<body>
<div class="container text-center">
<h1><img src="static/logo-sq.png" height="64px">TeraHz</h1>
</div>
<div class="container">
<button id="update" onclick="updateData()" class="btn btn-primary">Get data</button>
<button id="download" class="btn btn-primary">Download</button>
<button id="debug" class="btn btn-danger">DEBUG</button>
</div>
<div class="container">
<div class="row">
<div class="col-sm">
<h4>Spectrogram</h4>
<div id="graph" style="height:480px;width:720px"></div>
</div>
<div class="col-sm">
<h4>Visible+IR spectrum</h4>
<table class="table table-dark table-sm" id="specter">
<thead class="thead-dark">
<tr>
<th>Band</th>
<th>Wavelength [nm]</th>
<th>Irradiance [μW/cm²]</th>
</tr>
</thead>
{% for band, irr in data[0] %}
<tr id="{{band}}">
<td>
{{band}}
</td>
<td>
{{wld[band]}}
</td>
<td id="{{band}}_value">
{{irr}}
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="col-sm">
<h4>UV+Illuminance</h4>
<table class="table-dark table table-sm" id="luxuv">
<thead class="thead-dark">
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Illuminance [lx]</td>
<td id="lx">{{data[1]}}</td>
</tr>
<tr>
<td>UVA irradiance [μW/cm²]</td>
<td id="uva">{{data[2][0]}}</td>
</tr>
<tr>
<td>UVB irradiance [μW/cm²]</td>
<td id="uvb">{{data[2][1]}}</td>
</tr>
<tr>
<td>UVA/UVB average [μW/cm²]</td>
<td id="uvi">{{data[2][2]}}</td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>
+33 -53
View File
@@ -1,6 +1,6 @@
# sensors.py - a module for interfacing to the sensors # terahz.py - a module for interfacing to the sensors
'''Module for interfacing with TeraHz sensors''' '''Module for interfacing with TeraHz sensors'''
# Copyright 2019 Kristjan Komloši # Copyright 2019, 2020 Kristjan Komloši
# All code in this file is licensed under the ISC license, provided in LICENSE.txt # All code in this file is licensed under the ISC license, provided in LICENSE.txt
import serial as ser import serial as ser
import pandas as pd import pandas as pd
@@ -8,6 +8,7 @@ import smbus2
class Spectrometer: class Spectrometer:
'''Class representing the AS7265X specrometer''' '''Class representing the AS7265X specrometer'''
def initializeSensor(self): def initializeSensor(self):
'''confirm the sensor is responding and proceed\ '''confirm the sensor is responding and proceed\
with spectrometer initialization''' with spectrometer initialization'''
@@ -23,8 +24,7 @@ class Spectrometer:
if rstring == 'ERROR': if rstring == 'ERROR':
raise Exception # sensor is in error state raise Exception # sensor is in error state
except: except:
raise Exception( raise Exception('No AT command response')
'An exception ocurred when performing spectrometer handshake')
def setParameters(self, parameters): def setParameters(self, parameters):
'''applies the parameters like LED light and gain to the spectrometer''' '''applies the parameters like LED light and gain to the spectrometer'''
@@ -53,8 +53,7 @@ class Spectrometer:
self.serialObject.write('ATLED3={}\n'.format(led).encode()) self.serialObject.write('ATLED3={}\n'.format(led).encode())
self.serialObject.readline() self.serialObject.readline()
except: except:
raise Exception( raise Exception('Error setting spectrometer parameters')
'An exception occured during spectrometer initialization')
def getData(self): def getData(self):
'''Returns spectral data in a pandas DataFrame.''' '''Returns spectral data in a pandas DataFrame.'''
@@ -62,11 +61,10 @@ class Spectrometer:
self.serialObject.write(b'ATCDATA\n') self.serialObject.write(b'ATCDATA\n')
rawresp = self.serialObject.readline().decode() rawresp = self.serialObject.readline().decode()
except: except:
raise Exception( raise Exception('Error polling spectrometer data')
'An exception occurred when polling for spectrometer data')
else: else:
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] responseorder = list('RSTUVWGHIJKLABCDEF')
realorder = [i for i in 'ABCDEFGHRISJTUVWKL'] realorder = list('ABCDEFGHRISJTUVWKL')
response = pd.Series( response = pd.Series(
[float(i) / 35.0 for i in rawresp[:-3].split(',')], index=responseorder) [float(i) / 35.0 for i in rawresp[:-3].split(',')], index=responseorder)
return pd.DataFrame(response, index=realorder, columns=['uW/cm^2']).to_dict()['uW/cm^2'] return pd.DataFrame(response, index=realorder, columns=['uW/cm^2']).to_dict()['uW/cm^2']
@@ -78,14 +76,14 @@ class Spectrometer:
try: try:
self.serialObject = ser.Serial(path, baudrate, timeout=tout) self.serialObject = ser.Serial(path, baudrate, timeout=tout)
except: except:
raise Exception( raise Exception('Error opening serial port: {}'.format(path))
'An exception occured when opening the serial port at {}'.format(path))
else: else:
self.initializeSensor() self.initializeSensor()
class LxMeter: class LxMeter:
'''Class representing the APDS-9301 digital photometer.''' '''Class representing the APDS-9301 digital photometer.'''
def __init__(self, busNumber=1, addr=0x39): def __init__(self, busNumber=1, addr=0x39):
self.addr = addr self.addr = addr
try: try:
@@ -93,14 +91,14 @@ class LxMeter:
self.bus = smbus2.SMBus(busNumber) self.bus = smbus2.SMBus(busNumber)
except: except:
raise Exception( raise Exception(
'An exception occured opening the SMBus {}'.format(self.bus)) 'Error opening SMBus {}'.format(self.bus))
try: try:
self.bus.write_byte_data( self.bus.write_byte_data(
self.addr, 0xa0, 0x03) # enable the sensor self.addr, 0xa0, 0x03) # enable the sensor
self.setGain(16) self.setGain(16)
except: except:
raise Exception('An exception occured when enabling lux meter') raise Exception('Error enabling lux meter')
def setGain(self, gain): def setGain(self, gain):
'''Set the sensor gain. Either 1 or 16.''' '''Set the sensor gain. Either 1 or 16.'''
@@ -109,15 +107,13 @@ class LxMeter:
temp = self.bus.read_byte_data(self.addr, 0xa1) temp = self.bus.read_byte_data(self.addr, 0xa1)
self.bus.write_byte_data(self.addr, 0xa1, 0xef & temp) self.bus.write_byte_data(self.addr, 0xa1, 0xef & temp)
except: except:
raise Exception( raise Exception('Error setting lux meter gain')
'An exception occured when setting lux meter gain')
if gain == 16: if gain == 16:
try: try:
temp = self.bus.read_byte_data(self.addr, 0xa1) temp = self.bus.read_byte_data(self.addr, 0xa1)
self.bus.write_byte_data(self.addr, 0xa1, 0x10 | temp) self.bus.write_byte_data(self.addr, 0xa1, 0x10 | temp)
except: except:
raise Exception( raise Exception('Error setting lux meter gain')
'An exception occured when setting lux meter gain')
else: else:
raise Exception('Invalid gain') raise Exception('Invalid gain')
@@ -160,7 +156,7 @@ class LxMeter:
except: except:
raise Exception('An exception occured fetching lux channels') raise Exception('An exception occured fetching lux channels')
# scary computations ahead! refer to the apds-9301 datasheet! # Refer to APDS-9301 datasheet!
if chB / chA <= 0.5 and chB / chA > 0: if chB / chA <= 0.5 and chB / chA > 0:
lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4 lux = 0.0304 * chA - 0.062 * chA * (chB / chA)**1.4
elif chB / chA <= 0.61 and chB / chA > 0.5: elif chB / chA <= 0.61 and chB / chA > 0.5:
@@ -176,6 +172,7 @@ class LxMeter:
class UVSensor: class UVSensor:
'''Class representing VEML6075 UVA/B meter''' '''Class representing VEML6075 UVA/B meter'''
def __init__(self, bus=1, addr=0x10): def __init__(self, bus=1, addr=0x10):
self.addr = addr self.addr = addr
try: try:
@@ -187,9 +184,16 @@ class UVSensor:
try: try:
# enable the sensor and set the integration time # enable the sensor and set the integration time
self.bus.write_byte_data(self.addr, 0x00, 0b00010000) self.bus.write_byte_data(self.addr, 0x00, 0b00010000)
# trigger a measurement to prevent bus errors at first read
self.bus.write_byte_data(self.addr, 0x00, 0b00010010) # UV_AF=1
self.bus.write_byte_data(self.addr, 0x00, 0b00010110) # UV_TRIG=1
self.bus.write_byte_data(
self.addr, 0x00, 0b00010000) # normal mode
except: except:
raise Exception( raise Exception(
'An exception occured when initalizing the UV Sensor') 'Error initalizing the UV Sensor')
def getABI(self): def getABI(self):
'''Calculates the UVA and UVB irradiances, '''Calculates the UVA and UVB irradiances,
@@ -202,40 +206,16 @@ class UVSensor:
c1 = self.bus.read_word_data(self.addr, 0x0a) c1 = self.bus.read_word_data(self.addr, 0x0a)
c2 = self.bus.read_word_data(self.addr, 0x0b) c2 = self.bus.read_word_data(self.addr, 0x0b)
except: except:
raise Exception('An exception occured when fetching raw UV data') raise Exception('Error fetching raw UV data')
# scary computations ahead! refer to Vishay app note 84339 and Sparkfun # Refer to Vishay app note 84339 and Sparkfun VEML6075 documentation.
# VEML6075 documentation. # The computed values may be negative if UV is vastly weaker than
# visible and IR light. This is not a bug!
# compensate for visible and IR noise
aCorr = aRaw - 2.22 * c1 - 1.33 * c2
bCorr = bRaw - 2.95 * c1 - 1.74 * c2
# convert values into irradiances
a = aCorr * 1.06
b = bCorr * 0.48
# zero out negative results (readings with no uv)
if a < 0:
a = 0
if b < 0:
b = 0
# last, calculate the UV index
i = (a + b) / 2
a = (aRaw - 2.22 * c1 - 1.33 * c2) * 1.06
b = (bRaw - 2.95 * c1 - 1.74 * c2) * 0.48
i = (a + b) / 2 # calculate UV index
return [a, b, i] return [a, b, i]
def getA(self):
'''Returns UVA value. A getABI() wrapper.'''
return self.getABI()[0]
def getB(self):
'''Returns UVB value. A getABI() wrapper.'''
return self.getABI()[1]
def getI(self):
'''Returns UV index. A getABI() wrapper.'''
return self.getABI()[2]
def on(self): def on(self):
'''Turns the UV sensor on after shutdown.''' '''Turns the UV sensor on after shutdown.'''
try: try:
@@ -244,7 +224,7 @@ class UVSensor:
self.bus.write_byte_data(self.addr, 0x00, 0x10) self.bus.write_byte_data(self.addr, 0x00, 0x10)
except: except:
raise Exception( raise Exception(
'An exception occured when turning the UV sensor on') 'Error turning the UV sensor on')
def off(self): def off(self):
'''Shuts the UV sensor down.''' '''Shuts the UV sensor down.'''
@@ -254,4 +234,4 @@ class UVSensor:
self.bus.write_byte_data(self.addr, 0x00, 0x11) self.bus.write_byte_data(self.addr, 0x00, 0x11)
except: except:
raise Exception( raise Exception(
'An exception occured when shutting the UV sensor down') 'Error shutting the UV sensor down')
-45
View File
@@ -1,45 +0,0 @@
# getcdata.py - fetch the calibrated data from the AS7265x module
# All code in this file is licensed under the ISC license, provided in LICENSE.txt
import serial as ser
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import time
#global variables
uartpath = '/dev/ttyUSB0'
uartbaud = 115200
uarttout = 5
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
print('getcdata')
print('This utility is part of the TeraHz project')
wavelens = pd.Series(realorder)
plt.ion()
win = plt.figure()
spectrum=win.add_subplot(111)
with ser.Serial(uartpath, uartbaud, timeout=uarttout) as sensor:
while True:
sensor.write(b'ATCDATA\n')
rawresp = sensor.readline().decode()
# parses, calculates and saves the data
response = pd.Series([float(i)/35.0 for i in rawresp[:-3].split(',')], index=responseorder)
data = pd.DataFrame(response, index=realorder, columns = ['uW/cm^2']) # puts data into a DataFrame
data.insert(0, 'wavelenght', wl) #inserts a legend
print(data)
spectrum.cla()
spectrum.plot(data['wavelenght'], data['uW/cm^2'])
spectrum.set_xlabel('Valovna dolžina')
spectrum.set_ylabel('uW/cm2')
win.canvas.draw()
time.sleep(0.1)
-12
View File
@@ -1,12 +0,0 @@
import smbus2
bus = smbus2.SMBus(1)
result = bus.read_byte_data(0x39, 0x8a)
print('LUX Meter ID = {}'.format(result))
result = bus.read_word_data(0x10, 0x0c)
print('UV sensor ID = {}'.format(result))
result = bus.read_word_data(0x39, 0xec)
print('LUX chan 0 = {}'.format(result))
-22
View File
@@ -1,22 +0,0 @@
from serial import Serial
import tkinter as tk
import pandas as pd
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure
uartpath = '/dev/ttyUSB0'
uartbaud = 115200
uarttout = 5
wl = [410, 435, 460, 485, 510, 535, 560, 585, 610, 645, 680, 705, 730, 760, 810, 860, 900, 940]
responseorder = [i for i in 'RSTUVWGHIJKLABCDEF'] # works, do NOT touch!
realorder = [i for i in 'ABCDEFGHRISJTUVWKL']
root = tk.Tk()
root.wm_title('TeraHz Demo')
fig = Figure(figsize=(5, 4), dpi=100)
plot = fig.add_subplot(111)
canvas = FigureCanvasTkAgg(fig, master=root)
Binary file not shown.
-76
View File
@@ -1,76 +0,0 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
if [ ! "$1" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/root/projekti/TeraHz/utils/venv"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
if [ "x(venv) " != x ] ; then
PS1="(venv) ${PS1:-}"
else
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
else
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
fi
fi
export PS1
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r
fi
-37
View File
@@ -1,37 +0,0 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
if ("venv" != "") then
set env_name = "venv"
else
if (`basename "VIRTUAL_ENV"` == "__") then
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
else
set env_name = `basename "$VIRTUAL_ENV"`
endif
endif
set prompt = "[$env_name] $prompt"
unset env_name
endif
alias pydoc python -m pydoc
rehash
-75
View File
@@ -1,75 +0,0 @@
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
# you cannot run it directly
function deactivate -d "Exit virtualenv and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
functions -e fish_prompt
set -e _OLD_FISH_PROMPT_OVERRIDE
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
set -e VIRTUAL_ENV
if test "$argv[1]" != "nondestructive"
# Self destruct!
functions -e deactivate
end
end
# unset irrelevant variables
deactivate nondestructive
set -gx VIRTUAL_ENV "/root/projekti/TeraHz/utils/venv"
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
# unset PYTHONHOME if set
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# save the current fish_prompt function as the function _old_fish_prompt
functions -c fish_prompt _old_fish_prompt
# with the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command
set -l old_status $status
# Prompt override?
if test -n "(venv) "
printf "%s%s" "(venv) " (set_color normal)
else
# ...Otherwise, prepend env
set -l _checkbase (basename "$VIRTUAL_ENV")
if test $_checkbase = "__"
# special case for Aspen magic directories
# see http://www.zetadev.com/software/aspen/
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
else
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
end
end
# Restore the return status of the previous command.
echo "exit $old_status" | .
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
end
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from setuptools.command.easy_install import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from numpy.f2py.f2py2e import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-976
View File
@@ -1,976 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
#
# Very simple serial terminal
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
import codecs
import os
import sys
import threading
import serial
from serial.tools.list_ports import comports
from serial.tools import hexlify_codec
# pylint: disable=wrong-import-order,wrong-import-position
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
try:
raw_input
except NameError:
# pylint: disable=redefined-builtin,invalid-name
raw_input = input # in python3 it's "raw"
unichr = chr
def key_description(character):
"""generate a readable description for a key"""
ascii_code = ord(character)
if ascii_code < 32:
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
else:
return repr(character)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class ConsoleBase(object):
"""OS abstraction for console (input/output codec, no echo)"""
def __init__(self):
if sys.version_info >= (3, 0):
self.byte_output = sys.stdout.buffer
else:
self.byte_output = sys.stdout
self.output = sys.stdout
def setup(self):
"""Set console to read single characters, no echo"""
def cleanup(self):
"""Restore default console settings"""
def getkey(self):
"""Read a single key from the console"""
return None
def write_bytes(self, byte_string):
"""Write bytes (already encoded)"""
self.byte_output.write(byte_string)
self.byte_output.flush()
def write(self, text):
"""Write string"""
self.output.write(text)
self.output.flush()
def cancel(self):
"""Cancel getkey operation"""
# - - - - - - - - - - - - - - - - - - - - - - - -
# context manager:
# switch terminal temporary to normal mode (e.g. to get user input)
def __enter__(self):
self.cleanup()
return self
def __exit__(self, *args, **kwargs):
self.setup()
if os.name == 'nt': # noqa
import msvcrt
import ctypes
class Out(object):
"""file-like wrapper that uses os.write"""
def __init__(self, fd):
self.fd = fd
def flush(self):
pass
def write(self, s):
os.write(self.fd, s)
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
ctypes.windll.kernel32.SetConsoleCP(65001)
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
# the change of the code page is not propagated to Python, manually fix it
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
sys.stdout = self.output
self.output.encoding = 'UTF-8' # needed for input
def __del__(self):
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
def getkey(self):
while True:
z = msvcrt.getwch()
if z == unichr(13):
return unichr(10)
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
msvcrt.getwch()
else:
return z
def cancel(self):
# CancelIo, CancelSynchronousIo do not seem to work when using
# getwch, so instead, send a key to the window with the console
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
elif os.name == 'posix':
import atexit
import termios
import fcntl
class Console(ConsoleBase):
def __init__(self):
super(Console, self).__init__()
self.fd = sys.stdin.fileno()
self.old = termios.tcgetattr(self.fd)
atexit.register(self.cleanup)
if sys.version_info < (3, 0):
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
else:
self.enc_stdin = sys.stdin
def setup(self):
new = termios.tcgetattr(self.fd)
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
new[6][termios.VMIN] = 1
new[6][termios.VTIME] = 0
termios.tcsetattr(self.fd, termios.TCSANOW, new)
def getkey(self):
c = self.enc_stdin.read(1)
if c == unichr(0x7f):
c = unichr(8) # map the BS key (which yields DEL) to backspace
return c
def cancel(self):
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
def cleanup(self):
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
else:
raise NotImplementedError(
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
class Transform(object):
"""do-nothing: forward all data unchanged"""
def rx(self, text):
"""text received from serial port"""
return text
def tx(self, text):
"""text to be sent to serial port"""
return text
def echo(self, text):
"""text to be sent but displayed on console"""
return text
class CRLF(Transform):
"""ENTER sends CR+LF"""
def tx(self, text):
return text.replace('\n', '\r\n')
class CR(Transform):
"""ENTER sends CR"""
def rx(self, text):
return text.replace('\r', '\n')
def tx(self, text):
return text.replace('\n', '\r')
class LF(Transform):
"""ENTER sends LF"""
class NoTerminal(Transform):
"""remove typical terminal control codes from input"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
REPLACEMENT_MAP.update(
{
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
def rx(self, text):
return text.translate(self.REPLACEMENT_MAP)
echo = rx
class NoControls(NoTerminal):
"""Remove all control codes, incl. CR+LF"""
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
REPLACEMENT_MAP.update(
{
0x20: 0x2423, # visual space
0x7F: 0x2421, # DEL
0x9B: 0x2425, # CSI
})
class Printable(Transform):
"""Show decimal code for all non-ASCII characters and replace most control codes"""
def rx(self, text):
r = []
for c in text:
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
r.append(c)
elif c < ' ':
r.append(unichr(0x2400 + ord(c)))
else:
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
r.append(' ')
return ''.join(r)
echo = rx
class Colorize(Transform):
"""Apply different colors for received and echo"""
def __init__(self):
# XXX make it configurable, use colorama?
self.input_color = '\x1b[37m'
self.echo_color = '\x1b[31m'
def rx(self, text):
return self.input_color + text
def echo(self, text):
return self.echo_color + text
class DebugIO(Transform):
"""Print what is sent and received"""
def rx(self, text):
sys.stderr.write(' [RX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
def tx(self, text):
sys.stderr.write(' [TX:{}] '.format(repr(text)))
sys.stderr.flush()
return text
# other ideas:
# - add date/time for each newline
# - insert newline after: a) timeout b) packet end character
EOL_TRANSFORMATIONS = {
'crlf': CRLF,
'cr': CR,
'lf': LF,
}
TRANSFORMATIONS = {
'direct': Transform, # no transformation
'default': NoTerminal,
'nocontrol': NoControls,
'printable': Printable,
'colorize': Colorize,
'debug': DebugIO,
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def ask_for_port():
"""\
Show a list of ports and ask the user for a choice. To make selection
easier on systems with long device names, also allow the input of an
index.
"""
sys.stderr.write('\n--- Available ports:\n')
ports = []
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
ports.append(port)
while True:
port = raw_input('--- Enter port index or full name: ')
try:
index = int(port) - 1
if not 0 <= index < len(ports):
sys.stderr.write('--- Invalid index!\n')
continue
except ValueError:
pass
else:
port = ports[index]
return port
class Miniterm(object):
"""\
Terminal application. Copy data from serial port to console and vice versa.
Handle special keys from the console to show menu etc.
"""
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
self.console = Console()
self.serial = serial_instance
self.echo = echo
self.raw = False
self.input_encoding = 'UTF-8'
self.output_encoding = 'UTF-8'
self.eol = eol
self.filters = filters
self.update_transformations()
self.exit_character = 0x1d # GS/CTRL+]
self.menu_character = 0x14 # Menu: CTRL+T
self.alive = None
self._reader_alive = None
self.receiver_thread = None
self.rx_decoder = None
self.tx_decoder = None
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
# start serial->console thread
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
self.receiver_thread.daemon = True
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def start(self):
"""start worker threads"""
self.alive = True
self._start_reader()
# enter console->serial loop
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
self.transmitter_thread.daemon = True
self.transmitter_thread.start()
self.console.setup()
def stop(self):
"""set flag to stop worker threads"""
self.alive = False
def join(self, transmit_only=False):
"""wait for worker threads to terminate"""
self.transmitter_thread.join()
if not transmit_only:
if hasattr(self.serial, 'cancel_read'):
self.serial.cancel_read()
self.receiver_thread.join()
def close(self):
self.serial.close()
def update_transformations(self):
"""take list of transformation classes and instantiate them for rx and tx"""
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
for f in self.filters]
self.tx_transformations = [t() for t in transformations]
self.rx_transformations = list(reversed(self.tx_transformations))
def set_rx_encoding(self, encoding, errors='replace'):
"""set encoding for received data"""
self.input_encoding = encoding
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
def set_tx_encoding(self, encoding, errors='replace'):
"""set encoding for transmitted data"""
self.output_encoding = encoding
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
def dump_port_settings(self):
"""Write current settings to sys.stderr"""
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
p=self.serial))
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
('active' if self.serial.rts else 'inactive'),
('active' if self.serial.dtr else 'inactive'),
('active' if self.serial.break_condition else 'inactive')))
try:
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
('active' if self.serial.cts else 'inactive'),
('active' if self.serial.dsr else 'inactive'),
('active' if self.serial.ri else 'inactive'),
('active' if self.serial.cd else 'inactive')))
except serial.SerialException:
# on RFC 2217 ports, it can happen if no modem state notification was
# yet received. ignore this error.
pass
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def reader(self):
"""loop and copy serial->console"""
try:
while self.alive and self._reader_alive:
# read all that is there or wait for one byte
data = self.serial.read(self.serial.in_waiting or 1)
if data:
if self.raw:
self.console.write_bytes(data)
else:
text = self.rx_decoder.decode(data)
for transformation in self.rx_transformations:
text = transformation.rx(text)
self.console.write(text)
except serial.SerialException:
self.alive = False
self.console.cancel()
raise # XXX handle instead of re-raise?
def writer(self):
"""\
Loop and copy console->serial until self.exit_character character is
found. When self.menu_character is found, interpret the next key
locally.
"""
menu_active = False
try:
while self.alive:
try:
c = self.console.getkey()
except KeyboardInterrupt:
c = '\x03'
if not self.alive:
break
if menu_active:
self.handle_menu_key(c)
menu_active = False
elif c == self.menu_character:
menu_active = True # next char will be for menu
elif c == self.exit_character:
self.stop() # exit app
break
else:
#~ if self.raw:
text = c
for transformation in self.tx_transformations:
text = transformation.tx(text)
self.serial.write(self.tx_encoder.encode(text))
if self.echo:
echo_text = c
for transformation in self.tx_transformations:
echo_text = transformation.echo(echo_text)
self.console.write(echo_text)
except:
self.alive = False
raise
def handle_menu_key(self, c):
"""Implement a simple menu / settings"""
if c == self.menu_character or c == self.exit_character:
# Menu/exit character again -> send itself
self.serial.write(self.tx_encoder.encode(c))
if self.echo:
self.console.write(c)
elif c == '\x15': # CTRL+U -> upload file
self.upload_file()
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
sys.stderr.write(self.get_help_text())
elif c == '\x12': # CTRL+R -> Toggle RTS
self.serial.rts = not self.serial.rts
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
elif c == '\x04': # CTRL+D -> Toggle DTR
self.serial.dtr = not self.serial.dtr
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
elif c == '\x02': # CTRL+B -> toggle BREAK condition
self.serial.break_condition = not self.serial.break_condition
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
elif c == '\x05': # CTRL+E -> toggle local echo
self.echo = not self.echo
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
elif c == '\x06': # CTRL+F -> edit filters
self.change_filter()
elif c == '\x0c': # CTRL+L -> EOL mode
modes = list(EOL_TRANSFORMATIONS) # keys
eol = modes.index(self.eol) + 1
if eol >= len(modes):
eol = 0
self.eol = modes[eol]
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
self.update_transformations()
elif c == '\x01': # CTRL+A -> set encoding
self.change_encoding()
elif c == '\x09': # CTRL+I -> info
self.dump_port_settings()
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
elif c in 'pP': # P -> change port
self.change_port()
elif c in 'sS': # S -> suspend / open port temporarily
self.suspend_port()
elif c in 'bB': # B -> change baudrate
self.change_baudrate()
elif c == '8': # 8 -> change to 8 bits
self.serial.bytesize = serial.EIGHTBITS
self.dump_port_settings()
elif c == '7': # 7 -> change to 8 bits
self.serial.bytesize = serial.SEVENBITS
self.dump_port_settings()
elif c in 'eE': # E -> change to even parity
self.serial.parity = serial.PARITY_EVEN
self.dump_port_settings()
elif c in 'oO': # O -> change to odd parity
self.serial.parity = serial.PARITY_ODD
self.dump_port_settings()
elif c in 'mM': # M -> change to mark parity
self.serial.parity = serial.PARITY_MARK
self.dump_port_settings()
elif c in 'sS': # S -> change to space parity
self.serial.parity = serial.PARITY_SPACE
self.dump_port_settings()
elif c in 'nN': # N -> change to no parity
self.serial.parity = serial.PARITY_NONE
self.dump_port_settings()
elif c == '1': # 1 -> change to 1 stop bits
self.serial.stopbits = serial.STOPBITS_ONE
self.dump_port_settings()
elif c == '2': # 2 -> change to 2 stop bits
self.serial.stopbits = serial.STOPBITS_TWO
self.dump_port_settings()
elif c == '3': # 3 -> change to 1.5 stop bits
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
self.dump_port_settings()
elif c in 'xX': # X -> change software flow control
self.serial.xonxoff = (c == 'X')
self.dump_port_settings()
elif c in 'rR': # R -> change hardware flow control
self.serial.rtscts = (c == 'R')
self.dump_port_settings()
else:
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
def upload_file(self):
"""Ask user for filenname and send its contents"""
sys.stderr.write('\n--- File to upload: ')
sys.stderr.flush()
with self.console:
filename = sys.stdin.readline().rstrip('\r\n')
if filename:
try:
with open(filename, 'rb') as f:
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
while True:
block = f.read(1024)
if not block:
break
self.serial.write(block)
# Wait for output buffer to drain.
self.serial.flush()
sys.stderr.write('.') # Progress indicator.
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
except IOError as e:
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
def change_filter(self):
"""change the i/o transformations"""
sys.stderr.write('\n--- Available Filters:\n')
sys.stderr.write('\n'.join(
'--- {:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
with self.console:
new_filters = sys.stdin.readline().lower().split()
if new_filters:
for f in new_filters:
if f not in TRANSFORMATIONS:
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
break
else:
self.filters = new_filters
self.update_transformations()
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
def change_encoding(self):
"""change encoding on the serial port"""
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
with self.console:
new_encoding = sys.stdin.readline().strip()
if new_encoding:
try:
codecs.lookup(new_encoding)
except LookupError:
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
else:
self.set_rx_encoding(new_encoding)
self.set_tx_encoding(new_encoding)
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
def change_baudrate(self):
"""change the baudrate"""
sys.stderr.write('\n--- Baudrate: ')
sys.stderr.flush()
with self.console:
backup = self.serial.baudrate
try:
self.serial.baudrate = int(sys.stdin.readline().strip())
except ValueError as e:
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
self.serial.baudrate = backup
else:
self.dump_port_settings()
def change_port(self):
"""Have a conversation with the user to change the serial port"""
with self.console:
try:
port = ask_for_port()
except KeyboardInterrupt:
port = None
if port and port != self.serial.port:
# reader thread needs to be shut down
self._stop_reader()
# save settings
settings = self.serial.getSettingsDict()
try:
new_serial = serial.serial_for_url(port, do_not_open=True)
# restore settings and open
new_serial.applySettingsDict(settings)
new_serial.rts = self.serial.rts
new_serial.dtr = self.serial.dtr
new_serial.open()
new_serial.break_condition = self.serial.break_condition
except Exception as e:
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
new_serial.close()
else:
self.serial.close()
self.serial = new_serial
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
# and restart the reader thread
self._start_reader()
def suspend_port(self):
"""\
open port temporarily, allow reconnect, exit and port change to get
out of the loop
"""
# reader thread needs to be shut down
self._stop_reader()
self.serial.close()
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
do_change_port = False
while not self.serial.is_open:
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
exit=key_description(self.exit_character)))
k = self.console.getkey()
if k == self.exit_character:
self.stop() # exit app
break
elif k in 'pP':
do_change_port = True
break
try:
self.serial.open()
except Exception as e:
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
if do_change_port:
self.change_port()
else:
# and restart the reader thread
self._start_reader()
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
def get_help_text(self):
"""return the help text"""
# help text, starts with blank line!
return """
--- pySerial ({version}) - miniterm - help
---
--- {exit:8} Exit program
--- {menu:8} Menu escape key, followed by:
--- Menu keys:
--- {menu:7} Send the menu character itself to remote
--- {exit:7} Send the exit character itself to remote
--- {info:7} Show info
--- {upload:7} Upload file (prompt will be shown)
--- {repr:7} encoding
--- {filter:7} edit filters
--- Toggles:
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
--- {echo:7} echo {eol:7} EOL
---
--- Port settings ({menu} followed by the following):
--- p change port
--- 7 8 set data bits
--- N E O S M change parity (None, Even, Odd, Space, Mark)
--- 1 2 3 set stop bits (1, 2, 1.5)
--- b change baud rate
--- x X disable/enable software flow control
--- r R disable/enable hardware flow control
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
exit=key_description(self.exit_character),
menu=key_description(self.menu_character),
rts=key_description('\x12'),
dtr=key_description('\x04'),
brk=key_description('\x02'),
echo=key_description('\x05'),
info=key_description('\x09'),
upload=key_description('\x15'),
repr=key_description('\x01'),
filter=key_description('\x06'),
eol=key_description('\x0c'))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# default args can be used to override when calling main() from an other script
# e.g to create a miniterm-my-device.py
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
"""Command line tool, entry point"""
import argparse
parser = argparse.ArgumentParser(
description="Miniterm - A simple terminal program for the serial port.")
parser.add_argument(
"port",
nargs='?',
help="serial port name ('-' to show port list)",
default=default_port)
parser.add_argument(
"baudrate",
nargs='?',
type=int,
help="set baud rate, default: %(default)s",
default=default_baudrate)
group = parser.add_argument_group("port settings")
group.add_argument(
"--parity",
choices=['N', 'E', 'O', 'S', 'M'],
type=lambda c: c.upper(),
help="set parity, one of {N E O S M}, default: N",
default='N')
group.add_argument(
"--rtscts",
action="store_true",
help="enable RTS/CTS flow control (default off)",
default=False)
group.add_argument(
"--xonxoff",
action="store_true",
help="enable software flow control (default off)",
default=False)
group.add_argument(
"--rts",
type=int,
help="set initial RTS line state (possible values: 0, 1)",
default=default_rts)
group.add_argument(
"--dtr",
type=int,
help="set initial DTR line state (possible values: 0, 1)",
default=default_dtr)
group.add_argument(
"--ask",
action="store_true",
help="ask again for port when open fails",
default=False)
group = parser.add_argument_group("data handling")
group.add_argument(
"-e", "--echo",
action="store_true",
help="enable local echo (default off)",
default=False)
group.add_argument(
"--encoding",
dest="serial_port_encoding",
metavar="CODEC",
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
default='UTF-8')
group.add_argument(
"-f", "--filter",
action="append",
metavar="NAME",
help="add text transformation",
default=[])
group.add_argument(
"--eol",
choices=['CR', 'LF', 'CRLF'],
type=lambda c: c.upper(),
help="end of line mode",
default='CRLF')
group.add_argument(
"--raw",
action="store_true",
help="Do no apply any encodings/transformations",
default=False)
group = parser.add_argument_group("hotkeys")
group.add_argument(
"--exit-char",
type=int,
metavar='NUM',
help="Unicode of special character that is used to exit the application, default: %(default)s",
default=0x1d) # GS/CTRL+]
group.add_argument(
"--menu-char",
type=int,
metavar='NUM',
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
default=0x14) # Menu: CTRL+T
group = parser.add_argument_group("diagnostics")
group.add_argument(
"-q", "--quiet",
action="store_true",
help="suppress non-error messages",
default=False)
group.add_argument(
"--develop",
action="store_true",
help="show Python traceback on error",
default=False)
args = parser.parse_args()
if args.menu_char == args.exit_char:
parser.error('--exit-char can not be the same as --menu-char')
if args.filter:
if 'help' in args.filter:
sys.stderr.write('Available filters:\n')
sys.stderr.write('\n'.join(
'{:<10} = {.__doc__}'.format(k, v)
for k, v in sorted(TRANSFORMATIONS.items())))
sys.stderr.write('\n')
sys.exit(1)
filters = args.filter
else:
filters = ['default']
while True:
# no port given on command line -> ask user now
if args.port is None or args.port == '-':
try:
args.port = ask_for_port()
except KeyboardInterrupt:
sys.stderr.write('\n')
parser.error('user aborted and port is not given')
else:
if not args.port:
parser.error('port is not given')
try:
serial_instance = serial.serial_for_url(
args.port,
args.baudrate,
parity=args.parity,
rtscts=args.rtscts,
xonxoff=args.xonxoff,
do_not_open=True)
if not hasattr(serial_instance, 'cancel_read'):
# enable timeout for alive flag polling if cancel_read is not available
serial_instance.timeout = 1
if args.dtr is not None:
if not args.quiet:
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
serial_instance.dtr = args.dtr
if args.rts is not None:
if not args.quiet:
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
serial_instance.rts = args.rts
serial_instance.open()
except serial.SerialException as e:
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
if args.develop:
raise
if not args.ask:
sys.exit(1)
else:
args.port = '-'
else:
break
miniterm = Miniterm(
serial_instance,
echo=args.echo,
eol=args.eol.lower(),
filters=filters)
miniterm.exit_character = unichr(args.exit_char)
miniterm.menu_character = unichr(args.menu_char)
miniterm.raw = args.raw
miniterm.set_rx_encoding(args.serial_port_encoding)
miniterm.set_tx_encoding(args.serial_port_encoding)
if not args.quiet:
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
p=miniterm.serial))
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
key_description(miniterm.exit_character),
key_description(miniterm.menu_character),
key_description(miniterm.menu_character),
key_description('\x08')))
miniterm.start()
try:
miniterm.join(True)
except KeyboardInterrupt:
pass
if not args.quiet:
sys.stderr.write("\n--- exit ---\n")
miniterm.join()
miniterm.close()
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if __name__ == '__main__':
main()
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
-10
View File
@@ -1,10 +0,0 @@
#!/root/projekti/TeraHz/utils/venv/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
@@ -1,25 +0,0 @@
Metadata-Version: 2.0
Name: cycler
Version: 0.10.0
Summary: Composable style cycles
Home-page: http://github.com/matplotlib/cycler
Author: Thomas A Caswell
Author-email: matplotlib-users@python.org
License: BSD
Keywords: cycle kwargs
Platform: Cross platform (Linux
Platform: Mac OSX
Platform: Windows)
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Requires-Dist: six
UNKNOWN
@@ -1,9 +0,0 @@
__pycache__/cycler.cpython-36.pyc,,
cycler-0.10.0.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10
cycler-0.10.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
cycler-0.10.0.dist-info/METADATA,sha256=aWX1pyo7D2hSDNZ2Q6Zl7DxhUQdpyu1O5uNABnvz000,722
cycler-0.10.0.dist-info/RECORD,,
cycler-0.10.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
cycler-0.10.0.dist-info/metadata.json,sha256=CCBpg-KQU-VRL1unJcHPWKQeQbB84G0j7-BeCj7YUbU,875
cycler-0.10.0.dist-info/top_level.txt,sha256=D8BVVDdAAelLb2FOEz7lDpc6-AL21ylKPrMhtG6yzyE,7
cycler.py,sha256=ed3G39unvVEBrBZVDwnE0FFroRNsOLkbJ_TwIT5CjCU,15959
@@ -1,6 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.29.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any
@@ -1 +0,0 @@
{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "matplotlib-users@python.org", "name": "Thomas A Caswell", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/matplotlib/cycler"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "keywords": ["cycle", "kwargs"], "license": "BSD", "metadata_version": "2.0", "name": "cycler", "platform": "Cross platform (Linux", "run_requires": [{"requires": ["six"]}], "summary": "Composable style cycles", "version": "0.10.0"}
@@ -1,558 +0,0 @@
"""
Cycler
======
Cycling through combinations of values, producing dictionaries.
You can add cyclers::
from cycler import cycler
cc = (cycler(color=list('rgb')) +
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)
Results in::
{'color': 'r', 'linestyle': '-'}
{'color': 'g', 'linestyle': '--'}
{'color': 'b', 'linestyle': '-.'}
You can multiply cyclers::
from cycler import cycler
cc = (cycler(color=list('rgb')) *
cycler(linestyle=['-', '--', '-.']))
for d in cc:
print(d)
Results in::
{'color': 'r', 'linestyle': '-'}
{'color': 'r', 'linestyle': '--'}
{'color': 'r', 'linestyle': '-.'}
{'color': 'g', 'linestyle': '-'}
{'color': 'g', 'linestyle': '--'}
{'color': 'g', 'linestyle': '-.'}
{'color': 'b', 'linestyle': '-'}
{'color': 'b', 'linestyle': '--'}
{'color': 'b', 'linestyle': '-.'}
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import six
from itertools import product, cycle
from six.moves import zip, reduce
from operator import mul, add
import copy
__version__ = '0.10.0'
def _process_keys(left, right):
"""
Helper function to compose cycler keys
Parameters
----------
left, right : iterable of dictionaries or None
The cyclers to be composed
Returns
-------
keys : set
The keys in the composition of the two cyclers
"""
l_peek = next(iter(left)) if left is not None else {}
r_peek = next(iter(right)) if right is not None else {}
l_key = set(l_peek.keys())
r_key = set(r_peek.keys())
if l_key & r_key:
raise ValueError("Can not compose overlapping cycles")
return l_key | r_key
class Cycler(object):
"""
Composable cycles
This class has compositions methods:
``+``
for 'inner' products (zip)
``+=``
in-place ``+``
``*``
for outer products (itertools.product) and integer multiplication
``*=``
in-place ``*``
and supports basic slicing via ``[]``
Parameters
----------
left : Cycler or None
The 'left' cycler
right : Cycler or None
The 'right' cycler
op : func or None
Function which composes the 'left' and 'right' cyclers.
"""
def __call__(self):
return cycle(self)
def __init__(self, left, right=None, op=None):
"""Semi-private init
Do not use this directly, use `cycler` function instead.
"""
if isinstance(left, Cycler):
self._left = Cycler(left._left, left._right, left._op)
elif left is not None:
# Need to copy the dictionary or else that will be a residual
# mutable that could lead to strange errors
self._left = [copy.copy(v) for v in left]
else:
self._left = None
if isinstance(right, Cycler):
self._right = Cycler(right._left, right._right, right._op)
elif right is not None:
# Need to copy the dictionary or else that will be a residual
# mutable that could lead to strange errors
self._right = [copy.copy(v) for v in right]
else:
self._right = None
self._keys = _process_keys(self._left, self._right)
self._op = op
@property
def keys(self):
"""
The keys this Cycler knows about
"""
return set(self._keys)
def change_key(self, old, new):
"""
Change a key in this cycler to a new name.
Modification is performed in-place.
Does nothing if the old key is the same as the new key.
Raises a ValueError if the new key is already a key.
Raises a KeyError if the old key isn't a key.
"""
if old == new:
return
if new in self._keys:
raise ValueError("Can't replace %s with %s, %s is already a key" %
(old, new, new))
if old not in self._keys:
raise KeyError("Can't replace %s with %s, %s is not a key" %
(old, new, old))
self._keys.remove(old)
self._keys.add(new)
if self._right is not None and old in self._right.keys:
self._right.change_key(old, new)
# self._left should always be non-None
# if self._keys is non-empty.
elif isinstance(self._left, Cycler):
self._left.change_key(old, new)
else:
# It should be completely safe at this point to
# assume that the old key can be found in each
# iteration.
self._left = [{new: entry[old]} for entry in self._left]
def _compose(self):
"""
Compose the 'left' and 'right' components of this cycle
with the proper operation (zip or product as of now)
"""
for a, b in self._op(self._left, self._right):
out = dict()
out.update(a)
out.update(b)
yield out
@classmethod
def _from_iter(cls, label, itr):
"""
Class method to create 'base' Cycler objects
that do not have a 'right' or 'op' and for which
the 'left' object is not another Cycler.
Parameters
----------
label : str
The property key.
itr : iterable
Finite length iterable of the property values.
Returns
-------
cycler : Cycler
New 'base' `Cycler`
"""
ret = cls(None)
ret._left = list({label: v} for v in itr)
ret._keys = set([label])
return ret
def __getitem__(self, key):
# TODO : maybe add numpy style fancy slicing
if isinstance(key, slice):
trans = self.by_key()
return reduce(add, (_cycler(k, v[key])
for k, v in six.iteritems(trans)))
else:
raise ValueError("Can only use slices with Cycler.__getitem__")
def __iter__(self):
if self._right is None:
return iter(dict(l) for l in self._left)
return self._compose()
def __add__(self, other):
"""
Pair-wise combine two equal length cycles (zip)
Parameters
----------
other : Cycler
The second Cycler
"""
if len(self) != len(other):
raise ValueError("Can only add equal length cycles, "
"not {0} and {1}".format(len(self), len(other)))
return Cycler(self, other, zip)
def __mul__(self, other):
"""
Outer product of two cycles (`itertools.product`) or integer
multiplication.
Parameters
----------
other : Cycler or int
The second Cycler or integer
"""
if isinstance(other, Cycler):
return Cycler(self, other, product)
elif isinstance(other, int):
trans = self.by_key()
return reduce(add, (_cycler(k, v*other)
for k, v in six.iteritems(trans)))
else:
return NotImplemented
def __rmul__(self, other):
return self * other
def __len__(self):
op_dict = {zip: min, product: mul}
if self._right is None:
return len(self._left)
l_len = len(self._left)
r_len = len(self._right)
return op_dict[self._op](l_len, r_len)
def __iadd__(self, other):
"""
In-place pair-wise combine two equal length cycles (zip)
Parameters
----------
other : Cycler
The second Cycler
"""
if not isinstance(other, Cycler):
raise TypeError("Cannot += with a non-Cycler object")
# True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = zip
self._right = Cycler(other._left, other._right, other._op)
return self
def __imul__(self, other):
"""
In-place outer product of two cycles (`itertools.product`)
Parameters
----------
other : Cycler
The second Cycler
"""
if not isinstance(other, Cycler):
raise TypeError("Cannot *= with a non-Cycler object")
# True shallow copy of self is fine since this is in-place
old_self = copy.copy(self)
self._keys = _process_keys(old_self, other)
self._left = old_self
self._op = product
self._right = Cycler(other._left, other._right, other._op)
return self
def __eq__(self, other):
"""
Check equality
"""
if len(self) != len(other):
return False
if self.keys ^ other.keys:
return False
return all(a == b for a, b in zip(self, other))
def __repr__(self):
op_map = {zip: '+', product: '*'}
if self._right is None:
lab = self.keys.pop()
itr = list(v[lab] for v in self)
return "cycler({lab!r}, {itr!r})".format(lab=lab, itr=itr)
else:
op = op_map.get(self._op, '?')
msg = "({left!r} {op} {right!r})"
return msg.format(left=self._left, op=op, right=self._right)
def _repr_html_(self):
# an table showing the value of each key through a full cycle
output = "<table>"
sorted_keys = sorted(self.keys, key=repr)
for key in sorted_keys:
output += "<th>{key!r}</th>".format(key=key)
for d in iter(self):
output += "<tr>"
for k in sorted_keys:
output += "<td>{val!r}</td>".format(val=d[k])
output += "</tr>"
output += "</table>"
return output
def by_key(self):
"""Values by key
This returns the transposed values of the cycler. Iterating
over a `Cycler` yields dicts with a single value for each key,
this method returns a `dict` of `list` which are the values
for the given key.
The returned value can be used to create an equivalent `Cycler`
using only `+`.
Returns
-------
transpose : dict
dict of lists of the values for each key.
"""
# TODO : sort out if this is a bottle neck, if there is a better way
# and if we care.
keys = self.keys
# change this to dict comprehension when drop 2.6
out = dict((k, list()) for k in keys)
for d in self:
for k in keys:
out[k].append(d[k])
return out
# for back compatibility
_transpose = by_key
def simplify(self):
"""Simplify the Cycler
Returned as a composition using only sums (no multiplications)
Returns
-------
simple : Cycler
An equivalent cycler using only summation"""
# TODO: sort out if it is worth the effort to make sure this is
# balanced. Currently it is is
# (((a + b) + c) + d) vs
# ((a + b) + (c + d))
# I would believe that there is some performance implications
trans = self.by_key()
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans)))
def concat(self, other):
"""Concatenate this cycler and an other.
The keys must match exactly.
This returns a single Cycler which is equivalent to
`itertools.chain(self, other)`
Examples
--------
>>> num = cycler('a', range(3))
>>> let = cycler('a', 'abc')
>>> num.concat(let)
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
Parameters
----------
other : `Cycler`
The `Cycler` to concatenate to this one.
Returns
-------
ret : `Cycler`
The concatenated `Cycler`
"""
return concat(self, other)
def concat(left, right):
"""Concatenate two cyclers.
The keys must match exactly.
This returns a single Cycler which is equivalent to
`itertools.chain(left, right)`
Examples
--------
>>> num = cycler('a', range(3))
>>> let = cycler('a', 'abc')
>>> num.concat(let)
cycler('a', [0, 1, 2, 'a', 'b', 'c'])
Parameters
----------
left, right : `Cycler`
The two `Cycler` instances to concatenate
Returns
-------
ret : `Cycler`
The concatenated `Cycler`
"""
if left.keys != right.keys:
msg = '\n\t'.join(["Keys do not match:",
"Intersection: {both!r}",
"Disjoint: {just_one!r}"]).format(
both=left.keys & right.keys,
just_one=left.keys ^ right.keys)
raise ValueError(msg)
_l = left.by_key()
_r = right.by_key()
return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys))
def cycler(*args, **kwargs):
"""
Create a new `Cycler` object from a single positional argument,
a pair of positional arguments, or the combination of keyword arguments.
cycler(arg)
cycler(label1=itr1[, label2=iter2[, ...]])
cycler(label, itr)
Form 1 simply copies a given `Cycler` object.
Form 2 composes a `Cycler` as an inner product of the
pairs of keyword arguments. In other words, all of the
iterables are cycled simultaneously, as if through zip().
Form 3 creates a `Cycler` from a label and an iterable.
This is useful for when the label cannot be a keyword argument
(e.g., an integer or a name that has a space in it).
Parameters
----------
arg : Cycler
Copy constructor for Cycler (does a shallow copy of iterables).
label : name
The property key. In the 2-arg form of the function,
the label can be any hashable object. In the keyword argument
form of the function, it must be a valid python identifier.
itr : iterable
Finite length iterable of the property values.
Can be a single-property `Cycler` that would
be like a key change, but as a shallow copy.
Returns
-------
cycler : Cycler
New `Cycler` for the given property
"""
if args and kwargs:
raise TypeError("cyl() can only accept positional OR keyword "
"arguments -- not both.")
if len(args) == 1:
if not isinstance(args[0], Cycler):
raise TypeError("If only one positional argument given, it must "
" be a Cycler instance.")
return Cycler(args[0])
elif len(args) == 2:
return _cycler(*args)
elif len(args) > 2:
raise TypeError("Only a single Cycler can be accepted as the lone "
"positional argument. Use keyword arguments instead.")
if kwargs:
return reduce(add, (_cycler(k, v) for k, v in six.iteritems(kwargs)))
raise TypeError("Must have at least a positional OR keyword arguments")
def _cycler(label, itr):
"""
Create a new `Cycler` object from a property name and
iterable of values.
Parameters
----------
label : hashable
The property key.
itr : iterable
Finite length iterable of the property values.
Returns
-------
cycler : Cycler
New `Cycler` for the given property
"""
if isinstance(itr, Cycler):
keys = itr.keys
if len(keys) != 1:
msg = "Can not create Cycler from a multi-property Cycler"
raise ValueError(msg)
lab = keys.pop()
# Doesn't need to be a new list because
# _from_iter() will be creating that new list anyway.
itr = (v[lab] for v in itr)
return Cycler._from_iter(label, itr)
@@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
try:
from ._version import version as __version__
except ImportError:
__version__ = 'unknown'
__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz',
'utils', 'zoneinfo']
@@ -1,43 +0,0 @@
"""
Common code used in multiple modules.
"""
class weekday(object):
__slots__ = ["weekday", "n"]
def __init__(self, weekday, n=None):
self.weekday = weekday
self.n = n
def __call__(self, n):
if n == self.n:
return self
else:
return self.__class__(self.weekday, n)
def __eq__(self, other):
try:
if self.weekday != other.weekday or self.n != other.n:
return False
except AttributeError:
return False
return True
def __hash__(self):
return hash((
self.weekday,
self.n,
))
def __ne__(self, other):
return not (self == other)
def __repr__(self):
s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday]
if not self.n:
return s
else:
return "%s(%+d)" % (s, self.n)
# vim:ts=4:sw=4:et
@@ -1,4 +0,0 @@
# coding: utf-8
# file generated by setuptools_scm
# don't change, don't track in version control
version = '2.7.5'
@@ -1,89 +0,0 @@
# -*- coding: utf-8 -*-
"""
This module offers a generic easter computing method for any given year, using
Western, Orthodox or Julian algorithms.
"""
import datetime
__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"]
EASTER_JULIAN = 1
EASTER_ORTHODOX = 2
EASTER_WESTERN = 3
def easter(year, method=EASTER_WESTERN):
"""
This method was ported from the work done by GM Arts,
on top of the algorithm by Claus Tondering, which was
based in part on the algorithm of Ouding (1940), as
quoted in "Explanatory Supplement to the Astronomical
Almanac", P. Kenneth Seidelmann, editor.
This algorithm implements three different easter
calculation methods:
1 - Original calculation in Julian calendar, valid in
dates after 326 AD
2 - Original method, with date converted to Gregorian
calendar, valid in years 1583 to 4099
3 - Revised method, in Gregorian calendar, valid in
years 1583 to 4099 as well
These methods are represented by the constants:
* ``EASTER_JULIAN = 1``
* ``EASTER_ORTHODOX = 2``
* ``EASTER_WESTERN = 3``
The default method is method 3.
More about the algorithm may be found at:
`GM Arts: Easter Algorithms <http://www.gmarts.org/index.php?go=415>`_
and
`The Calendar FAQ: Easter <https://www.tondering.dk/claus/cal/easter.php>`_
"""
if not (1 <= method <= 3):
raise ValueError("invalid method")
# g - Golden year - 1
# c - Century
# h - (23 - Epact) mod 30
# i - Number of days from March 21 to Paschal Full Moon
# j - Weekday for PFM (0=Sunday, etc)
# p - Number of days from March 21 to Sunday on or before PFM
# (-6 to 28 methods 1 & 3, to 56 for method 2)
# e - Extra days to add for method 2 (converting Julian
# date to Gregorian date)
y = year
g = y % 19
e = 0
if method < 3:
# Old method
i = (19*g + 15) % 30
j = (y + y//4 + i) % 7
if method == 2:
# Extra dates to convert Julian to Gregorian date
e = 10
if y > 1600:
e = e + y//100 - 16 - (y//100 - 16)//4
else:
# New method
c = y//100
h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30
i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11))
j = (y + y//4 + i + 2 - c + c//4) % 7
# p can be from -6 to 56 corresponding to dates 22 March to 23 May
# (later dates apply to method 2, although 23 May never actually occurs)
p = i - j + e
d = 1 + (p + 27 + (p + 6)//40) % 31
m = 3 + (p + 26)//30
return datetime.date(int(y), int(m), int(d))
@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
from ._parser import parse, parser, parserinfo
from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
from ._parser import UnknownTimezoneWarning
from ._parser import __doc__
from .isoparser import isoparser, isoparse
__all__ = ['parse', 'parser', 'parserinfo',
'isoparse', 'isoparser',
'UnknownTimezoneWarning']
###
# Deprecate portions of the private interface so that downstream code that
# is improperly relying on it is given *some* notice.
def __deprecated_private_func(f):
from functools import wraps
import warnings
msg = ('{name} is a private function and may break without warning, '
'it will be moved and or renamed in future versions.')
msg = msg.format(name=f.__name__)
@wraps(f)
def deprecated_func(*args, **kwargs):
warnings.warn(msg, DeprecationWarning)
return f(*args, **kwargs)
return deprecated_func
def __deprecate_private_class(c):
import warnings
msg = ('{name} is a private class and may break without warning, '
'it will be moved and or renamed in future versions.')
msg = msg.format(name=c.__name__)
class private_class(c):
__doc__ = c.__doc__
def __init__(self, *args, **kwargs):
warnings.warn(msg, DeprecationWarning)
super(private_class, self).__init__(*args, **kwargs)
private_class.__name__ = c.__name__
return private_class
from ._parser import _timelex, _resultbase
from ._parser import _tzparser, _parsetz
_timelex = __deprecate_private_class(_timelex)
_tzparser = __deprecate_private_class(_tzparser)
_resultbase = __deprecate_private_class(_resultbase)
_parsetz = __deprecated_private_func(_parsetz)
File diff suppressed because it is too large Load Diff
@@ -1,406 +0,0 @@
# -*- coding: utf-8 -*-
"""
This module offers a parser for ISO-8601 strings
It is intended to support all valid date, time and datetime formats per the
ISO-8601 specification.
..versionadded:: 2.7.0
"""
from datetime import datetime, timedelta, time, date
import calendar
from dateutil import tz
from functools import wraps
import re
import six
__all__ = ["isoparse", "isoparser"]
def _takes_ascii(f):
@wraps(f)
def func(self, str_in, *args, **kwargs):
# If it's a stream, read the whole thing
str_in = getattr(str_in, 'read', lambda: str_in)()
# If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
if isinstance(str_in, six.text_type):
# ASCII is the same in UTF-8
try:
str_in = str_in.encode('ascii')
except UnicodeEncodeError as e:
msg = 'ISO-8601 strings should contain only ASCII characters'
six.raise_from(ValueError(msg), e)
return f(self, str_in, *args, **kwargs)
return func
class isoparser(object):
def __init__(self, sep=None):
"""
:param sep:
A single character that separates date and time portions. If
``None``, the parser will accept any single character.
For strict ISO-8601 adherence, pass ``'T'``.
"""
if sep is not None:
if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
raise ValueError('Separator must be a single, non-numeric ' +
'ASCII character')
sep = sep.encode('ascii')
self._sep = sep
@_takes_ascii
def isoparse(self, dt_str):
"""
Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
An ISO-8601 datetime string consists of a date portion, followed
optionally by a time portion - the date and time portions are separated
by a single character separator, which is ``T`` in the official
standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
combined with a time portion.
Supported date formats are:
Common:
- ``YYYY``
- ``YYYY-MM`` or ``YYYYMM``
- ``YYYY-MM-DD`` or ``YYYYMMDD``
Uncommon:
- ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
- ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
The ISO week and day numbering follows the same logic as
:func:`datetime.date.isocalendar`.
Supported time formats are:
- ``hh``
- ``hh:mm`` or ``hhmm``
- ``hh:mm:ss`` or ``hhmmss``
- ``hh:mm:ss.sss`` or ``hh:mm:ss.ssssss`` (3-6 sub-second digits)
Midnight is a special case for `hh`, as the standard supports both
00:00 and 24:00 as a representation.
.. caution::
Support for fractional components other than seconds is part of the
ISO-8601 standard, but is not currently implemented in this parser.
Supported time zone offset formats are:
- `Z` (UTC)
- `±HH:MM`
- `±HHMM`
- `±HH`
Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
with the exception of UTC, which will be represented as
:class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
:param dt_str:
A string or stream containing only an ISO-8601 datetime string
:return:
Returns a :class:`datetime.datetime` representing the string.
Unspecified components default to their lowest value.
.. warning::
As of version 2.7.0, the strictness of the parser should not be
considered a stable part of the contract. Any valid ISO-8601 string
that parses correctly with the default settings will continue to
parse correctly in future versions, but invalid strings that
currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
guaranteed to continue failing in future versions if they encode
a valid date.
.. versionadded:: 2.7.0
"""
components, pos = self._parse_isodate(dt_str)
if len(dt_str) > pos:
if self._sep is None or dt_str[pos:pos + 1] == self._sep:
components += self._parse_isotime(dt_str[pos + 1:])
else:
raise ValueError('String contains unknown ISO components')
return datetime(*components)
@_takes_ascii
def parse_isodate(self, datestr):
"""
Parse the date portion of an ISO string.
:param datestr:
The string portion of an ISO string, without a separator
:return:
Returns a :class:`datetime.date` object
"""
components, pos = self._parse_isodate(datestr)
if pos < len(datestr):
raise ValueError('String contains unknown ISO ' +
'components: {}'.format(datestr))
return date(*components)
@_takes_ascii
def parse_isotime(self, timestr):
"""
Parse the time portion of an ISO string.
:param timestr:
The time portion of an ISO string, without a separator
:return:
Returns a :class:`datetime.time` object
"""
return time(*self._parse_isotime(timestr))
@_takes_ascii
def parse_tzstr(self, tzstr, zero_as_utc=True):
"""
Parse a valid ISO time zone string.
See :func:`isoparser.isoparse` for details on supported formats.
:param tzstr:
A string representing an ISO time zone offset
:param zero_as_utc:
Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
:return:
Returns :class:`dateutil.tz.tzoffset` for offsets and
:class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
specified) offsets equivalent to UTC.
"""
return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
# Constants
_MICROSECOND_END_REGEX = re.compile(b'[-+Z]+')
_DATE_SEP = b'-'
_TIME_SEP = b':'
_MICRO_SEP = b'.'
def _parse_isodate(self, dt_str):
try:
return self._parse_isodate_common(dt_str)
except ValueError:
return self._parse_isodate_uncommon(dt_str)
def _parse_isodate_common(self, dt_str):
len_str = len(dt_str)
components = [1, 1, 1]
if len_str < 4:
raise ValueError('ISO string too short')
# Year
components[0] = int(dt_str[0:4])
pos = 4
if pos >= len_str:
return components, pos
has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
if has_sep:
pos += 1
# Month
if len_str - pos < 2:
raise ValueError('Invalid common month')
components[1] = int(dt_str[pos:pos + 2])
pos += 2
if pos >= len_str:
if has_sep:
return components, pos
else:
raise ValueError('Invalid ISO format')
if has_sep:
if dt_str[pos:pos + 1] != self._DATE_SEP:
raise ValueError('Invalid separator in ISO string')
pos += 1
# Day
if len_str - pos < 2:
raise ValueError('Invalid common day')
components[2] = int(dt_str[pos:pos + 2])
return components, pos + 2
def _parse_isodate_uncommon(self, dt_str):
if len(dt_str) < 4:
raise ValueError('ISO string too short')
# All ISO formats start with the year
year = int(dt_str[0:4])
has_sep = dt_str[4:5] == self._DATE_SEP
pos = 4 + has_sep # Skip '-' if it's there
if dt_str[pos:pos + 1] == b'W':
# YYYY-?Www-?D?
pos += 1
weekno = int(dt_str[pos:pos + 2])
pos += 2
dayno = 1
if len(dt_str) > pos:
if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
raise ValueError('Inconsistent use of dash separator')
pos += has_sep
dayno = int(dt_str[pos:pos + 1])
pos += 1
base_date = self._calculate_weekdate(year, weekno, dayno)
else:
# YYYYDDD or YYYY-DDD
if len(dt_str) - pos < 3:
raise ValueError('Invalid ordinal day')
ordinal_day = int(dt_str[pos:pos + 3])
pos += 3
if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
raise ValueError('Invalid ordinal day' +
' {} for year {}'.format(ordinal_day, year))
base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
components = [base_date.year, base_date.month, base_date.day]
return components, pos
def _calculate_weekdate(self, year, week, day):
"""
Calculate the day of corresponding to the ISO year-week-day calendar.
This function is effectively the inverse of
:func:`datetime.date.isocalendar`.
:param year:
The year in the ISO calendar
:param week:
The week in the ISO calendar - range is [1, 53]
:param day:
The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
:return:
Returns a :class:`datetime.date`
"""
if not 0 < week < 54:
raise ValueError('Invalid week: {}'.format(week))
if not 0 < day < 8: # Range is 1-7
raise ValueError('Invalid weekday: {}'.format(day))
# Get week 1 for the specific year:
jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
# Now add the specific number of weeks and days to get what we want
week_offset = (week - 1) * 7 + (day - 1)
return week_1 + timedelta(days=week_offset)
def _parse_isotime(self, timestr):
len_str = len(timestr)
components = [0, 0, 0, 0, None]
pos = 0
comp = -1
if len(timestr) < 2:
raise ValueError('ISO time too short')
has_sep = len_str >= 3 and timestr[2:3] == self._TIME_SEP
while pos < len_str and comp < 5:
comp += 1
if timestr[pos:pos + 1] in b'-+Z':
# Detect time zone boundary
components[-1] = self._parse_tzstr(timestr[pos:])
pos = len_str
break
if comp < 3:
# Hour, minute, second
components[comp] = int(timestr[pos:pos + 2])
pos += 2
if (has_sep and pos < len_str and
timestr[pos:pos + 1] == self._TIME_SEP):
pos += 1
if comp == 3:
# Microsecond
if timestr[pos:pos + 1] != self._MICRO_SEP:
continue
pos += 1
us_str = self._MICROSECOND_END_REGEX.split(timestr[pos:pos + 6],
1)[0]
components[comp] = int(us_str) * 10**(6 - len(us_str))
pos += len(us_str)
if pos < len_str:
raise ValueError('Unused components in ISO string')
if components[0] == 24:
# Standard supports 00:00 and 24:00 as representations of midnight
if any(component != 0 for component in components[1:4]):
raise ValueError('Hour may only be 24 at 24:00:00.000')
components[0] = 0
return components
def _parse_tzstr(self, tzstr, zero_as_utc=True):
if tzstr == b'Z':
return tz.tzutc()
if len(tzstr) not in {3, 5, 6}:
raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
if tzstr[0:1] == b'-':
mult = -1
elif tzstr[0:1] == b'+':
mult = 1
else:
raise ValueError('Time zone offset requires sign')
hours = int(tzstr[1:3])
if len(tzstr) == 3:
minutes = 0
else:
minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
if zero_as_utc and hours == 0 and minutes == 0:
return tz.tzutc()
else:
if minutes > 59:
raise ValueError('Invalid minutes in time zone offset')
if hours > 23:
raise ValueError('Invalid hours in time zone offset')
return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
DEFAULT_ISOPARSER = isoparser()
isoparse = DEFAULT_ISOPARSER.isoparse
@@ -1,590 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
import calendar
import operator
from math import copysign
from six import integer_types
from warnings import warn
from ._common import weekday
MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
class relativedelta(object):
"""
The relativedelta type is based on the specification of the excellent
work done by M.-A. Lemburg in his
`mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
However, notice that this type does *NOT* implement the same algorithm as
his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
There are two different ways to build a relativedelta instance. The
first one is passing it two date/datetime classes::
relativedelta(datetime1, datetime2)
The second one is passing it any number of the following keyword arguments::
relativedelta(arg1=x,arg2=y,arg3=z...)
year, month, day, hour, minute, second, microsecond:
Absolute information (argument is singular); adding or subtracting a
relativedelta with absolute information does not perform an arithmetic
operation, but rather REPLACES the corresponding value in the
original datetime with the value(s) in relativedelta.
years, months, weeks, days, hours, minutes, seconds, microseconds:
Relative information, may be negative (argument is plural); adding
or subtracting a relativedelta with relative information performs
the corresponding aritmetic operation on the original datetime value
with the information in the relativedelta.
weekday:
One of the weekday instances (MO, TU, etc). These
instances may receive a parameter N, specifying the Nth
weekday, which could be positive or negative (like MO(+1)
or MO(-2). Not specifying it is the same as specifying
+1. You can also use an integer, where 0=MO. Notice that
if the calculated date is already Monday, for example,
using MO(1) or MO(-1) won't change the day.
leapdays:
Will add given days to the date found, if year is a leap
year, and the date found is post 28 of february.
yearday, nlyearday:
Set the yearday or the non-leap year day (jump leap days).
These are converted to day/month/leapdays information.
There are relative and absolute forms of the keyword
arguments. The plural is relative, and the singular is
absolute. For each argument in the order below, the absolute form
is applied first (by setting each attribute to that value) and
then the relative form (by adding the value to the attribute).
The order of attributes considered when this relativedelta is
added to a datetime is:
1. Year
2. Month
3. Day
4. Hours
5. Minutes
6. Seconds
7. Microseconds
Finally, weekday is applied, using the rule described above.
For example
>>> dt = datetime(2018, 4, 9, 13, 37, 0)
>>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
datetime(2018, 4, 2, 14, 37, 0)
First, the day is set to 1 (the first of the month), then 25 hours
are added, to get to the 2nd day and 14th hour, finally the
weekday is applied, but since the 2nd is already a Monday there is
no effect.
"""
def __init__(self, dt1=None, dt2=None,
years=0, months=0, days=0, leapdays=0, weeks=0,
hours=0, minutes=0, seconds=0, microseconds=0,
year=None, month=None, day=None, weekday=None,
yearday=None, nlyearday=None,
hour=None, minute=None, second=None, microsecond=None):
if dt1 and dt2:
# datetime is a subclass of date. So both must be date
if not (isinstance(dt1, datetime.date) and
isinstance(dt2, datetime.date)):
raise TypeError("relativedelta only diffs datetime/date")
# We allow two dates, or two datetimes, so we coerce them to be
# of the same type
if (isinstance(dt1, datetime.datetime) !=
isinstance(dt2, datetime.datetime)):
if not isinstance(dt1, datetime.datetime):
dt1 = datetime.datetime.fromordinal(dt1.toordinal())
elif not isinstance(dt2, datetime.datetime):
dt2 = datetime.datetime.fromordinal(dt2.toordinal())
self.years = 0
self.months = 0
self.days = 0
self.leapdays = 0
self.hours = 0
self.minutes = 0
self.seconds = 0
self.microseconds = 0
self.year = None
self.month = None
self.day = None
self.weekday = None
self.hour = None
self.minute = None
self.second = None
self.microsecond = None
self._has_time = 0
# Get year / month delta between the two
months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
self._set_months(months)
# Remove the year/month delta so the timedelta is just well-defined
# time units (seconds, days and microseconds)
dtm = self.__radd__(dt2)
# If we've overshot our target, make an adjustment
if dt1 < dt2:
compare = operator.gt
increment = 1
else:
compare = operator.lt
increment = -1
while compare(dt1, dtm):
months += increment
self._set_months(months)
dtm = self.__radd__(dt2)
# Get the timedelta between the "months-adjusted" date and dt1
delta = dt1 - dtm
self.seconds = delta.seconds + delta.days * 86400
self.microseconds = delta.microseconds
else:
# Check for non-integer values in integer-only quantities
if any(x is not None and x != int(x) for x in (years, months)):
raise ValueError("Non-integer years and months are "
"ambiguous and not currently supported.")
# Relative information
self.years = int(years)
self.months = int(months)
self.days = days + weeks * 7
self.leapdays = leapdays
self.hours = hours
self.minutes = minutes
self.seconds = seconds
self.microseconds = microseconds
# Absolute information
self.year = year
self.month = month
self.day = day
self.hour = hour
self.minute = minute
self.second = second
self.microsecond = microsecond
if any(x is not None and int(x) != x
for x in (year, month, day, hour,
minute, second, microsecond)):
# For now we'll deprecate floats - later it'll be an error.
warn("Non-integer value passed as absolute information. " +
"This is not a well-defined condition and will raise " +
"errors in future versions.", DeprecationWarning)
if isinstance(weekday, integer_types):
self.weekday = weekdays[weekday]
else:
self.weekday = weekday
yday = 0
if nlyearday:
yday = nlyearday
elif yearday:
yday = yearday
if yearday > 59:
self.leapdays = -1
if yday:
ydayidx = [31, 59, 90, 120, 151, 181, 212,
243, 273, 304, 334, 366]
for idx, ydays in enumerate(ydayidx):
if yday <= ydays:
self.month = idx+1
if idx == 0:
self.day = yday
else:
self.day = yday-ydayidx[idx-1]
break
else:
raise ValueError("invalid year day (%d)" % yday)
self._fix()
def _fix(self):
if abs(self.microseconds) > 999999:
s = _sign(self.microseconds)
div, mod = divmod(self.microseconds * s, 1000000)
self.microseconds = mod * s
self.seconds += div * s
if abs(self.seconds) > 59:
s = _sign(self.seconds)
div, mod = divmod(self.seconds * s, 60)
self.seconds = mod * s
self.minutes += div * s
if abs(self.minutes) > 59:
s = _sign(self.minutes)
div, mod = divmod(self.minutes * s, 60)
self.minutes = mod * s
self.hours += div * s
if abs(self.hours) > 23:
s = _sign(self.hours)
div, mod = divmod(self.hours * s, 24)
self.hours = mod * s
self.days += div * s
if abs(self.months) > 11:
s = _sign(self.months)
div, mod = divmod(self.months * s, 12)
self.months = mod * s
self.years += div * s
if (self.hours or self.minutes or self.seconds or self.microseconds
or self.hour is not None or self.minute is not None or
self.second is not None or self.microsecond is not None):
self._has_time = 1
else:
self._has_time = 0
@property
def weeks(self):
return int(self.days / 7.0)
@weeks.setter
def weeks(self, value):
self.days = self.days - (self.weeks * 7) + value * 7
def _set_months(self, months):
self.months = months
if abs(self.months) > 11:
s = _sign(self.months)
div, mod = divmod(self.months * s, 12)
self.months = mod * s
self.years = div * s
else:
self.years = 0
def normalized(self):
"""
Return a version of this object represented entirely using integer
values for the relative attributes.
>>> relativedelta(days=1.5, hours=2).normalized()
relativedelta(days=1, hours=14)
:return:
Returns a :class:`dateutil.relativedelta.relativedelta` object.
"""
# Cascade remainders down (rounding each to roughly nearest microsecond)
days = int(self.days)
hours_f = round(self.hours + 24 * (self.days - days), 11)
hours = int(hours_f)
minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
minutes = int(minutes_f)
seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
seconds = int(seconds_f)
microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
# Constructor carries overflow back up with call to _fix()
return self.__class__(years=self.years, months=self.months,
days=days, hours=hours, minutes=minutes,
seconds=seconds, microseconds=microseconds,
leapdays=self.leapdays, year=self.year,
month=self.month, day=self.day,
weekday=self.weekday, hour=self.hour,
minute=self.minute, second=self.second,
microsecond=self.microsecond)
def __add__(self, other):
if isinstance(other, relativedelta):
return self.__class__(years=other.years + self.years,
months=other.months + self.months,
days=other.days + self.days,
hours=other.hours + self.hours,
minutes=other.minutes + self.minutes,
seconds=other.seconds + self.seconds,
microseconds=(other.microseconds +
self.microseconds),
leapdays=other.leapdays or self.leapdays,
year=(other.year if other.year is not None
else self.year),
month=(other.month if other.month is not None
else self.month),
day=(other.day if other.day is not None
else self.day),
weekday=(other.weekday if other.weekday is not None
else self.weekday),
hour=(other.hour if other.hour is not None
else self.hour),
minute=(other.minute if other.minute is not None
else self.minute),
second=(other.second if other.second is not None
else self.second),
microsecond=(other.microsecond if other.microsecond
is not None else
self.microsecond))
if isinstance(other, datetime.timedelta):
return self.__class__(years=self.years,
months=self.months,
days=self.days + other.days,
hours=self.hours,
minutes=self.minutes,
seconds=self.seconds + other.seconds,
microseconds=self.microseconds + other.microseconds,
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
if not isinstance(other, datetime.date):
return NotImplemented
elif self._has_time and not isinstance(other, datetime.datetime):
other = datetime.datetime.fromordinal(other.toordinal())
year = (self.year or other.year)+self.years
month = self.month or other.month
if self.months:
assert 1 <= abs(self.months) <= 12
month += self.months
if month > 12:
year += 1
month -= 12
elif month < 1:
year -= 1
month += 12
day = min(calendar.monthrange(year, month)[1],
self.day or other.day)
repl = {"year": year, "month": month, "day": day}
for attr in ["hour", "minute", "second", "microsecond"]:
value = getattr(self, attr)
if value is not None:
repl[attr] = value
days = self.days
if self.leapdays and month > 2 and calendar.isleap(year):
days += self.leapdays
ret = (other.replace(**repl)
+ datetime.timedelta(days=days,
hours=self.hours,
minutes=self.minutes,
seconds=self.seconds,
microseconds=self.microseconds))
if self.weekday:
weekday, nth = self.weekday.weekday, self.weekday.n or 1
jumpdays = (abs(nth) - 1) * 7
if nth > 0:
jumpdays += (7 - ret.weekday() + weekday) % 7
else:
jumpdays += (ret.weekday() - weekday) % 7
jumpdays *= -1
ret += datetime.timedelta(days=jumpdays)
return ret
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
return self.__neg__().__radd__(other)
def __sub__(self, other):
if not isinstance(other, relativedelta):
return NotImplemented # In case the other object defines __rsub__
return self.__class__(years=self.years - other.years,
months=self.months - other.months,
days=self.days - other.days,
hours=self.hours - other.hours,
minutes=self.minutes - other.minutes,
seconds=self.seconds - other.seconds,
microseconds=self.microseconds - other.microseconds,
leapdays=self.leapdays or other.leapdays,
year=(self.year if self.year is not None
else other.year),
month=(self.month if self.month is not None else
other.month),
day=(self.day if self.day is not None else
other.day),
weekday=(self.weekday if self.weekday is not None else
other.weekday),
hour=(self.hour if self.hour is not None else
other.hour),
minute=(self.minute if self.minute is not None else
other.minute),
second=(self.second if self.second is not None else
other.second),
microsecond=(self.microsecond if self.microsecond
is not None else
other.microsecond))
def __abs__(self):
return self.__class__(years=abs(self.years),
months=abs(self.months),
days=abs(self.days),
hours=abs(self.hours),
minutes=abs(self.minutes),
seconds=abs(self.seconds),
microseconds=abs(self.microseconds),
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
def __neg__(self):
return self.__class__(years=-self.years,
months=-self.months,
days=-self.days,
hours=-self.hours,
minutes=-self.minutes,
seconds=-self.seconds,
microseconds=-self.microseconds,
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
def __bool__(self):
return not (not self.years and
not self.months and
not self.days and
not self.hours and
not self.minutes and
not self.seconds and
not self.microseconds and
not self.leapdays and
self.year is None and
self.month is None and
self.day is None and
self.weekday is None and
self.hour is None and
self.minute is None and
self.second is None and
self.microsecond is None)
# Compatibility with Python 2.x
__nonzero__ = __bool__
def __mul__(self, other):
try:
f = float(other)
except TypeError:
return NotImplemented
return self.__class__(years=int(self.years * f),
months=int(self.months * f),
days=int(self.days * f),
hours=int(self.hours * f),
minutes=int(self.minutes * f),
seconds=int(self.seconds * f),
microseconds=int(self.microseconds * f),
leapdays=self.leapdays,
year=self.year,
month=self.month,
day=self.day,
weekday=self.weekday,
hour=self.hour,
minute=self.minute,
second=self.second,
microsecond=self.microsecond)
__rmul__ = __mul__
def __eq__(self, other):
if not isinstance(other, relativedelta):
return NotImplemented
if self.weekday or other.weekday:
if not self.weekday or not other.weekday:
return False
if self.weekday.weekday != other.weekday.weekday:
return False
n1, n2 = self.weekday.n, other.weekday.n
if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
return False
return (self.years == other.years and
self.months == other.months and
self.days == other.days and
self.hours == other.hours and
self.minutes == other.minutes and
self.seconds == other.seconds and
self.microseconds == other.microseconds and
self.leapdays == other.leapdays and
self.year == other.year and
self.month == other.month and
self.day == other.day and
self.hour == other.hour and
self.minute == other.minute and
self.second == other.second and
self.microsecond == other.microsecond)
def __hash__(self):
return hash((
self.weekday,
self.years,
self.months,
self.days,
self.hours,
self.minutes,
self.seconds,
self.microseconds,
self.leapdays,
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
self.microsecond,
))
def __ne__(self, other):
return not self.__eq__(other)
def __div__(self, other):
try:
reciprocal = 1 / float(other)
except TypeError:
return NotImplemented
return self.__mul__(reciprocal)
__truediv__ = __div__
def __repr__(self):
l = []
for attr in ["years", "months", "days", "leapdays",
"hours", "minutes", "seconds", "microseconds"]:
value = getattr(self, attr)
if value:
l.append("{attr}={value:+g}".format(attr=attr, value=value))
for attr in ["year", "month", "day", "weekday",
"hour", "minute", "second", "microsecond"]:
value = getattr(self, attr)
if value is not None:
l.append("{attr}={value}".format(attr=attr, value=repr(value)))
return "{classname}({attrs})".format(classname=self.__class__.__name__,
attrs=", ".join(l))
def _sign(x):
return int(copysign(1, x))
# vim:ts=4:sw=4:et
File diff suppressed because it is too large Load Diff
@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
from .tz import *
from .tz import __doc__
#: Convenience constant providing a :class:`tzutc()` instance
#:
#: .. versionadded:: 2.7.0
UTC = tzutc()
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
"enfold", "datetime_ambiguous", "datetime_exists",
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
class DeprecatedTzFormatWarning(Warning):
"""Warning raised when time zones are parsed from deprecated formats."""
@@ -1,415 +0,0 @@
from six import PY3
from functools import wraps
from datetime import datetime, timedelta, tzinfo
ZERO = timedelta(0)
__all__ = ['tzname_in_python2', 'enfold']
def tzname_in_python2(namefunc):
"""Change unicode output into bytestrings in Python 2
tzname() API changed in Python 3. It used to return bytes, but was changed
to unicode strings
"""
def adjust_encoding(*args, **kwargs):
name = namefunc(*args, **kwargs)
if name is not None and not PY3:
name = name.encode()
return name
return adjust_encoding
# The following is adapted from Alexander Belopolsky's tz library
# https://github.com/abalkin/tz
if hasattr(datetime, 'fold'):
# This is the pre-python 3.6 fold situation
def enfold(dt, fold=1):
"""
Provides a unified interface for assigning the ``fold`` attribute to
datetimes both before and after the implementation of PEP-495.
:param fold:
The value for the ``fold`` attribute in the returned datetime. This
should be either 0 or 1.
:return:
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
``fold`` for all versions of Python. In versions prior to
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
subclass of :py:class:`datetime.datetime` with the ``fold``
attribute added, if ``fold`` is 1.
.. versionadded:: 2.6.0
"""
return dt.replace(fold=fold)
else:
class _DatetimeWithFold(datetime):
"""
This is a class designed to provide a PEP 495-compliant interface for
Python versions before 3.6. It is used only for dates in a fold, so
the ``fold`` attribute is fixed at ``1``.
.. versionadded:: 2.6.0
"""
__slots__ = ()
def replace(self, *args, **kwargs):
"""
Return a datetime with the same attributes, except for those
attributes given new values by whichever keyword arguments are
specified. Note that tzinfo=None can be specified to create a naive
datetime from an aware datetime with no conversion of date and time
data.
This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
return a ``datetime.datetime`` even if ``fold`` is unchanged.
"""
argnames = (
'year', 'month', 'day', 'hour', 'minute', 'second',
'microsecond', 'tzinfo'
)
for arg, argname in zip(args, argnames):
if argname in kwargs:
raise TypeError('Duplicate argument: {}'.format(argname))
kwargs[argname] = arg
for argname in argnames:
if argname not in kwargs:
kwargs[argname] = getattr(self, argname)
dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
return dt_class(**kwargs)
@property
def fold(self):
return 1
def enfold(dt, fold=1):
"""
Provides a unified interface for assigning the ``fold`` attribute to
datetimes both before and after the implementation of PEP-495.
:param fold:
The value for the ``fold`` attribute in the returned datetime. This
should be either 0 or 1.
:return:
Returns an object for which ``getattr(dt, 'fold', 0)`` returns
``fold`` for all versions of Python. In versions prior to
Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
subclass of :py:class:`datetime.datetime` with the ``fold``
attribute added, if ``fold`` is 1.
.. versionadded:: 2.6.0
"""
if getattr(dt, 'fold', 0) == fold:
return dt
args = dt.timetuple()[:6]
args += (dt.microsecond, dt.tzinfo)
if fold:
return _DatetimeWithFold(*args)
else:
return datetime(*args)
def _validate_fromutc_inputs(f):
"""
The CPython version of ``fromutc`` checks that the input is a ``datetime``
object and that ``self`` is attached as its ``tzinfo``.
"""
@wraps(f)
def fromutc(self, dt):
if not isinstance(dt, datetime):
raise TypeError("fromutc() requires a datetime argument")
if dt.tzinfo is not self:
raise ValueError("dt.tzinfo is not self")
return f(self, dt)
return fromutc
class _tzinfo(tzinfo):
"""
Base class for all ``dateutil`` ``tzinfo`` objects.
"""
def is_ambiguous(self, dt):
"""
Whether or not the "wall time" of a given datetime is ambiguous in this
zone.
:param dt:
A :py:class:`datetime.datetime`, naive or time zone aware.
:return:
Returns ``True`` if ambiguous, ``False`` otherwise.
.. versionadded:: 2.6.0
"""
dt = dt.replace(tzinfo=self)
wall_0 = enfold(dt, fold=0)
wall_1 = enfold(dt, fold=1)
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
return same_dt and not same_offset
def _fold_status(self, dt_utc, dt_wall):
"""
Determine the fold status of a "wall" datetime, given a representation
of the same datetime as a (naive) UTC datetime. This is calculated based
on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
datetimes, and that this offset is the actual number of hours separating
``dt_utc`` and ``dt_wall``.
:param dt_utc:
Representation of the datetime as UTC
:param dt_wall:
Representation of the datetime as "wall time". This parameter must
either have a `fold` attribute or have a fold-naive
:class:`datetime.tzinfo` attached, otherwise the calculation may
fail.
"""
if self.is_ambiguous(dt_wall):
delta_wall = dt_wall - dt_utc
_fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
else:
_fold = 0
return _fold
def _fold(self, dt):
return getattr(dt, 'fold', 0)
def _fromutc(self, dt):
"""
Given a timezone-aware datetime in a given timezone, calculates a
timezone-aware datetime in a new timezone.
Since this is the one time that we *know* we have an unambiguous
datetime object, we take this opportunity to determine whether the
datetime is ambiguous and in a "fold" state (e.g. if it's the first
occurence, chronologically, of the ambiguous datetime).
:param dt:
A timezone-aware :class:`datetime.datetime` object.
"""
# Re-implement the algorithm from Python's datetime.py
dtoff = dt.utcoffset()
if dtoff is None:
raise ValueError("fromutc() requires a non-None utcoffset() "
"result")
# The original datetime.py code assumes that `dst()` defaults to
# zero during ambiguous times. PEP 495 inverts this presumption, so
# for pre-PEP 495 versions of python, we need to tweak the algorithm.
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc() requires a non-None dst() result")
delta = dtoff - dtdst
dt += delta
# Set fold=1 so we can default to being in the fold for
# ambiguous dates.
dtdst = enfold(dt, fold=1).dst()
if dtdst is None:
raise ValueError("fromutc(): dt.dst gave inconsistent "
"results; cannot convert")
return dt + dtdst
@_validate_fromutc_inputs
def fromutc(self, dt):
"""
Given a timezone-aware datetime in a given timezone, calculates a
timezone-aware datetime in a new timezone.
Since this is the one time that we *know* we have an unambiguous
datetime object, we take this opportunity to determine whether the
datetime is ambiguous and in a "fold" state (e.g. if it's the first
occurance, chronologically, of the ambiguous datetime).
:param dt:
A timezone-aware :class:`datetime.datetime` object.
"""
dt_wall = self._fromutc(dt)
# Calculate the fold status given the two datetimes.
_fold = self._fold_status(dt, dt_wall)
# Set the default fold value for ambiguous dates
return enfold(dt_wall, fold=_fold)
class tzrangebase(_tzinfo):
"""
This is an abstract base class for time zones represented by an annual
transition into and out of DST. Child classes should implement the following
methods:
* ``__init__(self, *args, **kwargs)``
* ``transitions(self, year)`` - this is expected to return a tuple of
datetimes representing the DST on and off transitions in standard
time.
A fully initialized ``tzrangebase`` subclass should also provide the
following attributes:
* ``hasdst``: Boolean whether or not the zone uses DST.
* ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
representing the respective UTC offsets.
* ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
abbreviations in DST and STD, respectively.
* ``_hasdst``: Whether or not the zone has DST.
.. versionadded:: 2.6.0
"""
def __init__(self):
raise NotImplementedError('tzrangebase is an abstract base class')
def utcoffset(self, dt):
isdst = self._isdst(dt)
if isdst is None:
return None
elif isdst:
return self._dst_offset
else:
return self._std_offset
def dst(self, dt):
isdst = self._isdst(dt)
if isdst is None:
return None
elif isdst:
return self._dst_base_offset
else:
return ZERO
@tzname_in_python2
def tzname(self, dt):
if self._isdst(dt):
return self._dst_abbr
else:
return self._std_abbr
def fromutc(self, dt):
""" Given a datetime in UTC, return local time """
if not isinstance(dt, datetime):
raise TypeError("fromutc() requires a datetime argument")
if dt.tzinfo is not self:
raise ValueError("dt.tzinfo is not self")
# Get transitions - if there are none, fixed offset
transitions = self.transitions(dt.year)
if transitions is None:
return dt + self.utcoffset(dt)
# Get the transition times in UTC
dston, dstoff = transitions
dston -= self._std_offset
dstoff -= self._std_offset
utc_transitions = (dston, dstoff)
dt_utc = dt.replace(tzinfo=None)
isdst = self._naive_isdst(dt_utc, utc_transitions)
if isdst:
dt_wall = dt + self._dst_offset
else:
dt_wall = dt + self._std_offset
_fold = int(not isdst and self.is_ambiguous(dt_wall))
return enfold(dt_wall, fold=_fold)
def is_ambiguous(self, dt):
"""
Whether or not the "wall time" of a given datetime is ambiguous in this
zone.
:param dt:
A :py:class:`datetime.datetime`, naive or time zone aware.
:return:
Returns ``True`` if ambiguous, ``False`` otherwise.
.. versionadded:: 2.6.0
"""
if not self.hasdst:
return False
start, end = self.transitions(dt.year)
dt = dt.replace(tzinfo=None)
return (end <= dt < end + self._dst_base_offset)
def _isdst(self, dt):
if not self.hasdst:
return False
elif dt is None:
return None
transitions = self.transitions(dt.year)
if transitions is None:
return False
dt = dt.replace(tzinfo=None)
isdst = self._naive_isdst(dt, transitions)
# Handle ambiguous dates
if not isdst and self.is_ambiguous(dt):
return not self._fold(dt)
else:
return isdst
def _naive_isdst(self, dt, transitions):
dston, dstoff = transitions
dt = dt.replace(tzinfo=None)
if dston < dstoff:
isdst = dston <= dt < dstoff
else:
isdst = not dstoff <= dt < dston
return isdst
@property
def _dst_base_offset(self):
return self._dst_offset - self._std_offset
__hash__ = None
def __ne__(self, other):
return not (self == other)
def __repr__(self):
return "%s(...)" % self.__class__.__name__
__reduce__ = object.__reduce__
@@ -1,49 +0,0 @@
from datetime import timedelta
class _TzSingleton(type):
def __init__(cls, *args, **kwargs):
cls.__instance = None
super(_TzSingleton, cls).__init__(*args, **kwargs)
def __call__(cls):
if cls.__instance is None:
cls.__instance = super(_TzSingleton, cls).__call__()
return cls.__instance
class _TzFactory(type):
def instance(cls, *args, **kwargs):
"""Alternate constructor that returns a fresh instance"""
return type.__call__(cls, *args, **kwargs)
class _TzOffsetFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
def __call__(cls, name, offset):
if isinstance(offset, timedelta):
key = (name, offset.total_seconds())
else:
key = (name, offset)
instance = cls.__instances.get(key, None)
if instance is None:
instance = cls.__instances.setdefault(key,
cls.instance(name, offset))
return instance
class _TzStrFactory(_TzFactory):
def __init__(cls, *args, **kwargs):
cls.__instances = {}
def __call__(cls, s, posix_offset=False):
key = (s, posix_offset)
instance = cls.__instances.get(key, None)
if instance is None:
instance = cls.__instances.setdefault(key,
cls.instance(s, posix_offset))
return instance
File diff suppressed because it is too large Load Diff
@@ -1,331 +0,0 @@
# This code was originally contributed by Jeffrey Harris.
import datetime
import struct
from six.moves import winreg
from six import text_type
try:
import ctypes
from ctypes import wintypes
except ValueError:
# ValueError is raised on non-Windows systems for some horrible reason.
raise ImportError("Running tzwin on non-Windows system")
from ._common import tzrangebase
__all__ = ["tzwin", "tzwinlocal", "tzres"]
ONEWEEK = datetime.timedelta(7)
TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
def _settzkeyname():
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
winreg.OpenKey(handle, TZKEYNAMENT).Close()
TZKEYNAME = TZKEYNAMENT
except WindowsError:
TZKEYNAME = TZKEYNAME9X
handle.Close()
return TZKEYNAME
TZKEYNAME = _settzkeyname()
class tzres(object):
"""
Class for accessing `tzres.dll`, which contains timezone name related
resources.
.. versionadded:: 2.5.0
"""
p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
def __init__(self, tzres_loc='tzres.dll'):
# Load the user32 DLL so we can load strings from tzres
user32 = ctypes.WinDLL('user32')
# Specify the LoadStringW function
user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
wintypes.UINT,
wintypes.LPWSTR,
ctypes.c_int)
self.LoadStringW = user32.LoadStringW
self._tzres = ctypes.WinDLL(tzres_loc)
self.tzres_loc = tzres_loc
def load_name(self, offset):
"""
Load a timezone name from a DLL offset (integer).
>>> from dateutil.tzwin import tzres
>>> tzr = tzres()
>>> print(tzr.load_name(112))
'Eastern Standard Time'
:param offset:
A positive integer value referring to a string from the tzres dll.
..note:
Offsets found in the registry are generally of the form
`@tzres.dll,-114`. The offset in this case if 114, not -114.
"""
resource = self.p_wchar()
lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
return resource[:nchar]
def name_from_string(self, tzname_str):
"""
Parse strings as returned from the Windows registry into the time zone
name as defined in the registry.
>>> from dateutil.tzwin import tzres
>>> tzr = tzres()
>>> print(tzr.name_from_string('@tzres.dll,-251'))
'Dateline Daylight Time'
>>> print(tzr.name_from_string('Eastern Standard Time'))
'Eastern Standard Time'
:param tzname_str:
A timezone name string as returned from a Windows registry key.
:return:
Returns the localized timezone string from tzres.dll if the string
is of the form `@tzres.dll,-offset`, else returns the input string.
"""
if not tzname_str.startswith('@'):
return tzname_str
name_splt = tzname_str.split(',-')
try:
offset = int(name_splt[1])
except:
raise ValueError("Malformed timezone string.")
return self.load_name(offset)
class tzwinbase(tzrangebase):
"""tzinfo class based on win32's timezones available in the registry."""
def __init__(self):
raise NotImplementedError('tzwinbase is an abstract base class')
def __eq__(self, other):
# Compare on all relevant dimensions, including name.
if not isinstance(other, tzwinbase):
return NotImplemented
return (self._std_offset == other._std_offset and
self._dst_offset == other._dst_offset and
self._stddayofweek == other._stddayofweek and
self._dstdayofweek == other._dstdayofweek and
self._stdweeknumber == other._stdweeknumber and
self._dstweeknumber == other._dstweeknumber and
self._stdhour == other._stdhour and
self._dsthour == other._dsthour and
self._stdminute == other._stdminute and
self._dstminute == other._dstminute and
self._std_abbr == other._std_abbr and
self._dst_abbr == other._dst_abbr)
@staticmethod
def list():
"""Return a list of all time zones known to the system."""
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
result = [winreg.EnumKey(tzkey, i)
for i in range(winreg.QueryInfoKey(tzkey)[0])]
return result
def display(self):
return self._display
def transitions(self, year):
"""
For a given year, get the DST on and off transition times, expressed
always on the standard time side. For zones with no transitions, this
function returns ``None``.
:param year:
The year whose transitions you would like to query.
:return:
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
``(dston, dstoff)`` for zones with an annual DST transition, or
``None`` for fixed offset zones.
"""
if not self.hasdst:
return None
dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
self._dsthour, self._dstminute,
self._dstweeknumber)
dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
self._stdhour, self._stdminute,
self._stdweeknumber)
# Ambiguous dates default to the STD side
dstoff -= self._dst_base_offset
return dston, dstoff
def _get_hasdst(self):
return self._dstmonth != 0
@property
def _dst_base_offset(self):
return self._dst_base_offset_
class tzwin(tzwinbase):
def __init__(self, name):
self._name = name
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
with winreg.OpenKey(handle, tzkeyname) as tzkey:
keydict = valuestodict(tzkey)
self._std_abbr = keydict["Std"]
self._dst_abbr = keydict["Dlt"]
self._display = keydict["Display"]
# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
tup = struct.unpack("=3l16h", keydict["TZI"])
stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
dstoffset = stdoffset-tup[2] # + DaylightBias * -1
self._std_offset = datetime.timedelta(minutes=stdoffset)
self._dst_offset = datetime.timedelta(minutes=dstoffset)
# for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
(self._stdmonth,
self._stddayofweek, # Sunday = 0
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[4:9]
(self._dstmonth,
self._dstdayofweek, # Sunday = 0
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[12:17]
self._dst_base_offset_ = self._dst_offset - self._std_offset
self.hasdst = self._get_hasdst()
def __repr__(self):
return "tzwin(%s)" % repr(self._name)
def __reduce__(self):
return (self.__class__, (self._name,))
class tzwinlocal(tzwinbase):
def __init__(self):
with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
keydict = valuestodict(tzlocalkey)
self._std_abbr = keydict["StandardName"]
self._dst_abbr = keydict["DaylightName"]
try:
tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
sn=self._std_abbr)
with winreg.OpenKey(handle, tzkeyname) as tzkey:
_keydict = valuestodict(tzkey)
self._display = _keydict["Display"]
except OSError:
self._display = None
stdoffset = -keydict["Bias"]-keydict["StandardBias"]
dstoffset = stdoffset-keydict["DaylightBias"]
self._std_offset = datetime.timedelta(minutes=stdoffset)
self._dst_offset = datetime.timedelta(minutes=dstoffset)
# For reasons unclear, in this particular key, the day of week has been
# moved to the END of the SYSTEMTIME structure.
tup = struct.unpack("=8h", keydict["StandardStart"])
(self._stdmonth,
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[1:5]
self._stddayofweek = tup[7]
tup = struct.unpack("=8h", keydict["DaylightStart"])
(self._dstmonth,
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[1:5]
self._dstdayofweek = tup[7]
self._dst_base_offset_ = self._dst_offset - self._std_offset
self.hasdst = self._get_hasdst()
def __repr__(self):
return "tzwinlocal()"
def __str__(self):
# str will return the standard name, not the daylight name.
return "tzwinlocal(%s)" % repr(self._std_abbr)
def __reduce__(self):
return (self.__class__, ())
def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
""" dayofweek == 0 means Sunday, whichweek 5 means last instance """
first = datetime.datetime(year, month, 1, hour, minute)
# This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
# Because 7 % 7 = 0
weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
wd = weekdayone + ((whichweek - 1) * ONEWEEK)
if (wd.month != month):
wd -= ONEWEEK
return wd
def valuestodict(key):
"""Convert a registry key's values to a dictionary."""
dout = {}
size = winreg.QueryInfoKey(key)[1]
tz_res = None
for i in range(size):
key_name, value, dtype = winreg.EnumValue(key, i)
if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
# If it's a DWORD (32-bit integer), it's stored as unsigned - convert
# that to a proper signed integer
if value & (1 << 31):
value = value - (1 << 32)
elif dtype == winreg.REG_SZ:
# If it's a reference to the tzres DLL, load the actual string
if value.startswith('@tzres'):
tz_res = tz_res or tzres()
value = tz_res.name_from_string(value)
value = value.rstrip('\x00') # Remove trailing nulls
dout[key_name] = value
return dout
@@ -1,2 +0,0 @@
# tzwin has moved to dateutil.tz.win
from .tz.win import *
@@ -1,71 +0,0 @@
# -*- coding: utf-8 -*-
"""
This module offers general convenience and utility functions for dealing with
datetimes.
.. versionadded:: 2.7.0
"""
from __future__ import unicode_literals
from datetime import datetime, time
def today(tzinfo=None):
"""
Returns a :py:class:`datetime` representing the current day at midnight
:param tzinfo:
The time zone to attach (also used to determine the current day).
:return:
A :py:class:`datetime.datetime` object representing the current day
at midnight.
"""
dt = datetime.now(tzinfo)
return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
def default_tzinfo(dt, tzinfo):
"""
Sets the the ``tzinfo`` parameter on naive datetimes only
This is useful for example when you are provided a datetime that may have
either an implicit or explicit time zone, such as when parsing a time zone
string.
.. doctest::
>>> from dateutil.tz import tzoffset
>>> from dateutil.parser import parse
>>> from dateutil.utils import default_tzinfo
>>> dflt_tz = tzoffset("EST", -18000)
>>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
2014-01-01 12:30:00+00:00
>>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
2014-01-01 12:30:00-05:00
:param dt:
The datetime on which to replace the time zone
:param tzinfo:
The :py:class:`datetime.tzinfo` subclass instance to assign to
``dt`` if (and only if) it is naive.
:return:
Returns an aware :py:class:`datetime.datetime`.
"""
if dt.tzinfo is not None:
return dt
else:
return dt.replace(tzinfo=tzinfo)
def within_delta(dt1, dt2, delta):
"""
Useful for comparing two datetimes that may a negilible difference
to be considered equal.
"""
delta = abs(delta)
difference = dt1 - dt2
return -delta <= difference <= delta
@@ -1,167 +0,0 @@
# -*- coding: utf-8 -*-
import warnings
import json
from tarfile import TarFile
from pkgutil import get_data
from io import BytesIO
from dateutil.tz import tzfile as _tzfile
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
METADATA_FN = 'METADATA'
class tzfile(_tzfile):
def __reduce__(self):
return (gettz, (self._filename,))
def getzoneinfofile_stream():
try:
return BytesIO(get_data(__name__, ZONEFILENAME))
except IOError as e: # TODO switch to FileNotFoundError?
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
return None
class ZoneInfoFile(object):
def __init__(self, zonefile_stream=None):
if zonefile_stream is not None:
with TarFile.open(fileobj=zonefile_stream) as tf:
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
for zf in tf.getmembers()
if zf.isfile() and zf.name != METADATA_FN}
# deal with links: They'll point to their parent object. Less
# waste of memory
links = {zl.name: self.zones[zl.linkname]
for zl in tf.getmembers() if
zl.islnk() or zl.issym()}
self.zones.update(links)
try:
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
metadata_str = metadata_json.read().decode('UTF-8')
self.metadata = json.loads(metadata_str)
except KeyError:
# no metadata in tar file
self.metadata = None
else:
self.zones = {}
self.metadata = None
def get(self, name, default=None):
"""
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
for retrieving zones from the zone dictionary.
:param name:
The name of the zone to retrieve. (Generally IANA zone names)
:param default:
The value to return in the event of a missing key.
.. versionadded:: 2.6.0
"""
return self.zones.get(name, default)
# The current API has gettz as a module function, although in fact it taps into
# a stateful class. So as a workaround for now, without changing the API, we
# will create a new "global" class instance the first time a user requests a
# timezone. Ugly, but adheres to the api.
#
# TODO: Remove after deprecation period.
_CLASS_ZONE_INSTANCE = []
def get_zonefile_instance(new_instance=False):
"""
This is a convenience function which provides a :class:`ZoneInfoFile`
instance using the data provided by the ``dateutil`` package. By default, it
caches a single instance of the ZoneInfoFile object and returns that.
:param new_instance:
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
used as the cached instance for the next call. Otherwise, new instances
are created only as necessary.
:return:
Returns a :class:`ZoneInfoFile` object.
.. versionadded:: 2.6
"""
if new_instance:
zif = None
else:
zif = getattr(get_zonefile_instance, '_cached_instance', None)
if zif is None:
zif = ZoneInfoFile(getzoneinfofile_stream())
get_zonefile_instance._cached_instance = zif
return zif
def gettz(name):
"""
This retrieves a time zone from the local zoneinfo tarball that is packaged
with dateutil.
:param name:
An IANA-style time zone name, as found in the zoneinfo file.
:return:
Returns a :class:`dateutil.tz.tzfile` time zone object.
.. warning::
It is generally inadvisable to use this function, and it is only
provided for API compatibility with earlier versions. This is *not*
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
time zone based on the inputs, favoring system zoneinfo. This is ONLY
for accessing the dateutil-specific zoneinfo (which may be out of
date compared to the system zoneinfo).
.. deprecated:: 2.6
If you need to use a specific zoneinfofile over the system zoneinfo,
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
Use :func:`get_zonefile_instance` to retrieve an instance of the
dateutil-provided zoneinfo.
"""
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
"to use the dateutil-provided zoneinfo files, instantiate a "
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
"instead. See the documentation for details.",
DeprecationWarning)
if len(_CLASS_ZONE_INSTANCE) == 0:
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
def gettz_db_metadata():
""" Get the zonefile metadata
See `zonefile_metadata`_
:returns:
A dictionary with the database metadata
.. deprecated:: 2.6
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
"""
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
"versions, to use the dateutil-provided zoneinfo files, "
"ZoneInfoFile object and query the 'metadata' attribute "
"instead. See the documentation for details.",
DeprecationWarning)
if len(_CLASS_ZONE_INSTANCE) == 0:
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
return _CLASS_ZONE_INSTANCE[0].metadata
@@ -1,53 +0,0 @@
import logging
import os
import tempfile
import shutil
import json
from subprocess import check_call
from tarfile import TarFile
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
filename is the timezone tarball from ``ftp.iana.org/tz``.
"""
tmpdir = tempfile.mkdtemp()
zonedir = os.path.join(tmpdir, "zoneinfo")
moduledir = os.path.dirname(__file__)
try:
with TarFile.open(filename) as tf:
for name in zonegroups:
tf.extract(name, tmpdir)
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
try:
check_call(["zic", "-d", zonedir] + filepaths)
except OSError as e:
_print_on_nosuchfile(e)
raise
# write metadata file
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
json.dump(metadata, f, indent=4, sort_keys=True)
target = os.path.join(moduledir, ZONEFILENAME)
with TarFile.open(target, "w:%s" % format) as tf:
for entry in os.listdir(zonedir):
entrypath = os.path.join(zonedir, entry)
tf.add(entrypath, entry)
finally:
shutil.rmtree(tmpdir)
def _print_on_nosuchfile(e):
"""Print helpful troubleshooting message
e is an exception raised by subprocess.check_call()
"""
if e.errno == 2:
logging.error(
"Could not find zic. Perhaps you need to install "
"libc-bin or some other package that provides it, "
"or it's not in your PATH?")
@@ -1,5 +0,0 @@
"""Run the EasyInstall command"""
if __name__ == '__main__':
from setuptools.command.easy_install import main
main()
@@ -1,16 +0,0 @@
Welcome to Kiwi
===============
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
:target: https://travis-ci.org/nucleic/kiwi
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
algorithm. Kiwi is an implementation of the algorithm based on the seminal
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
has been designed from the ground up to be lightweight and fast. Kiwi ranges
from 10x to 500x faster than the original Cassowary solver with typical use
cases gaining a 40x improvement. Memory savings are consistently > 5x.
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
@@ -1,28 +0,0 @@
Metadata-Version: 2.0
Name: kiwisolver
Version: 1.0.1
Summary: A fast implementation of the Cassowary constraint solver
Home-page: https://github.com/nucleic/kiwi
Author: The Nucleic Development Team
Author-email: sccolbert@gmail.com
License: UNKNOWN
Description-Content-Type: UNKNOWN
Platform: UNKNOWN
Requires-Dist: setuptools
Welcome to Kiwi
===============
.. image:: https://travis-ci.org/nucleic/kiwi.svg?branch=master
:target: https://travis-ci.org/nucleic/kiwi
Kiwi is an efficient C++ implementation of the Cassowary constraint solving
algorithm. Kiwi is an implementation of the algorithm based on the seminal
Cassowary paper. It is *not* a refactoring of the original C++ solver. Kiwi
has been designed from the ground up to be lightweight and fast. Kiwi ranges
from 10x to 500x faster than the original Cassowary solver with typical use
cases gaining a 40x improvement. Memory savings are consistently > 5x.
In addition to the C++ solver, Kiwi ships with hand-rolled Python bindings.
@@ -1,8 +0,0 @@
kiwisolver-1.0.1.dist-info/DESCRIPTION.rst,sha256=Qy5sjKaN4toH_Q7EUHWgwRVmpxBgeGUwEcyB75zRwSI,676
kiwisolver-1.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
kiwisolver-1.0.1.dist-info/METADATA,sha256=bFtjRI423l1EopYow7-FVdw80WP942cTW4I37mnnunU,1006
kiwisolver-1.0.1.dist-info/RECORD,,
kiwisolver-1.0.1.dist-info/WHEEL,sha256=xLbWRW0PO79-mIB5Y7BBxUY9L97LDdQCs-obTuQSLEg,109
kiwisolver-1.0.1.dist-info/metadata.json,sha256=H-tQ6em1_t3jGeivswhlBiHFJ7CBAI6G_RZAsT-QHCs,535
kiwisolver-1.0.1.dist-info/top_level.txt,sha256=xqwWj7oSHlpIjcw2QMJb8puTFPdjDBO78AZp9gjTh9c,11
kiwisolver.cpython-36m-x86_64-linux-gnu.so,sha256=4GkBV_S_pJ93VLqTL5uPrsTtcIaTx6RIvdblto_AmVg,3860760
@@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.30.0)
Root-Is-Purelib: false
Tag: cp36-cp36m-manylinux1_x86_64
@@ -1 +0,0 @@
{"description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "sccolbert@gmail.com", "name": "The Nucleic Development Team", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/nucleic/kiwi"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "metadata_version": "2.0", "name": "kiwisolver", "run_requires": [{"requires": ["setuptools"]}], "summary": "A fast implementation of the Cassowary constraint solver", "version": "1.0.1"}
@@ -1,2 +0,0 @@
import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('mpl_toolkits',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('mpl_toolkits', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('mpl_toolkits', [os.path.dirname(p)])));m = m or sys.modules.setdefault('mpl_toolkits', types.ModuleType('mpl_toolkits'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
import sys, types, os;has_mfs = sys.version_info > (3, 5);p = os.path.join(sys._getframe(1).f_locals['sitedir'], *('mpl_toolkits',));importlib = has_mfs and __import__('importlib.util');has_mfs and __import__('importlib.machinery');m = has_mfs and sys.modules.setdefault('mpl_toolkits', importlib.util.module_from_spec(importlib.machinery.PathFinder.find_spec('mpl_toolkits', [os.path.dirname(p)])));m = m or sys.modules.setdefault('mpl_toolkits', types.ModuleType('mpl_toolkits'));mp = (m or []) and m.__dict__.setdefault('__path__',[]);(p not in mp) and mp.append(p)
@@ -1,33 +0,0 @@
Metadata-Version: 2.1
Name: matplotlib
Version: 3.0.2
Summary: Python plotting package
Home-page: http://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: BSD
Download-URL: http://matplotlib.org/users/installing.html
Platform: any
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Python Software Foundation License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.5
Requires-Dist: numpy (>=1.10.0)
Requires-Dist: cycler (>=0.10)
Requires-Dist: kiwisolver (>=1.0.1)
Requires-Dist: pyparsing (!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1)
Requires-Dist: python-dateutil (>=2.1)
Matplotlib strives to produce publication quality 2D graphics
for interactive graphing, scientific publishing, user interface
development and web application servers targeting multiple user
interfaces and hardcopy output formats.
@@ -1,919 +0,0 @@
__pycache__/pylab.cpython-36.pyc,,
matplotlib-3.0.2-py3.6-nspkg.pth,sha256=HBCg6BgtP04BPqHtaNyC13Cbp9tWa8jd68ltGEV1XF8,1138
matplotlib-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
matplotlib-3.0.2.dist-info/METADATA,sha256=fs4NGrFCD56DFwQffsSExcU818TFY8T9OTinV1dNP5w,1226
matplotlib-3.0.2.dist-info/RECORD,,
matplotlib-3.0.2.dist-info/WHEEL,sha256=d2ILPScH-y2UwGxsW1PeA2TT-KW0Git4AJ6LeOK8sQo,109
matplotlib-3.0.2.dist-info/namespace_packages.txt,sha256=LQMWCv385LtvVcrCFmS8Kk1axcWAaUG9ycvuMhV6yoA,26
matplotlib-3.0.2.dist-info/top_level.txt,sha256=9tEw2ni8DdgX8CceoYHqSH1s50vrJ9SDfgtLIG8e3Y4,30
matplotlib/.libs/libpng16-cfdb1654.so.16.21.0,sha256=Fo8LBDWTuCclLkpSng_KP5pI7wcQtuXA9opT1FFkXl0,275648
matplotlib/.libs/libz-a147dcb0.so.1.2.3,sha256=1IGoOjRpujOMRn7cZ29ERtAxBt6SxTUlRLBkSqa_lsk,87848
matplotlib/__init__.py,sha256=pWjrFCjP1acCOKaKs7AMHt1xVBiNQ_EXQtq6DUJJQUU,64067
matplotlib/__pycache__/__init__.cpython-36.pyc,,
matplotlib/__pycache__/_animation_data.cpython-36.pyc,,
matplotlib/__pycache__/_cm.cpython-36.pyc,,
matplotlib/__pycache__/_cm_listed.cpython-36.pyc,,
matplotlib/__pycache__/_color_data.cpython-36.pyc,,
matplotlib/__pycache__/_constrained_layout.cpython-36.pyc,,
matplotlib/__pycache__/_layoutbox.cpython-36.pyc,,
matplotlib/__pycache__/_mathtext_data.cpython-36.pyc,,
matplotlib/__pycache__/_pylab_helpers.cpython-36.pyc,,
matplotlib/__pycache__/_version.cpython-36.pyc,,
matplotlib/__pycache__/afm.cpython-36.pyc,,
matplotlib/__pycache__/animation.cpython-36.pyc,,
matplotlib/__pycache__/artist.cpython-36.pyc,,
matplotlib/__pycache__/axis.cpython-36.pyc,,
matplotlib/__pycache__/backend_bases.cpython-36.pyc,,
matplotlib/__pycache__/backend_managers.cpython-36.pyc,,
matplotlib/__pycache__/backend_tools.cpython-36.pyc,,
matplotlib/__pycache__/bezier.cpython-36.pyc,,
matplotlib/__pycache__/blocking_input.cpython-36.pyc,,
matplotlib/__pycache__/category.cpython-36.pyc,,
matplotlib/__pycache__/cm.cpython-36.pyc,,
matplotlib/__pycache__/collections.cpython-36.pyc,,
matplotlib/__pycache__/colorbar.cpython-36.pyc,,
matplotlib/__pycache__/colors.cpython-36.pyc,,
matplotlib/__pycache__/container.cpython-36.pyc,,
matplotlib/__pycache__/contour.cpython-36.pyc,,
matplotlib/__pycache__/dates.cpython-36.pyc,,
matplotlib/__pycache__/docstring.cpython-36.pyc,,
matplotlib/__pycache__/dviread.cpython-36.pyc,,
matplotlib/__pycache__/figure.cpython-36.pyc,,
matplotlib/__pycache__/font_manager.cpython-36.pyc,,
matplotlib/__pycache__/fontconfig_pattern.cpython-36.pyc,,
matplotlib/__pycache__/gridspec.cpython-36.pyc,,
matplotlib/__pycache__/hatch.cpython-36.pyc,,
matplotlib/__pycache__/image.cpython-36.pyc,,
matplotlib/__pycache__/legend.cpython-36.pyc,,
matplotlib/__pycache__/legend_handler.cpython-36.pyc,,
matplotlib/__pycache__/lines.cpython-36.pyc,,
matplotlib/__pycache__/markers.cpython-36.pyc,,
matplotlib/__pycache__/mathtext.cpython-36.pyc,,
matplotlib/__pycache__/mlab.cpython-36.pyc,,
matplotlib/__pycache__/offsetbox.cpython-36.pyc,,
matplotlib/__pycache__/patches.cpython-36.pyc,,
matplotlib/__pycache__/path.cpython-36.pyc,,
matplotlib/__pycache__/patheffects.cpython-36.pyc,,
matplotlib/__pycache__/pylab.cpython-36.pyc,,
matplotlib/__pycache__/pyplot.cpython-36.pyc,,
matplotlib/__pycache__/quiver.cpython-36.pyc,,
matplotlib/__pycache__/rcsetup.cpython-36.pyc,,
matplotlib/__pycache__/sankey.cpython-36.pyc,,
matplotlib/__pycache__/scale.cpython-36.pyc,,
matplotlib/__pycache__/spines.cpython-36.pyc,,
matplotlib/__pycache__/stackplot.cpython-36.pyc,,
matplotlib/__pycache__/streamplot.cpython-36.pyc,,
matplotlib/__pycache__/table.cpython-36.pyc,,
matplotlib/__pycache__/texmanager.cpython-36.pyc,,
matplotlib/__pycache__/text.cpython-36.pyc,,
matplotlib/__pycache__/textpath.cpython-36.pyc,,
matplotlib/__pycache__/ticker.cpython-36.pyc,,
matplotlib/__pycache__/tight_bbox.cpython-36.pyc,,
matplotlib/__pycache__/tight_layout.cpython-36.pyc,,
matplotlib/__pycache__/transforms.cpython-36.pyc,,
matplotlib/__pycache__/type1font.cpython-36.pyc,,
matplotlib/__pycache__/units.cpython-36.pyc,,
matplotlib/__pycache__/widgets.cpython-36.pyc,,
matplotlib/_animation_data.py,sha256=CCdf8YwNX_08FS3YFPYzr2wi3id_WXIKLHrqM50HM8g,6157
matplotlib/_cm.py,sha256=C3xi_H7o8WF6qSv3Jl0DA1T2vbT-4wIDLFYfuJe5Zpg,66609
matplotlib/_cm_listed.py,sha256=EpTjQ6pZ9E_UeY4kDa6fU9za_VHBvhuOmCG6fwNRvlk,98362
matplotlib/_color_data.py,sha256=9hUzbyqLpEe-2LjEeAN3ja2ANuryNvtTseU8vuJXfsI,34776
matplotlib/_constrained_layout.py,sha256=1ygKBGfY0ttpPFhEWucL0wVnwYP-U_ZX8OxHlCUXXjQ,29304
matplotlib/_contour.cpython-36m-x86_64-linux-gnu.so,sha256=Gd6D_VYbA-imr5k186kroDG91DrOUYiltJS3QuARruQ,95144
matplotlib/_image.cpython-36m-x86_64-linux-gnu.so,sha256=akH-urj3-vOY9iYC-N9lORYlpSVVMPUKMBnCAVPrqc0,242496
matplotlib/_layoutbox.py,sha256=yXrJA2hBYKdzPNzbrsLl0Lv8zAPtnPulQ9-BbfhcVWM,24354
matplotlib/_mathtext_data.py,sha256=CmKFRW6mXCJqgZSQaiNOSG_VUn9WiSx5Hrg-4qKIn14,89371
matplotlib/_path.cpython-36m-x86_64-linux-gnu.so,sha256=jEhQzvD031IYQRSbo4Fxobi5KpZF-NV_7iuDDRaVBDU,190216
matplotlib/_png.cpython-36m-x86_64-linux-gnu.so,sha256=DlT39C99TemBT-i-7MuJuwJRg6eVwRhMMR4AxInU2QI,48144
matplotlib/_pylab_helpers.py,sha256=lC5IY7NOGCPVD5brJj-PdtJkzA53Fs4hrn9exdzWDFg,3528
matplotlib/_qhull.cpython-36m-x86_64-linux-gnu.so,sha256=dpM763p27zLpJIW8u045wQW71Oz3JwP9BHCHW6M-pHY,382640
matplotlib/_tri.cpython-36m-x86_64-linux-gnu.so,sha256=7InLl8WYUN4CyAtbrAWCXzycxTUs7_xNZx4QZKcifZg,128616
matplotlib/_version.py,sha256=bHCJPWh4TT1eI2_VgNQWyLUAJMklrvz_FkivkMGVXmk,471
matplotlib/afm.py,sha256=Cfj2v5Rgsr5GaDzuKySLd-aqsBdJBLX2T1gt7TQ175o,16934
matplotlib/animation.py,sha256=WyFTZBvyIN2tmvnT9LzlGZDOvqWgPn0SHOfzl0e1LlY,67680
matplotlib/artist.py,sha256=VbAnAHa4CPr39s_99S0yAD9-tBUMPxc-BLvhWeykSLk,48885
matplotlib/axes/__init__.py,sha256=npQuBvs_xEBEGUP2-BBZzCrelsAQYgB1U96kSZTSWIs,46
matplotlib/axes/__pycache__/__init__.cpython-36.pyc,,
matplotlib/axes/__pycache__/_axes.cpython-36.pyc,,
matplotlib/axes/__pycache__/_base.cpython-36.pyc,,
matplotlib/axes/__pycache__/_subplots.cpython-36.pyc,,
matplotlib/axes/_axes.py,sha256=o_hBMnccGhT_BedP7n12Kk6Y-qhEmL7S3guI5EF7r_M,306901
matplotlib/axes/_base.py,sha256=-gd9r0IAp785gRHpHpaN1IibgC5J069NNPKH2d_UbHQ,156195
matplotlib/axes/_subplots.py,sha256=pPG7GjDBFGDnjVXdojw3_ROHUO5kjZM2s7FsUwIH7sE,9479
matplotlib/axis.py,sha256=s2rcQiyNOCskQ3e4J9YeG-dI0GMbH8v3SWbrC7ymtPo,90811
matplotlib/backend_bases.py,sha256=7EaSuF58KKjm5sRYnmsUECkSJc0BcbsYmGd3gMpcpJM,111487
matplotlib/backend_managers.py,sha256=R3ZGS7NiyfAYutPX9KRqRXiZur8VVFb2_yQPNKtQ2Os,12983
matplotlib/backend_tools.py,sha256=nCruryzfTxOT0v8wyQPzPvCuNdOx5eWXTiiVniGoQmg,35810
matplotlib/backends/__init__.py,sha256=Y2fIYrWuxw3DsTGBgaYTkoExgiv5V-fpFMoL6Xluv1U,3593
matplotlib/backends/__pycache__/__init__.cpython-36.pyc,,
matplotlib/backends/__pycache__/_backend_tk.cpython-36.pyc,,
matplotlib/backends/__pycache__/_gtk3_compat.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_gtk3cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_macosx.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_mixed.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_nbagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_pdf.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_pgf.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_ps.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt4cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5agg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_qt5cairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_svg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_template.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_tkagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_tkcairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_webagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_webagg_core.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wx.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wxagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/backend_wxcairo.cpython-36.pyc,,
matplotlib/backends/__pycache__/qt_compat.cpython-36.pyc,,
matplotlib/backends/__pycache__/tkagg.cpython-36.pyc,,
matplotlib/backends/__pycache__/windowing.cpython-36.pyc,,
matplotlib/backends/__pycache__/wx_compat.cpython-36.pyc,,
matplotlib/backends/_backend_agg.cpython-36m-x86_64-linux-gnu.so,sha256=7egU7A_XSGu5zAhXxcs7ubTrUwDx9wCah09ET3u9BTg,358384
matplotlib/backends/_backend_tk.py,sha256=oG6iWxbo96fmE24Q1-2p62MX2sSXwp-bDsCiteqw7bw,37878
matplotlib/backends/_gtk3_compat.py,sha256=_mQjgaLq7PAdWCmkYkN3ZEc7SFms4T47Y6X1bmc3UlI,1458
matplotlib/backends/_tkagg.cpython-36m-x86_64-linux-gnu.so,sha256=ghIFRXt4_3htN74rENcBbpBc_Ly2pRg94F6h_xxE7vI,29080
matplotlib/backends/backend_agg.py,sha256=oWgXuSgkOr_w_Ix3xB07QwaW34CIrDeFr3Ar38KWLnI,21136
matplotlib/backends/backend_cairo.py,sha256=F_Er53Cssgiaz22qKUExxLvgBssxmsl0vRTenkckDJA,23049
matplotlib/backends/backend_gtk3.py,sha256=7yldMkTpLnLqd9GLi905Ba2Se0gBe2bNNjrojNYDKMM,34061
matplotlib/backends/backend_gtk3agg.py,sha256=Sdv_sVpJab05fFqZEz4a8hv2jB6194l0S7I9kioDCBc,2863
matplotlib/backends/backend_gtk3cairo.py,sha256=qvEc-eOfNhYhvG_4COnmCt3e_L-dInGMMnWJY3EG1s8,1516
matplotlib/backends/backend_macosx.py,sha256=cQLOUeHfteu3UzK5VhdBwNRohFidcNGsJ9NNCpcx52Y,6534
matplotlib/backends/backend_mixed.py,sha256=w0npmW09OKYhaHlpaKXY9vBhKNpUrL_gplxhjlm_re0,5738
matplotlib/backends/backend_nbagg.py,sha256=981WrP82fEZJIdND77C2JW4DpS3k82J6PyXthy18-jM,8707
matplotlib/backends/backend_pdf.py,sha256=L6GwqJKiPaFlqPRuhFbJQ9xGhZf8DGkL-FQ5W_nSamw,96965
matplotlib/backends/backend_pgf.py,sha256=8a9VSoU32ZatMBik36IjmrM56GkU0QFc3w1o3z3y89w,43189
matplotlib/backends/backend_ps.py,sha256=M47hHy6DgqRev1emHxv-K8diIoP5aYsNKW1sMex2h2A,61117
matplotlib/backends/backend_qt4.py,sha256=x9KHRXMxJfmlJ-6lMdmKrw9cGe5RlZAAGjk1Ga1xX2c,410
matplotlib/backends/backend_qt4agg.py,sha256=xTZMUL161hqcsOKSeU4sEs9kYloQrC7_OJjfyuYUAp0,245
matplotlib/backends/backend_qt4cairo.py,sha256=B-LD_AECxVVsZA6Zb-oxoN39iM-GUNl81LR7nVaJ8q4,159
matplotlib/backends/backend_qt5.py,sha256=HTxAS2-cOPD_pS40BP4foUl-UfQb3XHmQrNJsuWE8Vs,41475
matplotlib/backends/backend_qt5agg.py,sha256=plgjHJqJ0IawKuEH8Re_mTOLLrH90mRcMs65nbdTLvE,3237
matplotlib/backends/backend_qt5cairo.py,sha256=wTVbxWW9f5REOesXDIzI2eSmE5HS-nwWNcg2m-YvyLI,1977
matplotlib/backends/backend_svg.py,sha256=I_-tR0sEpRFt7jlM8S-dJX8vgjwMifEJTa5N3maJWC0,45518
matplotlib/backends/backend_template.py,sha256=drJi5J7r4ZMpmwvYiVp3uVfWDJ0VtZSEd2A4OD93fUU,9149
matplotlib/backends/backend_tkagg.py,sha256=WMslLWYmtxlmAaBH4tx4HjmRDWMKiSV91KHF9yeMRng,676
matplotlib/backends/backend_tkcairo.py,sha256=dVCh7ZD_2OR0DBQ0N3icD8cDV1SeEzCsRja446wWhPw,1069
matplotlib/backends/backend_webagg.py,sha256=kqDsqw1a0mZA3-ouBGegg8BlWys5iAA0-fAtxTvUZ5o,11124
matplotlib/backends/backend_webagg_core.py,sha256=c8FqOMph-FdYJ-kNauNG23enXWwZwbmQ3QzjhYud4IM,17554
matplotlib/backends/backend_wx.py,sha256=gQTIV29utVqq_W6iqRE5gqGwhDZ3sPg2s0fxXDkmcCY,74559
matplotlib/backends/backend_wxagg.py,sha256=TGdhDULgRzqhLNOzq7QAt0oe_AqHoT-IyZOO8RlE4Bw,4015
matplotlib/backends/backend_wxcairo.py,sha256=VC5TyJaX8TPLSgHv5ckAreoGrY_KiNRMQjVInMLlcFk,1843
matplotlib/backends/qt_compat.py,sha256=gdDU16Oe1HfwJJSGmvLvEJmrKDX6T8Tt1WiSUITR3qU,6658
matplotlib/backends/qt_editor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/backends/qt_editor/__pycache__/__init__.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/figureoptions.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/formlayout.cpython-36.pyc,,
matplotlib/backends/qt_editor/__pycache__/formsubplottool.cpython-36.pyc,,
matplotlib/backends/qt_editor/figureoptions.py,sha256=_TD7NJY_LzXDoljckLqKaVBYa3FSJwEFNrccYpbKMTM,9133
matplotlib/backends/qt_editor/formlayout.py,sha256=qCAmYnZUEwgBRMVsPWuu7e1fqCU0OSM-I5Snw7ShcBI,19573
matplotlib/backends/qt_editor/formsubplottool.py,sha256=HiiXkwCotra_hI9JU208KOs8Q9JuGH1uAW3mV5l3Evg,1934
matplotlib/backends/tkagg.py,sha256=ro4lL5U0cLMslYmLXQRhWwR1mxlcZYGO9awistZzL1E,1319
matplotlib/backends/web_backend/all_figures.html,sha256=GiIHkdjLO94c_GAHVX4Zk5R88uEnIUwW2CJgm3qCCv0,1512
matplotlib/backends/web_backend/css/boilerplate.css,sha256=qui16QXRnQFNJDbcMasfH6KtN9hLjv8883U9cJmsVCE,2310
matplotlib/backends/web_backend/css/fbm.css,sha256=Us0osu_rK8EUAdp_GXrh89tN_hUNCN-r7N1T1NvmmwI,1473
matplotlib/backends/web_backend/css/page.css,sha256=Djf6ZNMFaM6_hVaizSkDFoqk-jn81qgduwles4AroGk,1599
matplotlib/backends/web_backend/ipython_inline_figure.html,sha256=mzi-yWg4fcO6PdtTBCfiNuvcv04T53lcRQi-8hphwuE,1305
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_diagonals-thick_18_b81900_40x40.png,sha256=xRM8xoz-7ahUtdsImL_ZC6s8kJT2QnE04-cji0ZQfsE,418
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_diagonals-thick_20_666666_40x40.png,sha256=T9PQekCQeDFHwAxdZMQs_QzAqaqtimo0uLCotJ0xSqg,312
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_flat_10_000000_40x100.png,sha256=r1CSqnKXLAw-qLRMm5LbIAnqQWB3OLzAqdpZesaoc-o,205
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_f6f6f6_1x400.png,sha256=_ws_N6fYaeQspm2VHS5Wm-QV4CXhlUoSqGQ-lmW3xm0,262
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_100_fdf5ce_1x400.png,sha256=-VfVTyNvfByi7t0zq33BBb1mLO5iuIBvsso4Ye2ysXw,348
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_glass_65_ffffff_1x400.png,sha256=JFJf1ebnJmze7uXUNtoLcgn-dTh-bJxVxXVLIuQDGrU,207
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_gloss-wave_35_f6a828_500x100.png,sha256=Av4RPyWlnMm1at-9VqlutTAvTKW7637sDlQamg2ozNA,5815
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_100_eeeeee_1x100.png,sha256=80rH2tcJybpprH1zkHIN1U_aVhUcZOc9mv9OEYavhRA,278
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-bg_highlight-soft_75_ffe45c_1x100.png,sha256=JRY-0684rYlKOQKRQmXYJ5YiPhHS3kNPZmUVa3xi3hg,328
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_222222_256x240.png,sha256=_htyYBLdV3XU9kp9QnMKIQ8pBX6OgU8zkE05EsTZq9s,6922
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_228ef1_256x240.png,sha256=Z8eq2yeIM45jXe3uMbiKqOqQjA2MKR20tWcBTVc6bC8,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ef8c08_256x240.png,sha256=loEi-IH5oyL8PSSjAGPT549tcpGfRzIbFEaqPFhR8ck,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffd27a_256x240.png,sha256=O-dEGxrQchuZxTdpQJQ4LEirQLT1OA5lBNYO9O8mJo8,4549
matplotlib/backends/web_backend/jquery/css/themes/base/images/ui-icons_ffffff_256x240.png,sha256=sVicDAn7JIIW-osNHhgSqa-bSRWOtS4G94BitP_MAds,6299
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.css,sha256=zs9cWf98KIv5DMYiF1a9lhJGQwhVe5LKVPJ9HNEI880,35348
matplotlib/backends/web_backend/jquery/css/themes/base/jquery-ui.min.css,sha256=VQzrlVm7QjdSeQn_IecZgE9rnfM390H3VoIcDJljOSs,30163
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.js,sha256=kaIBSZlozqJ4IeqPwOlDOi97qhNHWTchpUedwo5-gMU,284395
matplotlib/backends/web_backend/jquery/js/jquery-1.11.3.min.js,sha256=7LkWEzqTdpEfELxcZZlS6wAx5Ff13zZ83lYO2_ujj7g,95957
matplotlib/backends/web_backend/jquery/js/jquery-ui.js,sha256=DI6NdAhhFRnO2k51mumYeDShet3I8AKCQf_tf7ARNhI,470596
matplotlib/backends/web_backend/jquery/js/jquery-ui.min.js,sha256=xNjb53_rY-WmG-4L6tTl9m6PpqknWZvRt0rO1SRnJzw,240427
matplotlib/backends/web_backend/js/mpl.js,sha256=SqYBZRsHYoQY9d-5eCZsVpIBnlR4T7t1JiYYSZOs6Uw,16964
matplotlib/backends/web_backend/js/mpl_tornado.js,sha256=lSxC7-yqF1GYY-6SheaHanx6SujMdcG7Vx2_3qbi-9Q,272
matplotlib/backends/web_backend/js/nbagg_mpl.js,sha256=WfV96-6LXzUSnzCmvQ93JopqO7KPqnfbT_07Q1XJeT4,7467
matplotlib/backends/web_backend/nbagg_uat.ipynb,sha256=y1N8hQzBJ05rJ2hZla2_Mw6tOUfNP1UHKo636W1e098,15933
matplotlib/backends/web_backend/single_figure.html,sha256=56UAOD6qgZ-T6wE2EjGpTBG4iwAmfG3QsnX5lLQp2dI,1203
matplotlib/backends/windowing.py,sha256=q-hSo_vvsjst6EjNkr94NIDbUucMocrMj9MSjVwkfY4,822
matplotlib/backends/wx_compat.py,sha256=RQFVDMaMLjT8YmazLiq3BcjCcCi6xuzS_AwmxtYd2vg,968
matplotlib/bezier.py,sha256=zwC07cDX9iyh9KWnZsPBQaFJ4CTue0NQpm1VwS2PEuM,15415
matplotlib/blocking_input.py,sha256=E6r1m5acvx_KLDp7rZXftnA5OBtJnjy_zGWBUO9k_OA,11112
matplotlib/category.py,sha256=AWYm1nucRySTh-6cXN2FeNYQOpwJPeUl9iFkjcr4oW0,5928
matplotlib/cbook/__init__.py,sha256=bFrhSeVf8Ya6pNEtkqOTVABSR--ADZLbTlsLqd3BUV4,65667
matplotlib/cbook/__pycache__/__init__.cpython-36.pyc,,
matplotlib/cbook/__pycache__/deprecation.cpython-36.pyc,,
matplotlib/cbook/deprecation.py,sha256=5ghVk2E7syukLQuADs7UtHuBRJYaiuvylSymAh_axDQ,9402
matplotlib/cm.py,sha256=MHQtLJ9UGDuyxHa-qyMxL2vikkhXB5OXhYdxUy-_68M,12843
matplotlib/collections.py,sha256=HqR0G7ydei6YkX06MYwp0TGrVZaXz7BDNiwEPGk0Fro,66851
matplotlib/colorbar.py,sha256=_UImtSk1sRHbLGNn_gLTy86mFYqzjovJMvioqcvn57w,59164
matplotlib/colors.py,sha256=HeZGu5MwSkMLEABhU5qYmJw4ZbvpxeOrdV2ytY_klSg,71636
matplotlib/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/compat/__pycache__/__init__.cpython-36.pyc,,
matplotlib/compat/__pycache__/subprocess.cpython-36.pyc,,
matplotlib/compat/subprocess.py,sha256=-K36IW-t5SjjWcP4nL80GAE-y7v0HZE6nQ-hscwmkvg,1562
matplotlib/container.py,sha256=Sbz7JTBAFirjClv-nSpVojuSLKIBoUo-dAve8QAFjl8,5245
matplotlib/contour.py,sha256=XiEg_unL8WQaQf3f3c41KBHltZPXJ8Fhk2W0OGvuacc,70231
matplotlib/dates.py,sha256=XsBhin-wZcY7FPcMrDQDBEYjYXNtmc-YbtwZiysKeqQ,61547
matplotlib/docstring.py,sha256=M5wraMzJf7veSfzUJUArSY68bM5lkH3q6niSmW0q1xw,3787
matplotlib/dviread.py,sha256=s_N3IU68PIVm03TFnBP8_UakGnIYwYCv1vxLDjpWsk4,37282
matplotlib/figure.py,sha256=p9yke2KjrUkOJ5ZUt-q44GqMjCU5woMnFMAXLR3-DJk,95506
matplotlib/font_manager.py,sha256=uWPJ8AR9gzeIljZz8baIRTDacgsAMORLgmV1lT6Ve_s,45972
matplotlib/fontconfig_pattern.py,sha256=LYKBekGIwiHNYyYe36Lfki4JyEkZMsBinmhVzBc1wns,6593
matplotlib/ft2font.cpython-36m-x86_64-linux-gnu.so,sha256=xKwvjhkVt1lLcRA4Swcz9iHqhWTk78UGl6CLyR5tgvw,894456
matplotlib/gridspec.py,sha256=C5rAu1tokWgaegWBEocG6phEfemgMF9dch-HwVfKC0A,20056
matplotlib/hatch.py,sha256=kmq09LW_AJxPY0LGJxJrrC9Yf6tPQYi209EMAveqQoE,6988
matplotlib/image.py,sha256=LGgYY2UmfjldiGhqAU6Qwi-3I_CJAzbL6b9hFMhCWHQ,54632
matplotlib/legend.py,sha256=M6PCguGj32SpnEuLvPhRKfWZuCofn1JYSubhRb9e5Yk,48770
matplotlib/legend_handler.py,sha256=gmQCUHjMmvcJ4g8dSqayhaXj82jkUK36i0-4DK6ik-A,25524
matplotlib/lines.py,sha256=uKymslFnSniquZR_5N266mXvkQJ_4eKu_sg5iP5t1rA,48503
matplotlib/markers.py,sha256=yILg87-gXelLwbu2o1xHNdCikXVHG50frJQJ6ywY8OY,34345
matplotlib/mathtext.py,sha256=vXlAVF9T7xA7-uRHnBu4T6LBZhr97a6obWEAr-X-mA4,121307
matplotlib/mlab.py,sha256=mhkOI6_Wgw0aaaqPDuCE53tHLNxl8tj_ajgoCZbRUcY,124056
matplotlib/mpl-data/fonts/afm/cmex10.afm,sha256=blR3ERmrVBV5XKkAnDCj4NMeYVgzH7cXtJ3u59u9GuE,12070
matplotlib/mpl-data/fonts/afm/cmmi10.afm,sha256=5qwEOpedEo76bDUahyuuF1q0cD84tRrX-VQ4p3MlfBo,10416
matplotlib/mpl-data/fonts/afm/cmr10.afm,sha256=WDvgC_D3UkGJg9u-J0U6RaT02lF4oz3lQxHtg1r3lYw,10101
matplotlib/mpl-data/fonts/afm/cmsy10.afm,sha256=AbmzvCVWBceHRfmRfeJ9E6xzOQTFLk0U1zDfpf3_MaM,8295
matplotlib/mpl-data/fonts/afm/cmtt10.afm,sha256=4ji7_mTpeWMa93o_UHBWPKCnqsBfhJJNllat1lJArP4,6501
matplotlib/mpl-data/fonts/afm/pagd8a.afm,sha256=jjFrigwkTpYLqa26cpzZvKQNBo-PuF4bmDVqaM4pMWw,17183
matplotlib/mpl-data/fonts/afm/pagdo8a.afm,sha256=sgNQdeYyx8J-itGw9h31y95aMBiTCRvmNSPTXwwS7xg,17255
matplotlib/mpl-data/fonts/afm/pagk8a.afm,sha256=ZUtfHPloNqcvGMHMxaKDSlshhOcjwheUx143RwpGdIU,17241
matplotlib/mpl-data/fonts/afm/pagko8a.afm,sha256=Yj1wBg6Jsqqz1KBfhRoJ3ACR-CMQol8Fj_ZM5NZ1gDk,17346
matplotlib/mpl-data/fonts/afm/pbkd8a.afm,sha256=Zl5o6J_di9Y5j2EpHtjew-_sfg7-WoeVmO9PzOYSTUc,15157
matplotlib/mpl-data/fonts/afm/pbkdi8a.afm,sha256=JAOno930iTyfZILMf11vWtiaTgrJcPpP6FRTRhEMMD4,15278
matplotlib/mpl-data/fonts/afm/pbkl8a.afm,sha256=UJqJjOJ6xQDgDBLX157mKpohIJFVmHM-N6x2-DiGv14,15000
matplotlib/mpl-data/fonts/afm/pbkli8a.afm,sha256=AWislZ2hDbs0ox_qOWREugsbS8_8lpL48LPMR40qpi0,15181
matplotlib/mpl-data/fonts/afm/pcrb8a.afm,sha256=6j1TS2Uc7DWSc-8l42TGDc1u0Fg8JspeWfxFayjUwi8,15352
matplotlib/mpl-data/fonts/afm/pcrbo8a.afm,sha256=smg3mjl9QaBDtQIt06ko5GvaxLsO9QtTvYANuE5hfG0,15422
matplotlib/mpl-data/fonts/afm/pcrr8a.afm,sha256=7nxFr0Ehz4E5KG_zSE5SZOhxRH8MyfnCbw-7x5wu7tw,15339
matplotlib/mpl-data/fonts/afm/pcrro8a.afm,sha256=NKEz7XtdFkh9cA8MvY-S3UOZlV2Y_J3tMEWFFxj7QSg,15443
matplotlib/mpl-data/fonts/afm/phvb8a.afm,sha256=NAx4M4HjL7vANCJbc-tk04Vkol-T0oaXeQ3T2h-XUvM,17155
matplotlib/mpl-data/fonts/afm/phvb8an.afm,sha256=8e_myD-AQkNF7q9XNLb2m76_lX2TUr3a5wog_LIE1sk,17086
matplotlib/mpl-data/fonts/afm/phvbo8a.afm,sha256=8fkBRmJ-SWY2YrBg8fFyjJyrJp8daQ6JPO6LvhM8xPI,17230
matplotlib/mpl-data/fonts/afm/phvbo8an.afm,sha256=aeVRvV4r15BBvxuRJ0MG8ZHuH2HViuIiCYkvuapmkmM,17195
matplotlib/mpl-data/fonts/afm/phvl8a.afm,sha256=IyMYM-bgl-gI6rG0EuZZ2OLzlxJfGeSh8xqsh0t-eJQ,15627
matplotlib/mpl-data/fonts/afm/phvlo8a.afm,sha256=s12C-eNnIDHJ_UVbuiprjxBjCiHIbS3Y8ORTC-qTpuI,15729
matplotlib/mpl-data/fonts/afm/phvr8a.afm,sha256=Kt8KaRidts89EBIK29X2JomDUEDxvroeaJz_RNTi6r4,17839
matplotlib/mpl-data/fonts/afm/phvr8an.afm,sha256=lL5fAHTRwODl-sB5mH7IfsD1tnnea4yRUK-_Ca2bQHM,17781
matplotlib/mpl-data/fonts/afm/phvro8a.afm,sha256=3KqK3eejiR4hIFBUynuSX_4lMdE2V2T58xOF8lX-fwc,17919
matplotlib/mpl-data/fonts/afm/phvro8an.afm,sha256=Vx9rRf3YfasMY7tz-njSxz67xHKk-fNkN7yBi0X2IP0,17877
matplotlib/mpl-data/fonts/afm/pncb8a.afm,sha256=aoXepTcDQtQa_mspflMJkEFKefzXHoyjz6ioJVI0YNc,16028
matplotlib/mpl-data/fonts/afm/pncbi8a.afm,sha256=pCWW1MYgy0EmvwaYsaYJaAI_LfrsKmDANHu7Pk0RaiU,17496
matplotlib/mpl-data/fonts/afm/pncr8a.afm,sha256=0CIB2BLe9r-6_Wl5ObRTTf98UOrezmGQ8ZOuBX5kLks,16665
matplotlib/mpl-data/fonts/afm/pncri8a.afm,sha256=5R-pLZOnaHNG8pjV6MP3Ai-d2OTQYR_cYCb5zQhzfSU,16920
matplotlib/mpl-data/fonts/afm/pplb8a.afm,sha256=3EzUbNnXr5Ft5eFLY00W9oWu59rHORgDXUuJaOoKN58,15662
matplotlib/mpl-data/fonts/afm/pplbi8a.afm,sha256=X_9tVspvrcMer3OS8qvdwjFFqpAXYZneyCL2NHA902g,15810
matplotlib/mpl-data/fonts/afm/pplr8a.afm,sha256=ijMb497FDJ9nVdVMb21F7W3-cu9sb_9nF0oriFpSn8k,15752
matplotlib/mpl-data/fonts/afm/pplri8a.afm,sha256=8KITbarcUUMi_hdoRLLmNHtlqs0TtOSKqtPFft7X5nY,15733
matplotlib/mpl-data/fonts/afm/psyr.afm,sha256=Iyt8ajE4B2Tm34oBj2pKtctIf9kPfq05suQefq8p3Ro,9644
matplotlib/mpl-data/fonts/afm/ptmb8a.afm,sha256=bL1fA1NC4_nW14Zrnxz4nHlXJb4dzELJPvodqKnYeMg,17983
matplotlib/mpl-data/fonts/afm/ptmbi8a.afm,sha256=-_Ui6XlKaFTHEnkoS_-1GtIr5VtGa3gFQ2ezLOYHs08,18070
matplotlib/mpl-data/fonts/afm/ptmr8a.afm,sha256=IEcsWcmzJyjCwkgsw4o6hIMmzlyXUglJat9s1PZNnEU,17942
matplotlib/mpl-data/fonts/afm/ptmri8a.afm,sha256=49fQMg5fIGguZ7rgc_2styMK55Pv5bPTs7wCzqpcGpk,18068
matplotlib/mpl-data/fonts/afm/putb8a.afm,sha256=qMaHTdpkrNL-m4DWhjpxJCSmgYkCv1qIzLlFfM0rl40,21532
matplotlib/mpl-data/fonts/afm/putbi8a.afm,sha256=g7AVJyiTxeMpNk_1cSfmYgM09uNUfPlZyWGv3D1vcAk,21931
matplotlib/mpl-data/fonts/afm/putr8a.afm,sha256=XYmNC5GQgSVAZKTIYdYeNksE6znNm9GF_0SmQlriqx0,22148
matplotlib/mpl-data/fonts/afm/putri8a.afm,sha256=i7fVe-iLyLtQxCfAa4IxdxH-ufcHmMk7hbCGG5TxAY4,21891
matplotlib/mpl-data/fonts/afm/pzcmi8a.afm,sha256=wyuoIWEZOcoXrSl1tPzLkEahik7kGi91JJj-tkFRG4A,16250
matplotlib/mpl-data/fonts/afm/pzdr.afm,sha256=MyjLAnzKYRdQBfof1W3k_hf30MvqOkqL__G22mQ5xww,9467
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Bold.afm,sha256=sIDDI-B82VZ3C0mI_mHFITCZ7PVn37AIYMv1CrHX4sE,15333
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-BoldOblique.afm,sha256=zg61QobD3YU9UBfCXmvmhBNaFKno-xj8sY0b2RpgfLw,15399
matplotlib/mpl-data/fonts/pdfcorefonts/Courier-Oblique.afm,sha256=vRQm5j1sTUN4hicT1PcVZ9P9DTTUHhEzfPXqUUzVZhE,15441
matplotlib/mpl-data/fonts/pdfcorefonts/Courier.afm,sha256=Mdcq2teZEBJrIqVXnsnhee7oZnTs6-P8_292kWGTrw4,15335
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Bold.afm,sha256=i2l4gcjuYXoXf28uK7yIVwuf0rnw6J7PwPVQeHj5iPw,69269
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-BoldOblique.afm,sha256=Um5O6qK11DXLt8uj_0IoWkc84TKqHK3bObSKUswQqvY,69365
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica-Oblique.afm,sha256=hVYDg2b52kqtbVeCzmiv25bW1yYdpkZS-LXlGREN2Rs,74392
matplotlib/mpl-data/fonts/pdfcorefonts/Helvetica.afm,sha256=23cvKDD7bQAJB3kdjSahJSTZaUOppznlIO6FXGslyW8,74292
matplotlib/mpl-data/fonts/pdfcorefonts/Symbol.afm,sha256=P5UaoXr4y0qh4SiMa5uqijDT6ZDr2-jPmj1ayry593E,9740
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Bold.afm,sha256=cQTmr2LFPwKQE_sGQageMcmFicjye16mKJslsJLHQyE,64251
matplotlib/mpl-data/fonts/pdfcorefonts/Times-BoldItalic.afm,sha256=pzWOdycm6RqocBWgAVY5Jq0z3Fp7LuqWgLNMx4q6OFw,59642
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Italic.afm,sha256=bK5puSMpGT_YUILwyJrXoxjfj7XJOdfv5TQ_iKsJRzw,66328
matplotlib/mpl-data/fonts/pdfcorefonts/Times-Roman.afm,sha256=hhNrUnpazuDDKD1WpraPxqPWCYLrO7D7bMVOg-zI13o,60460
matplotlib/mpl-data/fonts/pdfcorefonts/ZapfDingbats.afm,sha256=ZuOmt9GcKofjdOq8kqhPhtAIhOwkL2rTJTmZxAjFakA,9527
matplotlib/mpl-data/fonts/pdfcorefonts/readme.txt,sha256=MRv8ppSITYYAb7lt5EOw9DWWNZIblfxsFhu5TQE7cpI,828
matplotlib/mpl-data/fonts/ttf/DejaVuSans-Bold.ttf,sha256=sYS4njwQdfIva3FXW2_CDUlys8_TsjMiym_Vltyu8Wc,704128
matplotlib/mpl-data/fonts/ttf/DejaVuSans-BoldOblique.ttf,sha256=bt8CgxYBhq9FHL7nHnuEXy5Mq_Jku5ks5mjIPCVGXm8,641720
matplotlib/mpl-data/fonts/ttf/DejaVuSans-Oblique.ttf,sha256=zN90s1DxH9PdV3TeUOXmNGoaXaH1t9X7g1kGZel6UhM,633840
matplotlib/mpl-data/fonts/ttf/DejaVuSans.ttf,sha256=P99pyr8GBJ6nCgC1kZNA4s4ebQKwzDxLRPtoAb0eDSI,756072
matplotlib/mpl-data/fonts/ttf/DejaVuSansDisplay.ttf,sha256=ggmdz7paqGjN_CdFGYlSX-MpL3N_s8ngMozpzvWWUvY,25712
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Bold.ttf,sha256=uq2ppRcv4giGJRr_BDP8OEYZEtXa8HKH577lZiCo2pY,331536
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-BoldOblique.ttf,sha256=ppCBwVx2yCfgonpaf1x0thNchDSZlVSV_6jCDTqYKIs,253116
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono-Oblique.ttf,sha256=KAUoE_enCfyJ9S0ZLcmV708P3Fw9e3OknWhJsZFtDNA,251472
matplotlib/mpl-data/fonts/ttf/DejaVuSansMono.ttf,sha256=YC7Ia4lIz82VZIL-ZPlMNshndwFJ7y95HUYT9EO87LM,340240
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Bold.ttf,sha256=w3U_Lta8Zz8VhG3EWt2-s7nIcvMvsY_VOiHxvvHtdnY,355692
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-BoldItalic.ttf,sha256=2T7-x6nS6CZ2jRou6VuVhw4V4pWZqE80hK8d4c7C4YE,347064
matplotlib/mpl-data/fonts/ttf/DejaVuSerif-Italic.ttf,sha256=PnmU-8VPoQzjNSpC1Uj63X2crbacsRCbydlg9trFfwQ,345612
matplotlib/mpl-data/fonts/ttf/DejaVuSerif.ttf,sha256=EHJElW6ZYrnpb6zNxVGCXgrgiYrhNzcTPhuSGi_TX_o,379740
matplotlib/mpl-data/fonts/ttf/DejaVuSerifDisplay.ttf,sha256=KRTzLkfHd8J75Wd6-ufbTeefnkXeb8kJfZlJwjwU99U,14300
matplotlib/mpl-data/fonts/ttf/LICENSE_DEJAVU,sha256=11k43sCY8G8Kw8AIUwZdlPAgvhw8Yu8dwpdboVtNmw4,4816
matplotlib/mpl-data/fonts/ttf/LICENSE_STIX,sha256=cxFOZdp1AxNhXR6XxCzf5iJpNcu-APm-geOHhD-s0h8,5475
matplotlib/mpl-data/fonts/ttf/STIXGeneral.ttf,sha256=FnN4Ax4t3cYhbWeBnJJg6aBv_ExHjk4jy5im_USxg8I,448228
matplotlib/mpl-data/fonts/ttf/STIXGeneralBol.ttf,sha256=6FM9xwg_o0a9oZM9YOpKg7Z9CUW86vGzVB-CtKDixqA,237360
matplotlib/mpl-data/fonts/ttf/STIXGeneralBolIta.ttf,sha256=mHiP1LpI37sr0CbA4gokeosGxzcoeWKLemuw1bsJc2w,181152
matplotlib/mpl-data/fonts/ttf/STIXGeneralItalic.ttf,sha256=bPyzM9IrfDxiO9_UAXTxTIXD1nMcphZsHtyAFA6uhSc,175040
matplotlib/mpl-data/fonts/ttf/STIXNonUni.ttf,sha256=Ulb34CEzWsSFTRgPDovxmJZOwvyCAXYnbhaqvGU3u1c,59108
matplotlib/mpl-data/fonts/ttf/STIXNonUniBol.ttf,sha256=XRBqW3jR_8MBdFU0ObhiV7-kXwiBIMs7QVClHcT5tgs,30512
matplotlib/mpl-data/fonts/ttf/STIXNonUniBolIta.ttf,sha256=pb22DnbDf2yQqizotc3wBDqFGC_g27YcCGJivH9-Le8,41272
matplotlib/mpl-data/fonts/ttf/STIXNonUniIta.ttf,sha256=BMr9pWiBv2YIZdq04X4c3CgL6NPLUPrl64aV1N4w9Ug,46752
matplotlib/mpl-data/fonts/ttf/STIXSizFiveSymReg.ttf,sha256=wYuH1gYUpCuusqItRH5kf9p_s6mUD-9X3L5RvRtKSxs,13656
matplotlib/mpl-data/fonts/ttf/STIXSizFourSymBol.ttf,sha256=yNdvjUoSmsZCULmD7SVq9HabndG9P4dPhboL1JpAf0s,12228
matplotlib/mpl-data/fonts/ttf/STIXSizFourSymReg.ttf,sha256=-9xVMYL4_1rcO8FiCKrCfR4PaSmKtA42ddLGqwtei1w,15972
matplotlib/mpl-data/fonts/ttf/STIXSizOneSymBol.ttf,sha256=cYexyo8rZcdqMlpa9fNF5a2IoXLUTZuIvh0JD1Qp0i4,12556
matplotlib/mpl-data/fonts/ttf/STIXSizOneSymReg.ttf,sha256=0lbHzpndzJmO8S42mlkhsz5NbvJLQCaH5Mcc7QZRDzc,19760
matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymBol.ttf,sha256=3eBc-VtYbhQU3BnxiypfO6eAzEu8BdDvtIJSFbkS2oY,12192
matplotlib/mpl-data/fonts/ttf/STIXSizThreeSymReg.ttf,sha256=XFSKCptbESM8uxHtUFSAV2cybwxhSjd8dWVByq6f3w0,15836
matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymBol.ttf,sha256=MUCYHrA0ZqFiSE_PjIGlJZgMuv79aUgQqE7Dtu3kuo0,12116
matplotlib/mpl-data/fonts/ttf/STIXSizTwoSymReg.ttf,sha256=_sdxDuEwBDtADpu9CyIXQxV7sIqA2TZVBCUiUjq5UCk,15704
matplotlib/mpl-data/fonts/ttf/cmb10.ttf,sha256=B0SXtQxD6ldZcYFZH5iT04_BKofpUQT1ZX_CSB9hojo,25680
matplotlib/mpl-data/fonts/ttf/cmex10.ttf,sha256=ryjwwXByOsd2pxv6WVrKCemNFa5cPVTOGa_VYZyWqQU,21092
matplotlib/mpl-data/fonts/ttf/cmmi10.ttf,sha256=MJKWW4gR_WpnZXmWZIRRgfwd0TMLk3-RWAjEhdMWI00,32560
matplotlib/mpl-data/fonts/ttf/cmr10.ttf,sha256=Tdl2GwWMAJ25shRfVe5mF9CTwnPdPWxbPkP_YRD6m_Y,26348
matplotlib/mpl-data/fonts/ttf/cmss10.ttf,sha256=ffkag9BbLkcexjjLC0NaNgo8eSsJ_EKn2mfpHy55EVo,20376
matplotlib/mpl-data/fonts/ttf/cmsy10.ttf,sha256=uyJu2TLz8QDNDlL15JEu5VO0G2nnv9uNOFTbDrZgUjI,29396
matplotlib/mpl-data/fonts/ttf/cmtt10.ttf,sha256=YhHwmuk1mZka_alwwkZp2tGnfiU9kVYk-_IS9wLwcdc,28136
matplotlib/mpl-data/fonts/ttf/local.conf,sha256=wcg11V6oGPKn4c43Z_bGGo9TCyvjMWq9msuzMmdtRTo,988
matplotlib/mpl-data/images/back.gif,sha256=sdkxFRAh-Mgs44DTvruO5OxcI3Av9CS1g5MqMA_DDkQ,608
matplotlib/mpl-data/images/back.pdf,sha256=ZR7CJo_dAeCM-KlaGvskgtHQyRtrPIolc8REOmcoqJk,1623
matplotlib/mpl-data/images/back.png,sha256=E4dGf4Gnz1xJ1v2tMygHV0YNQgShreDeVApaMb-74mU,380
matplotlib/mpl-data/images/back.svg,sha256=yRdMiKsa-awUm2x_JE_rEV20rNTa7FInbFBEoMo-6ik,1512
matplotlib/mpl-data/images/back_large.gif,sha256=tqCtecrxNrPuDCUj7FGs8UXWftljKcwgp5cSBBhXwiQ,799
matplotlib/mpl-data/images/back_large.png,sha256=9A6hUSQeszhYONE4ZuH3kvOItM0JfDVu6tkfromCbsQ,620
matplotlib/mpl-data/images/filesave.gif,sha256=wAyNwOPd9c-EIPwcUAlqHSfLmxq167nhDVppOWPy9UA,723
matplotlib/mpl-data/images/filesave.pdf,sha256=P1EPPV2g50WTt8UaX-6kFoTZM1xVqo6S2H6FJ6Zd1ec,1734
matplotlib/mpl-data/images/filesave.png,sha256=b7ctucrM_F2mG-DycTedG_a_y4pHkx3F-zM7l18GLhk,458
matplotlib/mpl-data/images/filesave.svg,sha256=oxPVbLS9Pzelz71C1GCJWB34DZ0sx_pUVPRHBrCZrGs,2029
matplotlib/mpl-data/images/filesave_large.gif,sha256=IXrenlwu3wwO8WTRvxHt_q62NF6ZWyqk3jZhm6GE-G8,1498
matplotlib/mpl-data/images/filesave_large.png,sha256=LNbRD5KZ3Kf7nbp-stx_a1_6XfGBSWUfDdpgmnzoRvk,720
matplotlib/mpl-data/images/forward.gif,sha256=VNL9R-dECOX7wUAYPtU_DWn5hwi3SwLR17DhmBvUIxE,590
matplotlib/mpl-data/images/forward.pdf,sha256=KIqIL4YId43LkcOxV_TT5uvz1SP8k5iUNUeJmAElMV8,1630
matplotlib/mpl-data/images/forward.png,sha256=pKbLepgGiGeyY2TCBl8svjvm7Z4CS3iysFxcq4GR-wk,357
matplotlib/mpl-data/images/forward.svg,sha256=NnQDOenfjsn-o0aJMUfErrP320Zcx9XHZkLh0cjMHsk,1531
matplotlib/mpl-data/images/forward_large.gif,sha256=H6Jbcc7qJwHJAE294YqI5Bm-5irofX40cKRvYdrG_Ig,786
matplotlib/mpl-data/images/forward_large.png,sha256=36h7m7DZDHql6kkdpNPckyi2LKCe_xhhyavWARz_2kQ,593
matplotlib/mpl-data/images/hand.gif,sha256=3lRfmAqQU7A2t1YXXsB9IbwzK7FaRh-IZO84D5-xCrw,1267
matplotlib/mpl-data/images/hand.pdf,sha256=hspwkNY915KPD7AMWnVQs7LFPOtlcj0VUiLu76dMabQ,4172
matplotlib/mpl-data/images/hand.png,sha256=2cchRETGKa0hYNKUxnJABwkyYXEBPqJy_VqSPlT0W2Q,979
matplotlib/mpl-data/images/hand.svg,sha256=tsVIES_nINrAbH4FqdsCGOx0SVE37vcofSYBhnnaOP0,4888
matplotlib/mpl-data/images/hand_large.gif,sha256=H5IHmVTvOqHQb9FZ_7g7AlPt9gv-zRq0L5_Q9B7OuvU,973
matplotlib/mpl-data/images/help.pdf,sha256=CeE978IMi0YWznWKjIT1R8IrP4KhZ0S7usPUvreSgcA,1813
matplotlib/mpl-data/images/help.png,sha256=s4pQrqaQ0py8I7vc9hv3BI3DO_tky-7YBMpaHuBDCBY,472
matplotlib/mpl-data/images/help.ppm,sha256=mVPvgwcddzCM-nGZd8Lnl_CorzDkRIXQE17b7qo8vlU,1741
matplotlib/mpl-data/images/help.svg,sha256=KXabvQhqIWen_t2SvZuddFYa3S0iI3W8cAKm3s1fI8Q,1870
matplotlib/mpl-data/images/help_large.png,sha256=1IwEyWfGRgnoCWM-r9CJHEogTJVD5n1c8LXTK4AJ4RE,747
matplotlib/mpl-data/images/help_large.ppm,sha256=MiCSKp1Su88FXOi9MTtkQDA2srwbX3w5navi6cneAi4,6925
matplotlib/mpl-data/images/home.gif,sha256=NKuFM7tTtFngdfsOpJ4AxYTL8PYS5GWKAoiJjBMwLlU,666
matplotlib/mpl-data/images/home.pdf,sha256=e0e0pI-XRtPmvUCW2VTKL1DeYu1pvPmUUeRSgEbWmik,1737
matplotlib/mpl-data/images/home.png,sha256=IcFdAAUa6_A0qt8IO3I8p4rpXpQgAlJ8ndBECCh7C1w,468
matplotlib/mpl-data/images/home.svg,sha256=n_AosjJVXET3McymFuHgXbUr5vMLdXK2PDgghX8Cch4,1891
matplotlib/mpl-data/images/home_large.gif,sha256=k86PJCgED46sCFkOlUYHA0s5U7OjRsc517bpAtU2JSw,1422
matplotlib/mpl-data/images/home_large.png,sha256=uxS2O3tWOHh1iau7CaVV4ermIJaZ007ibm5Z3i8kXYg,790
matplotlib/mpl-data/images/matplotlib.pdf,sha256=BkSUf-2xoij-eXfpV2t7y1JFKG1zD1gtV6aAg3Xi_wE,22852
matplotlib/mpl-data/images/matplotlib.png,sha256=w8KLRYVa-voUZXa41hgJauQuoois23f3NFfdc72pUYY,1283
matplotlib/mpl-data/images/matplotlib.ppm,sha256=voTyfqUGvirNbkrsJGMwJT0z0g_KFWAnO8Hn8J4XXh8,1741
matplotlib/mpl-data/images/matplotlib.svg,sha256=QiTIcqlQwGaVPtHsEk-vtmJk1wxwZSvijhqBe_b9VCI,62087
matplotlib/mpl-data/images/matplotlib_large.png,sha256=ElRoue9grUqkZXJngk-nvh4GKfpvJ4gE69WryjCbX5U,3088
matplotlib/mpl-data/images/move.gif,sha256=FN52MptH4FZiwmV2rQgYCO2FvO3m5LtqYv8jk6Xbeyk,679
matplotlib/mpl-data/images/move.pdf,sha256=CXk3PGK9WL5t-5J-G2X5Tl-nb6lcErTBS5oUj2St6aU,1867
matplotlib/mpl-data/images/move.png,sha256=TmjR41IzSzxGbhiUcV64X0zx2BjrxbWH3cSKvnG2vzc,481
matplotlib/mpl-data/images/move.svg,sha256=_ZKpcwGD6DMTkZlbyj0nQbT8Ygt5vslEZ0OqXaXGd4E,2509
matplotlib/mpl-data/images/move_large.gif,sha256=RMIAr-G9OOY7vWC04oN6qv5TAHJxhQGhLsw_bNsvWbg,951
matplotlib/mpl-data/images/move_large.png,sha256=Skjz2nW_RTA5s_0g88gdq2hrVbm6DOcfYW4Fu42Fn9U,767
matplotlib/mpl-data/images/qt4_editor_options.pdf,sha256=2qu6GVyBrJvVHxychQoJUiXPYxBylbH2j90QnytXs_w,1568
matplotlib/mpl-data/images/qt4_editor_options.png,sha256=EryQjQ5hh2dwmIxtzCFiMN1U6Tnd11p1CDfgH5ZHjNM,380
matplotlib/mpl-data/images/qt4_editor_options.svg,sha256=E00YoX7u4NrxMHm_L1TM8PDJ88bX5qRdCrO-Uj59CEA,1244
matplotlib/mpl-data/images/qt4_editor_options_large.png,sha256=-Pd-9Vh5aIr3PZa8O6Ge_BLo41kiEnpmkdDj8a11JkY,619
matplotlib/mpl-data/images/subplots.gif,sha256=QfhmUdcrko08-WtrzCJUjrVFDTvUZCJEXpARNtzEwkg,691
matplotlib/mpl-data/images/subplots.pdf,sha256=Q0syPMI5EvtgM-CE-YXKOkL9eFUAZnj_X2Ihoj6R4p4,1714
matplotlib/mpl-data/images/subplots.png,sha256=MUfCItq3_yzb9yRieGOglpn0Y74h8IA7m5i70B63iRc,445
matplotlib/mpl-data/images/subplots.svg,sha256=8acBogXIr9OWGn1iD6mUkgahdFZgDybww385zLCLoIs,2130
matplotlib/mpl-data/images/subplots_large.gif,sha256=Ff3ERmtVAaGP9i1QGUNnIIKac6LGuSW2Qf4DrockZSI,1350
matplotlib/mpl-data/images/subplots_large.png,sha256=Edu9SwVMQEXJZ5ogU5cyW7VLcwXJdhdf-EtxxmxdkIs,662
matplotlib/mpl-data/images/zoom_to_rect.gif,sha256=mTX6h9fh2W9zmvUYqeibK0TZ7qIMKOB1nAXMpD_jDys,696
matplotlib/mpl-data/images/zoom_to_rect.pdf,sha256=SEvPc24gfZRpl-dHv7nx8KkxPyU66Kq4zgQTvGFm9KA,1609
matplotlib/mpl-data/images/zoom_to_rect.png,sha256=aNz3QZBrIgxu9E-fFfaQweCVNitGuDUFoC27e5NU2L4,530
matplotlib/mpl-data/images/zoom_to_rect.svg,sha256=1vRxr3cl8QTwTuRlQzD1jxu0fXZofTJ2PMgG97E7Bco,1479
matplotlib/mpl-data/images/zoom_to_rect_large.gif,sha256=nx5LUpTAH6ZynM3ZfZDS-wR87jbMUsUnyQ27NGkV0_c,1456
matplotlib/mpl-data/images/zoom_to_rect_large.png,sha256=V6pkxmm6VwFExdg_PEJWdK37HB7k3cE_corLa7RbUMk,1016
matplotlib/mpl-data/matplotlibrc,sha256=-CfOXSRZ7WaduxxmjJjX-_PLETPwuzleTpGhTLd7ylU,33061
matplotlib/mpl-data/sample_data/Minduka_Present_Blue_Pack.png,sha256=XnKGiCanpDKalQ5anvo5NZSAeDP7fyflzQAaivuc0IE,13634
matplotlib/mpl-data/sample_data/None_vs_nearest-pdf.png,sha256=5CPvcG3SDNfOXx39CMKHCNS9JKZ-fmOUwIfpppNXsQ0,106228
matplotlib/mpl-data/sample_data/README.txt,sha256=ABz19VBKfGewdY39QInG9Qccgn1MTYV3bT5Ph7TCy2Y,128
matplotlib/mpl-data/sample_data/aapl.npz,sha256=GssVYka_EccteiXbNRJJ5GsuqU7G8F597qX7srYXZsw,107503
matplotlib/mpl-data/sample_data/ada.png,sha256=X1hjJK1_1Nc8DN-EEhey3G7Sq8jBwQDKNSl4cCAE0uY,308313
matplotlib/mpl-data/sample_data/axes_grid/bivariate_normal.npy,sha256=DpWZ9udAh6ospYqneEa27D6EkRgORFwHosacZXVu98U,1880
matplotlib/mpl-data/sample_data/ct.raw.gz,sha256=LDvvgH-mycRQF2D29-w5MW94ZI0opvwKUoFI8euNpMk,256159
matplotlib/mpl-data/sample_data/data_x_x2_x3.csv,sha256=A0SU3buOUGhT-NI_6LQ6p70fFSIU3iLFdgzvzrKR6SE,132
matplotlib/mpl-data/sample_data/demodata.csv,sha256=MRybziqnyrqMCH9qG7Mr6BwcohIhftVG5dejXV2AX2M,659
matplotlib/mpl-data/sample_data/eeg.dat,sha256=KGVjFt8ABKz7p6XZirNfcxSTOpGGNuyA8JYErRKLRBc,25600
matplotlib/mpl-data/sample_data/embedding_in_wx3.xrc,sha256=cUqVw5vDHNSZoaO4J0ebZUf5SrJP36775abs7R9Bclg,2186
matplotlib/mpl-data/sample_data/goog.npz,sha256=QAkXzzDmtmT3sNqT18dFhg06qQCNqLfxYNLdEuajGLE,22845
matplotlib/mpl-data/sample_data/grace_hopper.jpg,sha256=qMptc0dlcDsJcoq0f-WfRz2Trjln_CTHwCiMPHrbcTA,61306
matplotlib/mpl-data/sample_data/grace_hopper.png,sha256=MCf0ju2kpC40srQ0xw4HEyOoKhLL4khP3jHfU9_dR7s,628280
matplotlib/mpl-data/sample_data/jacksboro_fault_dem.npz,sha256=1JP1CjPoKkQgSUxU0fyhU50Xe9wnqxkLxf5ukvYvtjc,174061
matplotlib/mpl-data/sample_data/logo2.png,sha256=ITxkJUsan2oqXgJDy6DJvwJ4aHviKeWGnxPkTjXUt7A,33541
matplotlib/mpl-data/sample_data/membrane.dat,sha256=q3lbQpIBpbtXXGNw1eFwkN_PwxdDGqk4L46IE2b0M1c,48000
matplotlib/mpl-data/sample_data/msft.csv,sha256=GArKb0O3DgKZRsKdJf6lX3rMSf-PCekIiBoLNdgF7Mk,3211
matplotlib/mpl-data/sample_data/percent_bachelors_degrees_women_usa.csv,sha256=TzoqamsV_N3d3lW7SKmj14zZVX4FOOg9jJcsC5U9pbA,5681
matplotlib/mpl-data/sample_data/s1045.ima.gz,sha256=MrQk1k9it-ccsk0p_VOTitVmTWCAVaZ6srKvQ2n4uJ4,33229
matplotlib/mpl-data/stylelib/Solarize_Light2.mplstyle,sha256=PECeO60wwJe2sSDvxapBJRuKGek0qLcoaN8qOX6tgNQ,1255
matplotlib/mpl-data/stylelib/_classic_test.mplstyle,sha256=XnegNNz-4tr8vnTgI1IakyHYPryuInJD1GidF9a8n6E,25458
matplotlib/mpl-data/stylelib/bmh.mplstyle,sha256=-KbhaI859BITHIoyUZIfpQDjfckgLAlDAS_ydKsm6mc,712
matplotlib/mpl-data/stylelib/classic.mplstyle,sha256=brJE6RZ114bTIVwl7yBm2sd6s0TyRrUq9t-qi--Ih20,25639
matplotlib/mpl-data/stylelib/dark_background.mplstyle,sha256=-EGmoFm_35Zk7oRp29UalT56HsOSuJbYMeQGdAATnz4,477
matplotlib/mpl-data/stylelib/fast.mplstyle,sha256=yTa2YEIIP9xi5V_G0p2vSlxghuhNwjRi9gPECMxyRiM,288
matplotlib/mpl-data/stylelib/fivethirtyeight.mplstyle,sha256=WNUmAFuBPcqQPVgt6AS1ldy8Be2XO01N-1YQL__Q6ZY,832
matplotlib/mpl-data/stylelib/ggplot.mplstyle,sha256=xhjLwr8hiikEXKy8APMy0Bmvtz1g0WnG84gX7e9lArs,957
matplotlib/mpl-data/stylelib/grayscale.mplstyle,sha256=KCLg-pXpns9cnKDXKN2WH6mV41OH-6cbT-5zKQotSdw,526
matplotlib/mpl-data/stylelib/seaborn-bright.mplstyle,sha256=pDqn3-NUyVLvlfkYs8n8HzNZvmslVMChkeH-HtZuJIc,144
matplotlib/mpl-data/stylelib/seaborn-colorblind.mplstyle,sha256=eCSzFj5_2vR6n5qu1rHE46wvSVGZcdVqz85ov40ZsH8,148
matplotlib/mpl-data/stylelib/seaborn-dark-palette.mplstyle,sha256=p5ABKNQHRG7bk4HXqMQrRBjDlxGAo3RCXHdQmP7g-Ng,142
matplotlib/mpl-data/stylelib/seaborn-dark.mplstyle,sha256=I4xQ75vE5_9X4k0cNDiqhhnF3OcrZ2xlPX8Ll7OCkoE,667
matplotlib/mpl-data/stylelib/seaborn-darkgrid.mplstyle,sha256=2bXOSzS5gmPzRBrRmzVWyhg_7ZaBRQ6t_-O-cRuyZoA,670
matplotlib/mpl-data/stylelib/seaborn-deep.mplstyle,sha256=44dLcXjjRgR-6yaopgGRInaVgz3jk8VJVQTbBIcxRB0,142
matplotlib/mpl-data/stylelib/seaborn-muted.mplstyle,sha256=T4o3jvqKD_ImXDkp66XFOV_xrBVFUolJU34JDFk1Xkk,143
matplotlib/mpl-data/stylelib/seaborn-notebook.mplstyle,sha256=PcvZQbYrDdducrNlavBPmQ1g2minio_9GkUUFRdgtoM,382
matplotlib/mpl-data/stylelib/seaborn-paper.mplstyle,sha256=n0mboUp2C4Usq2j6tNWcu4TZ_YT4-kKgrYO0t-rz1yw,393
matplotlib/mpl-data/stylelib/seaborn-pastel.mplstyle,sha256=8nV8qRpbUrnFZeyE6VcQ1oRuZPLil2W74M2U37DNMOE,144
matplotlib/mpl-data/stylelib/seaborn-poster.mplstyle,sha256=dUaKqTE4MRfUq2rWVXbbou7kzD7Z9PE9Ko8aXLza8JA,403
matplotlib/mpl-data/stylelib/seaborn-talk.mplstyle,sha256=7FnBaBEdWBbncTm6_ER-EQVa_bZgU7dncgez-ez8R74,403
matplotlib/mpl-data/stylelib/seaborn-ticks.mplstyle,sha256=CITZmZFUFp40MK2Oz8tI8a7WRoCizQU9Z4J172YWfWw,665
matplotlib/mpl-data/stylelib/seaborn-white.mplstyle,sha256=WjJ6LEU6rlCwUugToawciAbKP9oERFHr9rfFlUrdTx0,665
matplotlib/mpl-data/stylelib/seaborn-whitegrid.mplstyle,sha256=ec4BjsNzmOvHptcJ3mdPxULF3S1_U1EUocuqfIpw-Nk,664
matplotlib/mpl-data/stylelib/seaborn.mplstyle,sha256=_Xu6qXKzi4b3GymCOB1b1-ykKTQ8xhDliZ8ezHGTiAs,1130
matplotlib/mpl-data/stylelib/tableau-colorblind10.mplstyle,sha256=BsirZVd1LmPWT4tBIz6loZPjZcInoQrIGfC7rvzqmJw,190
matplotlib/offsetbox.py,sha256=KZdfpLd6rYRjmRAboaRBuKag8lDUCzMEeykgmp4irx8,55449
matplotlib/patches.py,sha256=wUAZISlGrUWm_bxDh88ryGoWkOXrJZttyanOnZWdDQ4,151034
matplotlib/path.py,sha256=Ys7EYJOyCTAQm0fROCvHdgg2Kxp5AZwkJhG42hw3QZc,37139
matplotlib/patheffects.py,sha256=l7huZDCPG8RS3LLq67EztQg7s64zngoUN1R60zxpp5k,14139
matplotlib/projections/__init__.py,sha256=-_LPKYaHOdfgxYdCtPs-nDJvjQ8ZTXIuAcC0v_1_zFY,2874
matplotlib/projections/__pycache__/__init__.cpython-36.pyc,,
matplotlib/projections/__pycache__/geo.cpython-36.pyc,,
matplotlib/projections/__pycache__/polar.cpython-36.pyc,,
matplotlib/projections/geo.py,sha256=EI7YSw7UbZTsb31dCTAH0aJRXQct-cOnm45alvAj3Cc,18909
matplotlib/projections/polar.py,sha256=6X8t4p4LAx8h7O7WJ-T5SDhbKaNsA8X6dcXIl4z4wgM,55006
matplotlib/pylab.py,sha256=lNqCpuoTGfjprzltmvpQ3DYthdj_FJf6PRFfeEv3HhY,10331
matplotlib/pyplot.py,sha256=5AwtjpRSBFNyRyKTVrWIWSl_EPVPyPQ6RpZdOidI1nI,110436
matplotlib/quiver.py,sha256=9eP6xfF6pOOQpQ3zxbvedZewUYE236OS9gwRNZhPvws,45910
matplotlib/rcsetup.py,sha256=aFy_3zYioc0oCEmRdvqIGlCRbSA6hj7BKKTO-k2__Gw,58203
matplotlib/sankey.py,sha256=tMlZvy0CmG55hpDVY6Dqk461x6vvSxvhDmkIXJG_qDo,38734
matplotlib/scale.py,sha256=l9r0dmYGeMCLkqcrcJRIBFj6tJq6Znvc9ZoRTynHLoI,19164
matplotlib/sphinxext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
matplotlib/sphinxext/__pycache__/__init__.cpython-36.pyc,,
matplotlib/sphinxext/__pycache__/mathmpl.cpython-36.pyc,,
matplotlib/sphinxext/__pycache__/plot_directive.cpython-36.pyc,,
matplotlib/sphinxext/mathmpl.py,sha256=sdWYQ5aBB9hGiEY5E3j9By-DaFENDNHtTHxLMRQXrWc,3919
matplotlib/sphinxext/plot_directive.py,sha256=uMBuVvlmjuLUr6r1iDm42L1yN7A1r_knUXrcfxPtTBU,27082
matplotlib/sphinxext/tests/__init__.py,sha256=gTnBimTaMh3t3WC49SvyeSH0rsbWxQgjAsr5H8HRDig,23
matplotlib/sphinxext/tests/__pycache__/__init__.cpython-36.pyc,,
matplotlib/sphinxext/tests/__pycache__/conftest.cpython-36.pyc,,
matplotlib/sphinxext/tests/__pycache__/test_tinypages.cpython-36.pyc,,
matplotlib/sphinxext/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
matplotlib/sphinxext/tests/test_tinypages.py,sha256=uU72lyon36bSL2xF_90zfkblEolK_Cj5VGrfEpv0_wU,2058
matplotlib/sphinxext/tests/tinypages/__pycache__/conf.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/__pycache__/range4.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/__pycache__/range6.cpython-36.pyc,,
matplotlib/sphinxext/tests/tinypages/_static/README.txt,sha256=1nnoizmUuHn5GKx8RL6MwJPlkyGmu_KHhYIMTDSWUNM,303
matplotlib/sphinxext/tests/tinypages/conf.py,sha256=0_a4wyqPA9oaOFpLLpSEzkZI-hwtyRbqLWBx9nf0sLA,8432
matplotlib/sphinxext/tests/tinypages/index.rst,sha256=kLSy7c3SoIAVsKOFkbzB4zFVzk3HW3d_rJHlHcNGBAg,446
matplotlib/sphinxext/tests/tinypages/range4.py,sha256=fs2krzi9sY9ysmJRQCdGs_Jh1L9vDXDrNse7c0aX_Rw,81
matplotlib/sphinxext/tests/tinypages/range6.py,sha256=a2EaHrNwXz4GJqhRuc7luqRpt7sqLKhTKeid9Drt2QQ,281
matplotlib/sphinxext/tests/tinypages/some_plots.rst,sha256=C9rwV9UVlhFvxm8VqV6PoAP1AQ8Kk0LGZI9va4joif0,2156
matplotlib/spines.py,sha256=dctV_-aWkJuc52EgZof2aPjcelWVKOXttz66sZyrPkk,21217
matplotlib/stackplot.py,sha256=4YbKAU349muVPKAkNSdt9r0tG_hcHj5iCy1Dp9otWGA,3977
matplotlib/streamplot.py,sha256=_ARll6zMPC0-eLPBW-svHOs4wcG8W2tTABIcFWVwN3I,21933
matplotlib/style/__init__.py,sha256=EExOAUAq3u_rscUwkfKtZoEgLA5npmltCrYZOP9ftjw,67
matplotlib/style/__pycache__/__init__.cpython-36.pyc,,
matplotlib/style/__pycache__/core.cpython-36.pyc,,
matplotlib/style/core.py,sha256=8LwOz9KtNuYLOzYiUyeUJNN_w-y_LsAGRhTN_SvCftA,7901
matplotlib/table.py,sha256=fp3kcp88hOthPbc02LwZbnvbQ3__vne_LpOk2Bw_e2o,21917
matplotlib/testing/__init__.py,sha256=z-NqrY_YBuiQGl4QVqRYAGOZcyGnSIcI16XjAbmIsjM,1498
matplotlib/testing/__pycache__/__init__.cpython-36.pyc,,
matplotlib/testing/__pycache__/compare.cpython-36.pyc,,
matplotlib/testing/__pycache__/conftest.cpython-36.pyc,,
matplotlib/testing/__pycache__/decorators.cpython-36.pyc,,
matplotlib/testing/__pycache__/determinism.cpython-36.pyc,,
matplotlib/testing/__pycache__/disable_internet.cpython-36.pyc,,
matplotlib/testing/__pycache__/exceptions.cpython-36.pyc,,
matplotlib/testing/compare.py,sha256=vklvca9q772lqZ7wsaHxI2n3MY65MCHnKTjFKV48R6s,17552
matplotlib/testing/conftest.py,sha256=NYJUijf2Rm25TwLysGfCNFKPEtD_zK9sWG5lcfUZS58,3253
matplotlib/testing/decorators.py,sha256=zOMJfeD75zD8OA5MrxsRCTOLiDGlZaMERNGw5LyBrN8,17704
matplotlib/testing/determinism.py,sha256=4GADMpjbO3127YtWNK2bNB2HTUWUkyEEQB397LGCpB0,4389
matplotlib/testing/disable_internet.py,sha256=YE5szJX8tNHyK8j90uPUOJO76MmUHKrXFqRnj_xGyu0,4747
matplotlib/testing/exceptions.py,sha256=72QmjiHG7DwxSvlJf8mei-hRit5AH3NKh0-osBo4YbY,138
matplotlib/testing/jpl_units/Duration.py,sha256=FDR6rn_Yvc3XlfLuJe4KI6ALMSJCytZGZX5xIQz-yuc,6875
matplotlib/testing/jpl_units/Epoch.py,sha256=1Htkm5XtuToVC5otGBWgsOlhPVH4FcV8EA9CkNHNwI8,7859
matplotlib/testing/jpl_units/EpochConverter.py,sha256=NbxO32pLLg7hMuiiB2QiAuiipGAfT6q2tSSf9Xys26o,5424
matplotlib/testing/jpl_units/StrConverter.py,sha256=g2EEkhg4ZdT8PSB-4MjPDNRbilRp7Wi72mTnsv7Ty7g,5295
matplotlib/testing/jpl_units/UnitDbl.py,sha256=b_4G6NaHJUl4Xd-NE1BZzombIHyOGO0i6Vb0MQGBeuw,9725
matplotlib/testing/jpl_units/UnitDblConverter.py,sha256=9AIYgnnR78G0E0D3rI20IgTawiCYC2bA_izCmy5csNo,5419
matplotlib/testing/jpl_units/UnitDblFormatter.py,sha256=bE8adtYRXG5gzQObrzR-ROLwkFkpru7GDjjUIlRh7Ss,1416
matplotlib/testing/jpl_units/__init__.py,sha256=fWVROJbodccLPtdnFzhV8ItE1Dl1uinQc9HcYz4hmpE,3062
matplotlib/testing/jpl_units/__pycache__/Duration.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/Epoch.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/EpochConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/StrConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDbl.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDblConverter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/UnitDblFormatter.cpython-36.pyc,,
matplotlib/testing/jpl_units/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tests/__init__.py,sha256=g3AQvrbsKOFFnkrCoQzlqr1cXCDV0LvGsPnvPhNOrYs,463
matplotlib/tests/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tests/__pycache__/conftest.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_afm.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_agg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_animation.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_arrow_patches.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_artist.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_axes.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_bases.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_nbagg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_pdf.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_pgf.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_ps.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_qt4.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_qt5.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_svg.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backend_tools.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_backends_interactive.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_basic.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_bbox_tight.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_category.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_cbook.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_collections.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_colorbar.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_colors.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_compare_images.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_constrainedlayout.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_container.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_contour.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_cycles.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_dates.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_dviread.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_figure.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_font_manager.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_gridspec.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_image.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_legend.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_lines.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_marker.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_mathtext.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_matplotlib.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_mlab.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_offsetbox.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_patches.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_path.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_patheffects.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_pickle.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_png.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_preprocess_data.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_pyplot.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_quiver.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_rcparams.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_sankey.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_scale.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_simplification.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_skew.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_spines.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_streamplot.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_style.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_subplots.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_table.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_texmanager.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_text.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_ticker.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_tightlayout.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_transforms.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_triangulation.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_ttconv.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_type1font.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_units.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_usetex.cpython-36.pyc,,
matplotlib/tests/__pycache__/test_widgets.cpython-36.pyc,,
matplotlib/tests/cmr10.pfb,sha256=_c7eh5QBjfXytY8JBfsgorQY7Y9ntz7hJEWFXfvlsb4,35752
matplotlib/tests/conftest.py,sha256=QtpdWPUoXL_9F8WIytDc3--h0nPjbo8PToig7svIT1Y,258
matplotlib/tests/mpltest.ttf,sha256=Jwb2O5KRVk_2CMqnhL0igeI3iGQCY3eChyS16N589zE,2264
matplotlib/tests/test_afm.py,sha256=64Qvm_dkFOh88o8oBouswePl1kgSUE_37jrRN0__jng,2218
matplotlib/tests/test_agg.py,sha256=aorEpO-NshLjutQQV-yF1ZikLIqss9fF7ouXsjG7o3s,7299
matplotlib/tests/test_animation.py,sha256=rMZwOf3WEVT7xGYDYc2xkx6XjW8_mQ0kFL3wouIaZ0g,8267
matplotlib/tests/test_arrow_patches.py,sha256=dfRDXMnKZt0FBY5JAtjKx8qbCnHCzAQoFhuEy3EzhPg,5950
matplotlib/tests/test_artist.py,sha256=PjaIk-F4jN0QEhPm3ZinWIMOB04LesHnMe0xmLmlm4g,9203
matplotlib/tests/test_axes.py,sha256=QQbwWolkRT1jytrYIp1tzsJN1tbpqF7tL0M4mIAa148,189994
matplotlib/tests/test_backend_bases.py,sha256=_jib8QrQa98qAarlE01aIk7Hkfs1gQbcVDIMGzsVr2U,3585
matplotlib/tests/test_backend_nbagg.py,sha256=TvfgH4gOzFxGRvNohjn1c9vBqEKL4OI6IWwBnZdBWgg,1066
matplotlib/tests/test_backend_pdf.py,sha256=6D7Yah7exx8xFwGsfm5ZcjYcxnRtZs0D8l3z1UZ7qPI,7925
matplotlib/tests/test_backend_pgf.py,sha256=GEYeAku09gn3erePaZMHRWVUBAFwuzdAXmrnFl5a4mc,8157
matplotlib/tests/test_backend_ps.py,sha256=KX7Xak4Yb2UMD6KEiM1-UJTtA5MwbSYhRg3l5gyofWI,4505
matplotlib/tests/test_backend_qt4.py,sha256=gNbJ4BFnwhZ-LGZ9HlXMYtQPsgPAHoYSCs8TDCFmPUA,4199
matplotlib/tests/test_backend_qt5.py,sha256=Pf4B8pNrh8qeh-vKeVeip6_fr2UhCy1fH6HDxLC0Edg,6574
matplotlib/tests/test_backend_svg.py,sha256=fbp1hAcPoxetMsfRdZ50ZttMX-vY1eqSiBfFtCXKB80,5439
matplotlib/tests/test_backend_tools.py,sha256=C-B7NCkyWsQ5KzQEnI5Be16DsAHHZJU9P5v9--wsF-o,501
matplotlib/tests/test_backends_interactive.py,sha256=AuioLNYNgZZ4Q5HJWh58CLsX5CRW0ZJBQrPYZn6DE68,5012
matplotlib/tests/test_basic.py,sha256=jexF5eJw4gwb0hYMx6_u8wJa3hgtwKclag1Lhws_f7U,730
matplotlib/tests/test_bbox_tight.py,sha256=ZcHz4qXTRhiVOiGQgSaqQgA7IjsRh84qDYOoVe82IH4,3280
matplotlib/tests/test_category.py,sha256=HJ1oBEZD0A6wHUnrz49kymv93UX0fkkdFCclIBrI09w,10303
matplotlib/tests/test_cbook.py,sha256=Tm8U0CgUpWthx7xQ_DicpslnOHlXnG9Wr0bO9E-YJAM,15647
matplotlib/tests/test_collections.py,sha256=no4WHkfoo6s6Mlc0hrrd_ssl9bpapGGTMJ7WsvCh8Hg,22100
matplotlib/tests/test_colorbar.py,sha256=YbP1IZp9HftMxpUa_yvVKFJexKCGabXQkZSaPam8qvM,17071
matplotlib/tests/test_colors.py,sha256=JrmZ9kvwN21bUwwa2YMAhrlURLRYDGDAA0yMlOHN5nc,25629
matplotlib/tests/test_compare_images.py,sha256=n3Uoukid0GcjyQpd6ZrqIY9u3RLNE2XAPWwtcHIsqto,3155
matplotlib/tests/test_constrainedlayout.py,sha256=easxoL_jBZe5ChE7eQcjz6aqx4LPxQde9FkRsWoOg5I,13261
matplotlib/tests/test_container.py,sha256=ijZ2QDT_GLdMwKW393K4WU6R7A5jJNaH1uhLRDwoXXI,550
matplotlib/tests/test_contour.py,sha256=2Yl9crBjJegsAEco-mo6oa31TfoJkupLbqlwl9IFkWU,12909
matplotlib/tests/test_cycles.py,sha256=75QI7uIkh4b5ckGd0B7irJuqhIivYx8CmxRAciWRj1g,7490
matplotlib/tests/test_dates.py,sha256=SDos5lnmY_dNNIT-hjVZ3e5OpmSePAOIWFJoA_9tDdM,25085
matplotlib/tests/test_dviread.py,sha256=kTk9Qv6q9Kk3fJcDAEWm35HF-sKsP6Ybec6N8jEHasE,2342
matplotlib/tests/test_figure.py,sha256=LVk5dyf6H8V8Ot2bIQXAnscaqLegeDfud1wtLXusJXo,14204
matplotlib/tests/test_font_manager.py,sha256=8dVBlum-bY9gmy_YVXWERbWtbGNQllwzCyCNxffGCFI,3271
matplotlib/tests/test_gridspec.py,sha256=zahj5Rd4pB0xtAc_3KX7fQWyBys0P-IQk-Cq0cs8VgY,626
matplotlib/tests/test_image.py,sha256=uAMkqy06wYZeJASFKIt-hdWCG310dL-AFp6cnLdnjlM,28263
matplotlib/tests/test_legend.py,sha256=2Q6Qn2C72wRqZvY6Mt9v_udD0bVARDTbWcqBZD1-oCk,19782
matplotlib/tests/test_lines.py,sha256=4n7pdODOXqLvX1zing9SuXXxChPDe7nEr42gaiIG8d8,6221
matplotlib/tests/test_marker.py,sha256=fhHHW93wCl5KbrZRL2iEVziv2BLBZU6zSt66hgsI5jY,739
matplotlib/tests/test_mathtext.py,sha256=3G0W1S2J5QOHs0EfQiAdP5M114sh81sJtDxITrF1gzs,12696
matplotlib/tests/test_matplotlib.py,sha256=DIBqISzUIYanSxNWJL9n2oob1dRLOOAr6TIz2BTWK1I,706
matplotlib/tests/test_mlab.py,sha256=gpd4pJ8fIQNCXdwJV1C1__EKBMdnkvQLVgyFdgNgrZc,96981
matplotlib/tests/test_offsetbox.py,sha256=u_VCL-lWvQ66IGTzuotzRxq23ySzcB86Gjvmeib-S3w,4198
matplotlib/tests/test_patches.py,sha256=IbeCN3mVunvkukneSZKjVbZKGJg0h2oqmOzrI400HQ0,16218
matplotlib/tests/test_path.py,sha256=EwvnGhKmdMFl5w4PC2KWwKVm2qKLuRksM70kb-MUVTU,7164
matplotlib/tests/test_patheffects.py,sha256=Sl90AY4wri37bEvRuS3QEthE_GV5i5drh0Yk2oGJjc0,5372
matplotlib/tests/test_pickle.py,sha256=38XMrLSsH7RgNVesFLB68UAD6HZS7Eylbs3DktWmkBw,5533
matplotlib/tests/test_png.py,sha256=3TieTdSRvSL9mS7bhxpozw1CClQxKYunK83TLPU4bnA,1786
matplotlib/tests/test_preprocess_data.py,sha256=oJuYg8kpqgLrA1wYEUzMLULRoKM3IRf9_ejRzPzM0AU,15990
matplotlib/tests/test_pyplot.py,sha256=wOld704X_pAZVNvnL3WD2KUHliRPtAoqLBh0-OrdeGg,974
matplotlib/tests/test_quiver.py,sha256=hAKbP4S6npdpIfwjw6efhMr6En2A-0fJAjLOiMofxWk,5789
matplotlib/tests/test_rcparams.py,sha256=bmDAFAz3g0ZLHLVqVU9F4POt-X5HbAqpLNfBp4m81N8,20498
matplotlib/tests/test_rcparams.rc,sha256=zwPbYzajd7FTIYURvpwTBAn8i060Do3OVDGZ2xHZeLw,74
matplotlib/tests/test_sankey.py,sha256=ZZBtNqIsFcJLoZTCKSM2xaUfrtMjoSica3Mi-FtMysw,162
matplotlib/tests/test_scale.py,sha256=8V-tt5E79R-P_Zz6e7iH_7KCHU6e1Ql2SLnQdpt7BmQ,4100
matplotlib/tests/test_simplification.py,sha256=c1GSkJwiGxksnUj5WgYbvsr3yDcmbEKHmB_12W_jp7s,10895
matplotlib/tests/test_skew.py,sha256=zOhGb5V-9A531ZpmHlqFsdTL9xfdj2-RL3N7OQJbV20,7058
matplotlib/tests/test_spines.py,sha256=1cN5KequShVG83DggeUxt5QWE9uIkfmyONaoVNJOojw,2326
matplotlib/tests/test_streamplot.py,sha256=F-ilyMfXm47d-XCz6L06GE8KQ-bEHbhKLqIS0EVKv0I,3497
matplotlib/tests/test_style.py,sha256=JAcu_9wDS_Gvtisce9Z6rHrO-SHZVjyPeBDYfKgDZ1c,5333
matplotlib/tests/test_subplots.py,sha256=yMzfFiUpZBcBt2FpLFySHzxmoj2B4jevjLviFWkPouo,5551
matplotlib/tests/test_table.py,sha256=Qoe4Sm-yyog-NOHASdBOjjGQrFQOo2amm01KAgP5MJo,5906
matplotlib/tests/test_texmanager.py,sha256=zCtJ3JnZNfP2AQNy7q2LQAgaflSe7S5htJkJNylQSGE,459
matplotlib/tests/test_text.py,sha256=Vg5HlZU9ob584X5AZAxQuIm6PFj_GO9xnxIBKZU7O2c,15435
matplotlib/tests/test_ticker.py,sha256=OPuJl75xgr_AZ8IDUuyi90PgAXBVT91EnORB2h31mUQ,32802
matplotlib/tests/test_tightlayout.py,sha256=7w_LPOY1EM0Z6i7GPt1--w4z0wmf-5M6rvpd0wauqKg,10016
matplotlib/tests/test_transforms.py,sha256=Y3FWBzEiBBydD5X4id-bgpzRSFCBxyZFtPTbto2KUv0,24713
matplotlib/tests/test_triangulation.py,sha256=XXCtxqhP6jHFe2mbIbB0-KNsdkzxhUDI1V9bDz1A_Eg,44838
matplotlib/tests/test_ttconv.py,sha256=xilgvzZpTpHSnumaUlHvQ_zdIQ7U7xBD1Aflx34I-xU,641
matplotlib/tests/test_type1font.py,sha256=C0pCPBGOv49SR2xxDOq6LSXAEH_ZNvIWvr_jG-23Gmc,2097
matplotlib/tests/test_units.py,sha256=yUnl2ds8QGdJdZWME2YHysQbSr9DG4aEkgIIstMZyMc,5196
matplotlib/tests/test_usetex.py,sha256=9ANPkY6aKfyY0_DopPLivplxlT__v5y5wHX2_8-Z4Z8,918
matplotlib/tests/test_utf32_be_rcparams.rc,sha256=K66jcKehDKcG1yTXJCOSsmp1iteU9Kvsd_eobV5wNW4,56
matplotlib/tests/test_widgets.py,sha256=7sBak0W8XT-NhZ9zNEZC8i_PA6LJIkBgiE_gB42h2cE,16787
matplotlib/texmanager.py,sha256=KBU8HQhgj0DNOTwpCZhH_pJ1IqyfEnF1IgUOBDUpRqQ,17284
matplotlib/text.py,sha256=-q9B-fS_GmCoAEHhEDbY7m9fidyFLjSn3600KAcROkE,81684
matplotlib/textpath.py,sha256=FLg0qVzTVf9_CFbE3O39Pw7-4hiAYiWoqz_dwKMmHYY,17917
matplotlib/ticker.py,sha256=Pb6Eyaiz6ncDoDjE7Vew8h63i1s-x9hNR-aRHP9-fhs,88149
matplotlib/tight_bbox.py,sha256=Yf5X-HVlMe3AqwR5tUJQBH0LLZqOGoh2uP0oJnU6ErA,2463
matplotlib/tight_layout.py,sha256=iQt1J_nr40VPf3GCgqed_r_umsYE5lnvjWxLdh3-uPs,14562
matplotlib/transforms.py,sha256=2pDKJqB7Fnx2WLvWUKY8wG7ry9xk0xtVq9dDu32HvLc,99669
matplotlib/tri/__init__.py,sha256=XMaejh88uov7Neu7MuYMyaNQqaxg49nXaiJfvjifrRM,256
matplotlib/tri/__pycache__/__init__.cpython-36.pyc,,
matplotlib/tri/__pycache__/triangulation.cpython-36.pyc,,
matplotlib/tri/__pycache__/tricontour.cpython-36.pyc,,
matplotlib/tri/__pycache__/trifinder.cpython-36.pyc,,
matplotlib/tri/__pycache__/triinterpolate.cpython-36.pyc,,
matplotlib/tri/__pycache__/tripcolor.cpython-36.pyc,,
matplotlib/tri/__pycache__/triplot.cpython-36.pyc,,
matplotlib/tri/__pycache__/trirefine.cpython-36.pyc,,
matplotlib/tri/__pycache__/tritools.cpython-36.pyc,,
matplotlib/tri/triangulation.py,sha256=YM3AIH44SK4LOJiLPB4Wo-DQ1d2q75SdUQ_W2kaxrO4,8427
matplotlib/tri/tricontour.py,sha256=Uz3bHK7xw3fHnjFstOlurnz9-btm6dfa0lKE7Vt_P0k,9375
matplotlib/tri/trifinder.py,sha256=_S-whwBCe5m9byOzcdAXFJXs0gAIXqy9rVGkXKiM14U,3505
matplotlib/tri/triinterpolate.py,sha256=uWh1PPiaN0nMM30txiEVoAxtFZXvX0hxvPnWzKP3xoc,64969
matplotlib/tri/tripcolor.py,sha256=DwBFSsZ_jBrFKIPrYetMXNqy_i9GS9-BQUDjPig2WOw,5326
matplotlib/tri/triplot.py,sha256=aZ9O_VVLH0AOne31u11ltLlyVyhqKtyzec7WH3b3pkk,2857
matplotlib/tri/trirefine.py,sha256=DZS_gihMxkUMzuxAijKnEDo4Po_ahIHY7-uGnjUY1Eg,14142
matplotlib/tri/tritools.py,sha256=dC_OcwrFN3gunCe3SgHjQTH_dHBCncM1Fex0bQ_b1Jg,12498
matplotlib/ttconv.cpython-36m-x86_64-linux-gnu.so,sha256=tnAP4SNjDwMwdCZFJjzoDYLYa0wf3LAMZmKd9M_QmS4,83888
matplotlib/type1font.py,sha256=aBak-e5VKpZpH_LyYqNUyAX4vgRisZq4sfeEcV45-j4,12173
matplotlib/units.py,sha256=URBYLJTwt1n-w7A0LcznVlMCMMnLaJFEVrhEHTt0Yv8,6665
matplotlib/widgets.py,sha256=vAYlyIJUze6G7CW9CUmY1LePR5L5XFJrPqvVIYycWzY,94072
mpl_toolkits/axes_grid/__init__.py,sha256=d0j8ET68OmR22G59uzWO4BQ3Jv2kCmJC15nZRAf227M,559
mpl_toolkits/axes_grid/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/anchored_artists.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/angle_helper.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axes_size.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axis_artist.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axisline_style.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/axislines.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/clip_path.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/colorbar.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/floating_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/grid_finder.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/inset_locator.cpython-36.pyc,,
mpl_toolkits/axes_grid/__pycache__/parasite_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid/anchored_artists.py,sha256=_F6-9iacZidb5JpJ8jCOZ9PdiZaR5qpfBjf-3VjTzNc,291
mpl_toolkits/axes_grid/angle_helper.py,sha256=Tb4Mb_NGkUdkisebe2dqfBdFmUZiSmGyUnftiSeSIls,51
mpl_toolkits/axes_grid/axes_divider.py,sha256=Q1NvDXXtKVuX7iUoKFFRw11Wg1eHEGt3-qDWG7DOVxg,269
mpl_toolkits/axes_grid/axes_grid.py,sha256=t2Fc8fM-_qINumuDxctOEYhMI3M1ZfqEVc3th-cnz5g,152
mpl_toolkits/axes_grid/axes_rgb.py,sha256=nKv0IWpKHN2NW5eZqx_rbaZqqMWvYw9GK94gIAVEmE0,301
mpl_toolkits/axes_grid/axes_size.py,sha256=v4Nhxe7DVp1FkKX03DqJJ1aevDanDvgKT9r0ouDzTxw,48
mpl_toolkits/axes_grid/axis_artist.py,sha256=zUlJFUHueDsMtzLi_mK2_Wf-nSBQgiTsMOFpo_SngZ0,50
mpl_toolkits/axes_grid/axisline_style.py,sha256=lNVHXkFWhSWPXOOfF-wlVkDPzmzuStJyJzF-NS5Wf_g,53
mpl_toolkits/axes_grid/axislines.py,sha256=kVyhb6laiImmuNE53QTQh3kgxz0sO1mcSMpnqIdjylA,48
mpl_toolkits/axes_grid/clip_path.py,sha256=s-d36hUiy9I9BSr9wpxjgoAACCQrczHjw072JvArNvE,48
mpl_toolkits/axes_grid/colorbar.py,sha256=DckRf6tadLeTNjx-Zk1u3agnSGZgizDjd0Dxw1-GRdw,171
mpl_toolkits/axes_grid/floating_axes.py,sha256=i35OfV1ZMF-DkLo4bKmzFZP6LgCwXfdDKxYlGqjyKOM,52
mpl_toolkits/axes_grid/grid_finder.py,sha256=Y221c-Jh_AFd3Oolzvr0B1Zrz9MoXPatUABQdLsFdpw,50
mpl_toolkits/axes_grid/grid_helper_curvelinear.py,sha256=nRl_B-755X7UpVqqdwkqc_IwiTmM48z3eOMHuvJT5HI,62
mpl_toolkits/axes_grid/inset_locator.py,sha256=qqXlT8JWokP0kV-8NHknZDINtK-jbXfkutH_1tcRe_o,216
mpl_toolkits/axes_grid/parasite_axes.py,sha256=kCFtaRTd0O8ePL78GOYvhEKqn8rE9bk61v0kVgMb6UE,469
mpl_toolkits/axes_grid1/__init__.py,sha256=SEWPa2ggZnKkFVX4yaJOPN7KgyV_T-cyjr8UjIjjhPs,272
mpl_toolkits/axes_grid1/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/anchored_artists.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/axes_size.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/colorbar.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/inset_locator.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/mpl_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid1/__pycache__/parasite_axes.cpython-36.pyc,,
mpl_toolkits/axes_grid1/anchored_artists.py,sha256=SPXVgw8CLMGTyPScN3Q2WHeWJbhtQo52Fo3DaLJ8yrY,21162
mpl_toolkits/axes_grid1/axes_divider.py,sha256=RNaghesva1N6F4h6J-5Amy15LVqzQyW-FelkfQ_m9n8,29873
mpl_toolkits/axes_grid1/axes_grid.py,sha256=BOh_MSGrTK9_-rAFuY5UST84PRDLObCj2tOg71wYjXI,27243
mpl_toolkits/axes_grid1/axes_rgb.py,sha256=UlErJuhcqtN0skRLckf_v-GvUtAWDTVW401wUzwXOxI,6603
mpl_toolkits/axes_grid1/axes_size.py,sha256=m4LSknVO9c6vcpT1bEZBKYUGkoJ7BOrPnLRTcLmnmFQ,8933
mpl_toolkits/axes_grid1/colorbar.py,sha256=BLNBORudFV18ShwQiiVdceUOTJESQZ4yefVu23yVex0,27428
mpl_toolkits/axes_grid1/inset_locator.py,sha256=EeLwbA6sUhCBFzPVA1MwCTGE8N1APdKZXq4xSiCXG1Y,23722
mpl_toolkits/axes_grid1/mpl_axes.py,sha256=nUXeFjye-NBR1SCXYx1qirV6QIYSB2e01AEeoWlzm7w,4680
mpl_toolkits/axes_grid1/parasite_axes.py,sha256=TjNMInmARhT4tZn2PBafcecuyammO3KBuCnUUYyhbdc,12908
mpl_toolkits/axisartist/__init__.py,sha256=2zsgjqTtP_NXv78MEaKabmfmkjA7yhy77pIcaR57YWs,748
mpl_toolkits/axisartist/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/angle_helper.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_divider.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_grid.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axes_rgb.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axis_artist.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axisline_style.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/axislines.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/clip_path.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/floating_axes.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/grid_finder.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/axisartist/__pycache__/parasite_axes.cpython-36.pyc,,
mpl_toolkits/axisartist/angle_helper.py,sha256=csNOzIHc3s1fvJm7XpuZy74kQY2oeC5przHcVSSFVLc,12932
mpl_toolkits/axisartist/axes_divider.py,sha256=FqYC72nAYkmU9oaawDb7TjMxb1NSjhbYocD1vxwCrvM,509
mpl_toolkits/axisartist/axes_grid.py,sha256=vfd_EXHuYQ7iIVK2FOm6inLhb7huZxtOSvFyOVW2GmU,610
mpl_toolkits/axisartist/axes_rgb.py,sha256=TpJCB8eA0wHZVXOxxfFoy1Tk_KFj68sZvo74doDeHYE,179
mpl_toolkits/axisartist/axis_artist.py,sha256=4IdmKz2zRHnRCerIO-lYnf7LiUphSE7fe_Hq5VapDms,44753
mpl_toolkits/axisartist/axisline_style.py,sha256=It8dzmdESmoAmwwEjOs-YjtBydKlNmb41vV6v8tZ1-s,5107
mpl_toolkits/axisartist/axislines.py,sha256=63q7XMODxvM3mwHCmBvtczaOgre-dGigNqavgrDZ844,22082
mpl_toolkits/axisartist/clip_path.py,sha256=_fxHB05pFazHxDmNRXN7xO5EfJaxFPHMwFfqwfAs2uA,3736
mpl_toolkits/axisartist/floating_axes.py,sha256=B3_1_qTFDSWIpfbRxMf9TXyoRzQmwMISdpqU46nC-Uc,16491
mpl_toolkits/axisartist/grid_finder.py,sha256=E_JrpMeAIUj9FZ6Q7_rd4cEYeDbb5TgjoOsJ_5YQIoc,11513
mpl_toolkits/axisartist/grid_helper_curvelinear.py,sha256=yH4--wkTm2C2FSSc_TQcvU-24wJlJLj18JphW5Kzz80,15491
mpl_toolkits/axisartist/parasite_axes.py,sha256=1sQwBEYuXHpaEeObb7cXh0I1xWroYtcvFiEmwrzqK3w,447
mpl_toolkits/mplot3d/__init__.py,sha256=V2iPIP9VyRhoJsFWnQf5AkfyI1GSSP9H6hICEe9edJo,27
mpl_toolkits/mplot3d/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/art3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/axes3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/axis3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/__pycache__/proj3d.cpython-36.pyc,,
mpl_toolkits/mplot3d/art3d.py,sha256=Juj1shJAObe_JDhF3Fm4qtMVPmvLMUYt2lKp8dakteQ,24879
mpl_toolkits/mplot3d/axes3d.py,sha256=EDZc-nuqbI6HNt7oioFRL36EKODH8HTA68EsElSW_II,103257
mpl_toolkits/mplot3d/axis3d.py,sha256=z8Q20DsMzFvA-jYWFsbbo-fFB7Yzegdlg7Wz1Ws5VPE,18574
mpl_toolkits/mplot3d/proj3d.py,sha256=JNb8VcfoAOwRJMLAOT6pdX2PDE7fCx5L48PGdIACzvU,4612
mpl_toolkits/tests/__init__.py,sha256=iPdasxJf0vpIi11tQ98OVSQgS0UaPUyOEGGfAryAhIA,381
mpl_toolkits/tests/__pycache__/__init__.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/conftest.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axes_grid.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axes_grid1.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_angle_helper.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_axis_artist.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_axislines.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_clip_path.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_floating_axes.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_grid_finder.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_axisartist_grid_helper_curvelinear.cpython-36.pyc,,
mpl_toolkits/tests/__pycache__/test_mplot3d.cpython-36.pyc,,
mpl_toolkits/tests/baseline_images/test_axes_grid/imagegrid_cbar_mode.png,sha256=yvo6erXXc3Z9aO0rrEezBooCc6KhAw7wKv4WngOQmFA,87393
mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows.png,sha256=XMZGgG7_9k96bKhI2G--XBVKpct5O5psbGH2Wvj5YA0,10784
mpl_toolkits/tests/baseline_images/test_axes_grid1/anchored_direction_arrows_many_args.png,sha256=fkPsdmhd4S1g-QxMb55M63iAgWmC2G4ytcLOT9tMAD0,11039
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.pdf,sha256=eW2CuM_T4d95dC-DU0PmmQD7gqRQIO0rcQpvp-zu1i4,25446
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.png,sha256=VfRfs6p4akgjGxxNm6Bu83Pg0v1KmU7WPu97_-kzNFc,48825
mpl_toolkits/tests/baseline_images/test_axes_grid1/divider_append_axes.svg,sha256=usfsa3y-s-N2KMOzsOZHTq-PZXgAPXsSM-lkxJ3ZUi0,172812
mpl_toolkits/tests/baseline_images/test_axes_grid1/fill_facecolor.png,sha256=Tkrylxebxm8SuWZjQK0qXSX8m9QsQU6kYm7L2dgt4yg,14845
mpl_toolkits/tests/baseline_images/test_axes_grid1/image_grid.png,sha256=EStruex2GqxBIGm49SXqrn8lO4-_XhsAnvs5CmtUawc,3872
mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_axes.png,sha256=RQmR39E6Vskvl7G4LInHibW9E1VK0QgCvI-hBlb-E2E,9928
mpl_toolkits/tests/baseline_images/test_axes_grid1/inset_locator.png,sha256=bQKKKUuoU_EZwZT_9FzzeVKsKwUUBOZV55g4vVUbnCU,9490
mpl_toolkits/tests/baseline_images/test_axes_grid1/inverted_zoomed_axes.png,sha256=rvglsLg8Kl9jE_JukTJ5B3EHozsIYJsaYA0JIOicZL8,25997
mpl_toolkits/tests/baseline_images/test_axes_grid1/twin_axes_empty_and_removed.png,sha256=0YzkFhxs4SBG_FEmnWB10bXIxl9aq7WJveQAqHm0JrQ,37701
mpl_toolkits/tests/baseline_images/test_axes_grid1/zoomed_axes.png,sha256=mUu8zJtz8FMb7h5l4Cfp3oBi9jaNR5OoyaDgwqpAZp4,25893
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist.png,sha256=qdlk9UPScCAN9RBOhoNqLmJvmkXt8pCuwuQtrz5E8Bs,10151
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_labelbase.png,sha256=An5lovtvAiNA1NZI-E8kOj6eYTruQMqwf3J7pXwdk4A,10598
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticklabels.png,sha256=7vuAKkIqcpgJrc2AF7oslf-E_sDfSlCoymyc87u4AWs,5696
mpl_toolkits/tests/baseline_images/test_axisartist_axis_artist/axis_artist_ticks.png,sha256=CkVtCWG13ViW0w2DsbzfXSvoFWHYaaqQYeEYpbKbOg8,5695
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/ParasiteAxesAuxTrans_meshplot.png,sha256=FOgl-Glmzhdp6V8mz4StofTsFXGysFkEcUeaWtmJDZs,34354
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/Subplot.png,sha256=tRpYCjR5zUkafA85DVmY3duTEouwCZq6jDwSF4UsBS8,26919
mpl_toolkits/tests/baseline_images/test_axisartist_axislines/SubplotZero.png,sha256=3kCrz7HQMYrK3iDgYgf8kyigxRtIGFBbcUzJPtiXh_E,28682
mpl_toolkits/tests/baseline_images/test_axisartist_clip_path/clip_path.png,sha256=BtMyb7ZawcgId9jl1_qW72lU_ZyxLN780uQ9bCLjbHA,25701
mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear3.png,sha256=4th7Y74_9YV6X25RqJW0Op9WDzGRCcxF1kfNogkgozE,52835
mpl_toolkits/tests/baseline_images/test_axisartist_floating_axes/curvelinear4.png,sha256=cYjrSiH6Mvor-VhmwNUgX7Le3_k1rurpd8L5vhTf16s,29374
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/axis_direction.png,sha256=3fue92dg-ntYI0XX0nB31IFpgRT2V3izqjrmLvEdYN4,40536
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/custom_transform.png,sha256=4cQhIFK1z8oPUVyvkHNZ_m-GCbikmUbTvkvYVGy6U4o,15118
mpl_toolkits/tests/baseline_images/test_axisartist_grid_helper_curvelinear/polar_box.png,sha256=wWaPM3I7_435SkVQqIggb8BHrWBMWrsSVyMZQQJ6fE4,62526
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_cla.png,sha256=htnP1CA8dd85KqdnOsHVlsehT90MUoQD8kFTyra0AuE,51409
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_labelpad.png,sha256=zrLsk8t7s970yaY3cqj6SOMbI6UY8Loe0Zbp0WqFtwQ,66817
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.pdf,sha256=zkfSOR2bJYC_X425qnXHMmGJPSlLSpFs53YB_R760Gw,6603
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png,sha256=SoyN30SsuvEECZyB_ReGP3ZKGZJazOp05dXa3YUn7Jc,47796
mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg,sha256=Kb_zdIzZG6JKnztMcmOUG4esPsuteljB_A2sxrhIA3Y,18046
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.pdf,sha256=YI5gzs8NK6fWo6JB0wf8xeZ-FrHlS3P8DSCccsLU4fE,7197
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.png,sha256=Qw909B4nDmV9DlMuo1DKk7y5ndjtvni5d_DcysmG9VA,100466
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d.svg,sha256=iU1Pk60SDC89km6bwz9Li9mXdNdZ_Vn15bkbAUG2iKo,30591
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_notshaded.png,sha256=p9nV0cr7ZqhmM5VRHYcByR_QWKh91b8jjt71nkrq3Bc,66289
mpl_toolkits/tests/baseline_images/test_mplot3d/bar3d_shaded.png,sha256=9RXKAlcPSrQYmhvqH9JMnlLaR62satjwyUq1joXcy6g,64595
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.pdf,sha256=CtzH5MJNMY_hZGAyp9py9PLI0a8kJ-jNnpQKQYtoQEE,25170
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.png,sha256=tii1IakS8MC_Vuwd95HhcyM0iq4zGN5DxgRFfB9mKu8,83161
mpl_toolkits/tests/baseline_images/test_mplot3d/contour3d.svg,sha256=3VjmHaN5hRXJ-HH9duS1M6nR8gkgBOCtb3zruWcSujU,67618
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.pdf,sha256=H7FyZjuiA12CzP8FDDnVNf42HxGfMlg_8rA57HA1sIo,52812
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.png,sha256=Jb-fhAcgogE8jn9DSsaqInUfWC7D_5Pf3QRf7XWAX2Q,42575
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d.svg,sha256=cz9TicdIGJ_8UdHTKQZ76n2rAv6Rx2EELPg8AyAEgMU,173077
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.pdf,sha256=raTRNOdfYPFJvZiM7BbRbdWRPElQe0sn6jsKH63TTyA,3950
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.png,sha256=dE8eHoj43eePB44F1nLM2RLj8iqw8rCYI3D0VD3gUg0,39694
mpl_toolkits/tests/baseline_images/test_mplot3d/contourf3d_fill.svg,sha256=BM-PcmZJ-8iyB4wUWQxcMuskmDXemrsX52LKPYEz850,11093
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.pdf,sha256=6pKrI8lUIPxRwi3ofm8DK8UqNeZsBYprXwX51vwFv1Y,4354
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.png,sha256=DQT-NruHCeG5LKpjG-dlLln3aCoPKhua5PQnHTafBGU,60217
mpl_toolkits/tests/baseline_images/test_mplot3d/lines3d.svg,sha256=c32m_X6tFYlY5PCVYJ0cPzvZDTbgRA-GIwzDUVTNKYA,12919
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.pdf,sha256=Y-C6q8kG4QdVsBpck1Qg90W87rSnbOzrDQP1v7qFZDI,53962
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.png,sha256=rGHpqTyXqt1PvCSvSrP7Dnd0uNeeEmPPgJwADxXdfM8,39763
mpl_toolkits/tests/baseline_images/test_mplot3d/mixedsubplot.svg,sha256=TQ9KQlKC0BU4lnayB2S8ArbsfAh87qKjFEh5WKjWbvk,286241
mpl_toolkits/tests/baseline_images/test_mplot3d/plot_3d_from_2d.png,sha256=AWos5EJWMerD0tgVZyvBofz-5hnCq6fhGHKmQi-izAg,56593
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.pdf,sha256=mJYYIj01eM9ouCPpoeWu-KDrXe3_o2um4_JddGAzWGg,2680
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.png,sha256=ePzSA-iFaQbmH603vw1jhs9vyIt45xXnbpIuUF3a1l8,52065
mpl_toolkits/tests/baseline_images/test_mplot3d/poly3dcollection_closed.svg,sha256=K-D9vp-jB8KFTilVotfIQvuhG3qTMT04XZ6LUIfpZ60,8594
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube.png,sha256=AJ0EoayvdBoywpOUWcxbMQ0oB7cTzcoWGgGyx2qgQMU,23182
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png,sha256=5Phz7BclSciZpg4SDu-eUQ-v_ikHbEqReQWCdeHywQk,16210
mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_lines_dists.png,sha256=XCd4hX2ckc5GCxcgenkRJ8MT7pX-3iMLylD2rCjNl-4,18898
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.pdf,sha256=T4nKDMCEi2zKpoq5FOsdyMQJd0igS9yBeyUL84a0oso,18589
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.png,sha256=PBllNI1kHf1rz-oPK507OwsPNE9DPwivXAVJM9DemBI,104755
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d.svg,sha256=_cwXN4aH-mQx5ADarZtXsY-vaeRFg4dPuVmP6S9NZbM,128623
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.pdf,sha256=DSFSucOBU22R3zmG8eKlWWtLy5Wb7L7wqZ1B7CMybKc,7405
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.png,sha256=98D3k5QIL7KugUwzqJhdLtp9dgDGgx8hGa9_u8cvX6o,37954
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_empty.svg,sha256=H_b_HtjccyLTNDjIqUek6DwQugfsyb88nVBepBzFdTo,7745
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.pdf,sha256=_VIBNh32vX_K9QrH-0o4z13FAE0JZQuOzOfkCTsSe7c,10939
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.png,sha256=67yp7-6f-vDiYTmCqMFfuIEGly5UHCCUOV84YJtLsX8,80392
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_masked.svg,sha256=JO8n2RUeQANeCpq_PmtyjmVPqMmuqvZQg7vk_3hsv8I,68796
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_middle.png,sha256=N4o26wMzfnyxndPbZ2VnsjIAiNYrFN9Aa40ficwO9AM,104735
mpl_toolkits/tests/baseline_images/test_mplot3d/quiver3d_pivot_tail.png,sha256=Ff_UrWxD-VIMQLN1uXy5u_Yd5e1P427YfGM05nvU2kE,104951
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.pdf,sha256=93umAGrsz8bYekxMETAlU67eCTY3tzycxpVmm2M7eEE,5847
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.png,sha256=MDaocusHz6Itinjm2j6fnDh-rl1fqVjnqM89nP8bwZs,43155
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d.svg,sha256=yc-PIx36-DdoCE1Vd8JRih45GoBR7eVtdi6wMZVZUXU,12494
mpl_toolkits/tests/baseline_images/test_mplot3d/scatter3d_color.png,sha256=Y7De9BIFLp0Ova4fk9IcXloNjiwmifTrFA1IfVJA3aE,41598
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.pdf,sha256=OSvMZiGJzmVdtv_e0XHIBw4bLSY7DLtWGDAYgh6vNuo,48096
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.png,sha256=Ok0UmO2DELze2yK8mRx0CifmRAgvjyS1IvERsBRvFlU,54712
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d.svg,sha256=CaqTIT-jdKSKfCqp8GC_QAc_3Dxwgr97uvDR-a4xdcw,270325
mpl_toolkits/tests/baseline_images/test_mplot3d/surface3d_shaded.png,sha256=tW1q8o6BAlqGD-ILqAXXTBxEt08fwIwEnK-L7n4fHFo,43405
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.pdf,sha256=CfWlocbvwHK48CADjhNjjXJ2eHsSvoNWWpz33RvkuQQ,13906
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.png,sha256=cuahU107LG85pT3kyHngOPV2GchTUwu1AkLYbb4UDd8,77741
mpl_toolkits/tests/baseline_images/test_mplot3d/text3d.svg,sha256=MmcVy3u0Syymf933rIsaUY8XOv6b9r6ScVXx33NFxBg,36357
mpl_toolkits/tests/baseline_images/test_mplot3d/tricontour.png,sha256=8IjYmJP6cBhnPGLz-WDyn7UUMYZ10Kz2MpjOFwDUVow,71328
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.pdf,sha256=OVbK3ausYMApR7_UJPx0pf3bt4A3CpZumhAVhzQMbs0,169155
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.png,sha256=nO0gJBIluLEX3mlxXY3C6bx-9Jf_xJyXAnTXKnqrIkQ,99103
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d.svg,sha256=PFUTTF34KiJcol6J5JmRjkaillzRWAgsbfxgEhpWxmA,103333
mpl_toolkits/tests/baseline_images/test_mplot3d/trisurf3d_shaded.png,sha256=45y5oF9MzbR07cP-T9CNF7yLWo8qUDoG3E91kc6jueE,94492
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png,sha256=4BUCO65oRVuxgOs3i06OFVNWi_UJLelUQzJ7WCzhdOQ,179128
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png,sha256=DMDM2KV84Z0TH7BlWJAuESle0NbFcCsmDF9Q0rPmP3w,65236
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png,sha256=ze6vMiuPmRj7FOX_1ltXMWwQNaRD5jl3drzXbdMKcFs,96319
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png,sha256=6X8fQ-dYqDlRt9oSoeYbic2mQb1DDlqPfAUpMbh2O9U,136235
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png,sha256=mG67qqJF-9TKYbacsbVknSnUOj8D9m0sYmy0imfwY2M,60940
mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png,sha256=OeInzIzGNXG490czp9McRMqEMcgiz0ABkUky7QlTwfE,122177
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.pdf,sha256=3gYea_CtpNg1UmurOhyWt3vHZSkLX1-trEY779yrxbc,36169
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.png,sha256=epmsR4rWGzh7prW3RL_t8ZcUEsphM5bc0t5j__iauOY,108371
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3d.svg,sha256=-hjWYF-WSE3aDuCSpHYztjeV_yktEqatDK2yZyn_rhE,85892
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerocstride.png,sha256=WaO_3NcaZPFzlui5SQYJ-TbUylHkSbieneCYPffNgAA,81117
mpl_toolkits/tests/baseline_images/test_mplot3d/wireframe3dzerorstride.png,sha256=y1JvfuVOBiNhJoJ2HdOXyBYBBkQm-oaPcoekfT-cMso,84284
mpl_toolkits/tests/conftest.py,sha256=Ph6QZKdfAnkPwU52StddC-uwtCHfANKX1dDXgtX122g,213
mpl_toolkits/tests/test_axes_grid.py,sha256=UCQFk5p-9sbTMCS6RKk7BfyCiTb9yRnsarH23eUem0o,1638
mpl_toolkits/tests/test_axes_grid1.py,sha256=mHdjXAd-3X1OEW0yS9iap1i2GUuvqtH4-UnnOICT_U4,15028
mpl_toolkits/tests/test_axisartist_angle_helper.py,sha256=2jLmTrH4fw3Xty2CwaBsRJISb8qxWWn8ALRa4HL2FQA,5702
mpl_toolkits/tests/test_axisartist_axis_artist.py,sha256=h8UXVxnt-fsfvjEOLxnyrwA4z0b7Lf-1UtvIh_SNaI4,2893
mpl_toolkits/tests/test_axisartist_axislines.py,sha256=nzxykaFzR1XaQ7KlJNf9VNDXoXRQY4BZKuH_iIyiCHk,2266
mpl_toolkits/tests/test_axisartist_clip_path.py,sha256=7K1Y-2DPbDdyvpAHq3XEDaXfsikw-u8v7olwaqwZ53o,1054
mpl_toolkits/tests/test_axisartist_floating_axes.py,sha256=bTaH-fTMJum4DAjH5h4t-hH3W5BNNw9LuiWPaC24j6Q,4165
mpl_toolkits/tests/test_axisartist_grid_finder.py,sha256=e65sLudWFIXeU08Sis3_SI1JEI6eq8YqKj-80F_Nohk,325
mpl_toolkits/tests/test_axisartist_grid_helper_curvelinear.py,sha256=TpV3ShQOPAk57KV-97dWfpULRws0zyAOQwn3JpejTe4,7487
mpl_toolkits/tests/test_mplot3d.py,sha256=BUvMUV5OkJskzLZ1KBIIFgKIWoiWaw38FOYBJT0_xRM,26306
pylab.py,sha256=u_By3CHla-rBMg57egFXIxZ3P_J6zEkSu_dNpBcH5pw,90
@@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.31.1)
Root-Is-Purelib: false
Tag: cp36-cp36m-manylinux1_x86_64
@@ -1,2 +0,0 @@
mpl_toolkits
mpl_toolkits
@@ -1,3 +0,0 @@
matplotlib
mpl_toolkits
pylab
File diff suppressed because it is too large Load Diff
@@ -1,210 +0,0 @@
# Javascript template for HTMLWriter
JS_INCLUDE = """
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/
css/font-awesome.min.css">
<script language="javascript">
/* Define the Animation class */
function Animation(frames, img_id, slider_id, interval, loop_select_id){
this.img_id = img_id;
this.slider_id = slider_id;
this.loop_select_id = loop_select_id;
this.interval = interval;
this.current_frame = 0;
this.direction = 0;
this.timer = null;
this.frames = new Array(frames.length);
for (var i=0; i<frames.length; i++)
{
this.frames[i] = new Image();
this.frames[i].src = frames[i];
}
document.getElementById(this.slider_id).max = this.frames.length - 1;
this.set_frame(this.current_frame);
}
Animation.prototype.get_loop_state = function(){
var button_group = document[this.loop_select_id].state;
for (var i = 0; i < button_group.length; i++) {
var button = button_group[i];
if (button.checked) {
return button.value;
}
}
return undefined;
}
Animation.prototype.set_frame = function(frame){
this.current_frame = frame;
document.getElementById(this.img_id).src =
this.frames[this.current_frame].src;
document.getElementById(this.slider_id).value = this.current_frame;
}
Animation.prototype.next_frame = function()
{
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
}
Animation.prototype.previous_frame = function()
{
this.set_frame(Math.max(0, this.current_frame - 1));
}
Animation.prototype.first_frame = function()
{
this.set_frame(0);
}
Animation.prototype.last_frame = function()
{
this.set_frame(this.frames.length - 1);
}
Animation.prototype.slower = function()
{
this.interval /= 0.7;
if(this.direction > 0){this.play_animation();}
else if(this.direction < 0){this.reverse_animation();}
}
Animation.prototype.faster = function()
{
this.interval *= 0.7;
if(this.direction > 0){this.play_animation();}
else if(this.direction < 0){this.reverse_animation();}
}
Animation.prototype.anim_step_forward = function()
{
this.current_frame += 1;
if(this.current_frame < this.frames.length){
this.set_frame(this.current_frame);
}else{
var loop_state = this.get_loop_state();
if(loop_state == "loop"){
this.first_frame();
}else if(loop_state == "reflect"){
this.last_frame();
this.reverse_animation();
}else{
this.pause_animation();
this.last_frame();
}
}
}
Animation.prototype.anim_step_reverse = function()
{
this.current_frame -= 1;
if(this.current_frame >= 0){
this.set_frame(this.current_frame);
}else{
var loop_state = this.get_loop_state();
if(loop_state == "loop"){
this.last_frame();
}else if(loop_state == "reflect"){
this.first_frame();
this.play_animation();
}else{
this.pause_animation();
this.first_frame();
}
}
}
Animation.prototype.pause_animation = function()
{
this.direction = 0;
if (this.timer){
clearInterval(this.timer);
this.timer = null;
}
}
Animation.prototype.play_animation = function()
{
this.pause_animation();
this.direction = 1;
var t = this;
if (!this.timer) this.timer = setInterval(function() {
t.anim_step_forward();
}, this.interval);
}
Animation.prototype.reverse_animation = function()
{
this.pause_animation();
this.direction = -1;
var t = this;
if (!this.timer) this.timer = setInterval(function() {
t.anim_step_reverse();
}, this.interval);
}
</script>
"""
# HTML template for HTMLWriter
DISPLAY_TEMPLATE = """
<div class="animation" align="center">
<img id="_anim_img{id}">
<br>
<input id="_anim_slider{id}" type="range" style="width:350px"
name="points" min="0" max="1" step="1" value="0"
onchange="anim{id}.set_frame(parseInt(this.value));"></input>
<br>
<button onclick="anim{id}.slower()"><i class="fa fa-minus"></i></button>
<button onclick="anim{id}.first_frame()"><i class="fa fa-fast-backward">
</i></button>
<button onclick="anim{id}.previous_frame()">
<i class="fa fa-step-backward"></i></button>
<button onclick="anim{id}.reverse_animation()">
<i class="fa fa-play fa-flip-horizontal"></i></button>
<button onclick="anim{id}.pause_animation()"><i class="fa fa-pause">
</i></button>
<button onclick="anim{id}.play_animation()"><i class="fa fa-play"></i>
</button>
<button onclick="anim{id}.next_frame()"><i class="fa fa-step-forward">
</i></button>
<button onclick="anim{id}.last_frame()"><i class="fa fa-fast-forward">
</i></button>
<button onclick="anim{id}.faster()"><i class="fa fa-plus"></i></button>
<form action="#n" name="_anim_loop_select{id}" class="anim_control">
<input type="radio" name="state"
value="once" {once_checked}> Once </input>
<input type="radio" name="state"
value="loop" {loop_checked}> Loop </input>
<input type="radio" name="state"
value="reflect" {reflect_checked}> Reflect </input>
</form>
</div>
<script language="javascript">
/* Instantiate the Animation class. */
/* The IDs given should match those used in the template above. */
(function() {{
var img_id = "_anim_img{id}";
var slider_id = "_anim_slider{id}";
var loop_select_id = "_anim_loop_select{id}";
var frames = new Array({Nframes});
{fill_frames}
/* set a timeout to make sure all the above elements are created before
the object is initialized. */
setTimeout(function() {{
anim{id} = new Animation(frames, img_id, slider_id, {interval},
loop_select_id);
}}, 0);
}})()
</script>
"""
INCLUDED_FRAMES = """
for (var i=0; i<{Nframes}; i++){{
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
".{frame_format}";
}}
"""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,729 +0,0 @@
"""
This module provides the routine to adjust subplot layouts so that there are
no overlapping axes or axes decorations. All axes decorations are dealt with
(labels, ticks, titles, ticklabels) and some dependent artists are also dealt
with (colorbar, suptitle, legend).
Layout is done via :meth:`~matplotlib.gridspec`, with one constraint per
gridspec, so it is possible to have overlapping axes if the gridspecs
overlap (i.e. using :meth:`~matplotlib.gridspec.GridSpecFromSubplotSpec`).
Axes placed using ``figure.subplots()`` or ``figure.add_subplots()`` will
participate in the layout. Axes manually placed via ``figure.add_axes()``
will not.
See Tutorial: :doc:`/tutorials/intermediate/constrainedlayout_guide`
"""
# Development Notes:
# What gets a layoutbox:
# - figure
# - gridspec
# - subplotspec
# EITHER:
# - axes + pos for the axes (i.e. the total area taken by axis and
# the actual "position" argument that needs to be sent to
# ax.set_position.)
# - The axes layout box will also encomapss the legend, and that is
# how legends get included (axes legeneds, not figure legends)
# - colorbars are sibblings of the axes if they are single-axes
# colorbars
# OR:
# - a gridspec can be inside a subplotspec.
# - subplotspec
# EITHER:
# - axes...
# OR:
# - gridspec... with arbitrary nesting...
# - colorbars are siblings of the subplotspecs if they are multi-axes
# colorbars.
# - suptitle:
# - right now suptitles are just stacked atop everything else in figure.
# Could imagine suptitles being gridspec suptitles, but not implimented
#
# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
# be more general way to add extra-axes annotations.
import numpy as np
import logging
import warnings
from matplotlib.legend import Legend
import matplotlib.transforms as transforms
import matplotlib._layoutbox as layoutbox
_log = logging.getLogger(__name__)
def _in_same_column(colnum0min, colnum0max, colnumCmin, colnumCmax):
return (colnumCmin <= colnum0min <= colnumCmax
or colnumCmin <= colnum0max <= colnumCmax)
def _in_same_row(rownum0min, rownum0max, rownumCmin, rownumCmax):
return (rownumCmin <= rownum0min <= rownumCmax
or rownumCmin <= rownum0max <= rownumCmax)
def _axes_all_finite_sized(fig):
"""
helper function to make sure all axes in the
figure have a finite width and height. If not, return False
"""
for ax in fig.axes:
if ax._layoutbox is not None:
newpos = ax._poslayoutbox.get_rect()
if newpos[2] <= 0 or newpos[3] <= 0:
return False
return True
######################################################
def do_constrained_layout(fig, renderer, h_pad, w_pad,
hspace=None, wspace=None):
"""
Do the constrained_layout. Called at draw time in
``figure.constrained_layout()``
Parameters
----------
fig: Figure
is the ``figure`` instance to do the layout in.
renderer: Renderer
the renderer to use.
h_pad, w_pad : float
are in figure-normalized units, and are a padding around the axes
elements.
hspace, wspace : float
are in fractions of the subplot sizes.
"""
''' Steps:
1. get a list of unique gridspecs in this figure. Each gridspec will be
constrained separately.
2. Check for gaps in the gridspecs. i.e. if not every axes slot in the
gridspec has been filled. If empty, add a ghost axis that is made so
that it cannot be seen (though visible=True). This is needed to make
a blank spot in the layout.
3. Compare the tight_bbox of each axes to its `position`, and assume that
the difference is the space needed by the elements around the edge of
the axes (decorations) like the title, ticklabels, x-labels, etc. This
can include legends who overspill the axes boundaries.
4. Constrain gridspec elements to line up:
a) if colnum0 neq colnumC, the two subplotspecs are stacked next to
each other, with the appropriate order.
b) if colnum0 == columnC line up the left or right side of the
_poslayoutbox (depending if it is the min or max num that is equal).
c) do the same for rows...
5. The above doesn't constrain relative sizes of the _poslayoutboxes at
all, and indeed zero-size is a solution that the solver often finds more
convenient than expanding the sizes. Right now the solution is to compare
subplotspec sizes (i.e. drowsC and drows0) and constrain the larger
_poslayoutbox to be larger than the ratio of the sizes. i.e. if drows0 >
drowsC, then ax._poslayoutbox > axc._poslayoutbox * drowsC / drows0. This
works fine *if* the decorations are similar between the axes. If the
larger subplotspec has much larger axes decorations, then the constraint
above is incorrect.
We need the greater than in the above, in general, rather than an equals
sign. Consider the case of the left column having 2 rows, and the right
column having 1 row. We want the top and bottom of the _poslayoutboxes to
line up. So that means if there are decorations on the left column axes
they will be smaller than half as large as the right hand axis.
This can break down if the decoration size for the right hand axis (the
margins) is very large. There must be a math way to check for this case.
'''
invTransFig = fig.transFigure.inverted().transform_bbox
# list of unique gridspecs that contain child axes:
gss = set()
for ax in fig.axes:
if hasattr(ax, 'get_subplotspec'):
gs = ax.get_subplotspec().get_gridspec()
if gs._layoutbox is not None:
gss.add(gs)
if len(gss) == 0:
warnings.warn('There are no gridspecs with layoutboxes. '
'Possibly did not call parent GridSpec with the figure= '
'keyword')
if fig._layoutbox.constrained_layout_called < 1:
for gs in gss:
# fill in any empty gridspec slots w/ ghost axes...
_make_ghost_gridspec_slots(fig, gs)
for nnn in range(2):
# do the algrithm twice. This has to be done because decorators
# change size after the first re-position (i.e. x/yticklabels get
# larger/smaller). This second reposition tends to be much milder,
# so doing twice makes things work OK.
for ax in fig.axes:
_log.debug(ax._layoutbox)
if ax._layoutbox is not None:
# make margins for each layout box based on the size of
# the decorators.
_make_layout_margins(ax, renderer, h_pad, w_pad)
# do layout for suptitle.
if fig._suptitle is not None and fig._suptitle._layoutbox is not None:
sup = fig._suptitle
bbox = invTransFig(sup.get_window_extent(renderer=renderer))
height = bbox.y1 - bbox.y0
sup._layoutbox.edit_height(height+h_pad)
# OK, the above lines up ax._poslayoutbox with ax._layoutbox
# now we need to
# 1) arrange the subplotspecs. We do it at this level because
# the subplotspecs are meant to contain other dependent axes
# like colorbars or legends.
# 2) line up the right and left side of the ax._poslayoutbox
# that have the same subplotspec maxes.
if fig._layoutbox.constrained_layout_called < 1:
# arrange the subplotspecs... This is all done relative to each
# other. Some subplotspecs conatain axes, and others contain
# gridspecs the ones that contain gridspecs are a set proportion
# of their parent gridspec. The ones that contain axes are
# not so constrained.
figlb = fig._layoutbox
for child in figlb.children:
if child._is_gridspec_layoutbox():
# This routine makes all the subplot spec containers
# have the correct arrangement. It just stacks the
# subplot layoutboxes in the correct order...
_arrange_subplotspecs(child, hspace=hspace, wspace=wspace)
for gs in gss:
_align_spines(fig, gs)
fig._layoutbox.constrained_layout_called += 1
fig._layoutbox.update_variables()
# check if any axes collapsed to zero. If not, don't change positions:
if _axes_all_finite_sized(fig):
# Now set the position of the axes...
for ax in fig.axes:
if ax._layoutbox is not None:
newpos = ax._poslayoutbox.get_rect()
# Now set the new position.
# ax.set_position will zero out the layout for
# this axis, allowing users to hard-code the position,
# so this does the same w/o zeroing layout.
ax._set_position(newpos, which='original')
else:
warnings.warn('constrained_layout not applied. At least '
'one axes collapsed to zero width or height.')
def _make_ghost_gridspec_slots(fig, gs):
"""
Check for unoccupied gridspec slots and make ghost axes for these
slots... Do for each gs separately. This is a pretty big kludge
but shoudn't have too much ill effect. The worst is that
someone querrying the figure will wonder why there are more
axes than they thought.
"""
nrows, ncols = gs.get_geometry()
hassubplotspec = np.zeros(nrows * ncols, dtype=bool)
axs = []
for ax in fig.axes:
if (hasattr(ax, 'get_subplotspec')
and ax._layoutbox is not None
and ax.get_subplotspec().get_gridspec() == gs):
axs += [ax]
for ax in axs:
ss0 = ax.get_subplotspec()
if ss0.num2 is None:
ss0.num2 = ss0.num1
hassubplotspec[ss0.num1:(ss0.num2 + 1)] = True
for nn, hss in enumerate(hassubplotspec):
if not hss:
# this gridspec slot doesn't have an axis so we
# make a "ghost".
ax = fig.add_subplot(gs[nn])
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
ax.set_facecolor((1, 0, 0, 0))
def _make_layout_margins(ax, renderer, h_pad, w_pad):
"""
For each axes, make a margin between the *pos* layoutbox and the
*axes* layoutbox be a minimum size that can accommodate the
decorations on the axis.
"""
fig = ax.figure
invTransFig = fig.transFigure.inverted().transform_bbox
pos = ax.get_position(original=True)
tightbbox = ax.get_tightbbox(renderer=renderer)
bbox = invTransFig(tightbbox)
# use stored h_pad if it exists
h_padt = ax._poslayoutbox.h_pad
if h_padt is None:
h_padt = h_pad
w_padt = ax._poslayoutbox.w_pad
if w_padt is None:
w_padt = w_pad
ax._poslayoutbox.edit_left_margin_min(-bbox.x0 +
pos.x0 + w_padt)
ax._poslayoutbox.edit_right_margin_min(bbox.x1 -
pos.x1 + w_padt)
ax._poslayoutbox.edit_bottom_margin_min(
-bbox.y0 + pos.y0 + h_padt)
ax._poslayoutbox.edit_top_margin_min(bbox.y1-pos.y1+h_padt)
_log.debug('left %f', (-bbox.x0 + pos.x0 + w_pad))
_log.debug('right %f', (bbox.x1 - pos.x1 + w_pad))
_log.debug('bottom %f', (-bbox.y0 + pos.y0 + h_padt))
# Sometimes its possible for the solver to collapse
# rather than expand axes, so they all have zero height
# or width. This stops that... It *should* have been
# taken into account w/ pref_width...
if fig._layoutbox.constrained_layout_called < 1:
ax._poslayoutbox.constrain_height_min(20, strength='weak')
ax._poslayoutbox.constrain_width_min(20, strength='weak')
ax._layoutbox.constrain_height_min(20, strength='weak')
ax._layoutbox.constrain_width_min(20, strength='weak')
ax._poslayoutbox.constrain_top_margin(0, strength='weak')
ax._poslayoutbox.constrain_bottom_margin(0,
strength='weak')
ax._poslayoutbox.constrain_right_margin(0, strength='weak')
ax._poslayoutbox.constrain_left_margin(0, strength='weak')
def _align_spines(fig, gs):
"""
- Align right/left and bottom/top spines of appropriate subplots.
- Compare size of subplotspec including height and width ratios
and make sure that the axes spines are at least as large
as they should be.
"""
# for each gridspec...
nrows, ncols = gs.get_geometry()
width_ratios = gs.get_width_ratios()
height_ratios = gs.get_height_ratios()
if width_ratios is None:
width_ratios = np.ones(ncols)
if height_ratios is None:
height_ratios = np.ones(nrows)
# get axes in this gridspec....
axs = []
for ax in fig.axes:
if (hasattr(ax, 'get_subplotspec')
and ax._layoutbox is not None):
if ax.get_subplotspec().get_gridspec() == gs:
axs += [ax]
rownummin = np.zeros(len(axs), dtype=np.int8)
rownummax = np.zeros(len(axs), dtype=np.int8)
colnummin = np.zeros(len(axs), dtype=np.int8)
colnummax = np.zeros(len(axs), dtype=np.int8)
width = np.zeros(len(axs))
height = np.zeros(len(axs))
for n, ax in enumerate(axs):
ss0 = ax.get_subplotspec()
if ss0.num2 is None:
ss0.num2 = ss0.num1
rownummin[n], colnummin[n] = divmod(ss0.num1, ncols)
rownummax[n], colnummax[n] = divmod(ss0.num2, ncols)
width[n] = np.sum(
width_ratios[colnummin[n]:(colnummax[n] + 1)])
height[n] = np.sum(
height_ratios[rownummin[n]:(rownummax[n] + 1)])
for nn, ax in enumerate(axs[:-1]):
ss0 = ax.get_subplotspec()
# now compare ax to all the axs:
#
# If the subplotspecs have the same colnumXmax, then line
# up their right sides. If they have the same min, then
# line up their left sides (and vertical equivalents).
rownum0min, colnum0min = rownummin[nn], colnummin[nn]
rownum0max, colnum0max = rownummax[nn], colnummax[nn]
width0, height0 = width[nn], height[nn]
alignleft = False
alignright = False
alignbot = False
aligntop = False
alignheight = False
alignwidth = False
for mm in range(nn+1, len(axs)):
axc = axs[mm]
rownumCmin, colnumCmin = rownummin[mm], colnummin[mm]
rownumCmax, colnumCmax = rownummax[mm], colnummax[mm]
widthC, heightC = width[mm], height[mm]
# Horizontally align axes spines if they have the
# same min or max:
if not alignleft and colnum0min == colnumCmin:
# we want the _poslayoutboxes to line up on left
# side of the axes spines...
layoutbox.align([ax._poslayoutbox,
axc._poslayoutbox],
'left')
alignleft = True
if not alignright and colnum0max == colnumCmax:
# line up right sides of _poslayoutbox
layoutbox.align([ax._poslayoutbox,
axc._poslayoutbox],
'right')
alignright = True
# Vertically align axes spines if they have the
# same min or max:
if not aligntop and rownum0min == rownumCmin:
# line up top of _poslayoutbox
_log.debug('rownum0min == rownumCmin')
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
'top')
aligntop = True
if not alignbot and rownum0max == rownumCmax:
# line up bottom of _poslayoutbox
_log.debug('rownum0max == rownumCmax')
layoutbox.align([ax._poslayoutbox, axc._poslayoutbox],
'bottom')
alignbot = True
###########
# Now we make the widths and heights of position boxes
# similar. (i.e the spine locations)
# This allows vertically stacked subplots to have
# different sizes if they occupy different amounts
# of the gridspec: i.e.
# gs = gridspec.GridSpec(3,1)
# ax1 = gs[0,:]
# ax2 = gs[1:,:]
# then drows0 = 1, and drowsC = 2, and ax2
# should be at least twice as large as ax1.
# But it can be more than twice as large because
# it needs less room for the labeling.
#
# For height, this only needs to be done if the
# subplots share a column. For width if they
# share a row.
drowsC = (rownumCmax - rownumCmin + 1)
drows0 = (rownum0max - rownum0min + 1)
dcolsC = (colnumCmax - colnumCmin + 1)
dcols0 = (colnum0max - colnum0min + 1)
if not alignheight and drows0 == drowsC:
ax._poslayoutbox.constrain_height(
axc._poslayoutbox.height * height0 / heightC)
alignheight = True
elif _in_same_column(colnum0min, colnum0max,
colnumCmin, colnumCmax):
if height0 > heightC:
ax._poslayoutbox.constrain_height_min(
axc._poslayoutbox.height * height0 / heightC)
# these constraints stop the smaller axes from
# being allowed to go to zero height...
axc._poslayoutbox.constrain_height_min(
ax._poslayoutbox.height * heightC /
(height0*1.8))
elif height0 < heightC:
axc._poslayoutbox.constrain_height_min(
ax._poslayoutbox.height * heightC / height0)
ax._poslayoutbox.constrain_height_min(
ax._poslayoutbox.height * height0 /
(heightC*1.8))
# widths...
if not alignwidth and dcols0 == dcolsC:
ax._poslayoutbox.constrain_width(
axc._poslayoutbox.width * width0 / widthC)
alignwidth = True
elif _in_same_row(rownum0min, rownum0max,
rownumCmin, rownumCmax):
if width0 > widthC:
ax._poslayoutbox.constrain_width_min(
axc._poslayoutbox.width * width0 / widthC)
axc._poslayoutbox.constrain_width_min(
ax._poslayoutbox.width * widthC /
(width0*1.8))
elif width0 < widthC:
axc._poslayoutbox.constrain_width_min(
ax._poslayoutbox.width * widthC / width0)
ax._poslayoutbox.constrain_width_min(
axc._poslayoutbox.width * width0 /
(widthC*1.8))
def _arrange_subplotspecs(gs, hspace=0, wspace=0):
"""
arrange the subplotspec children of this gridspec, and then recursively
do the same of any gridspec children of those gridspecs...
"""
sschildren = []
for child in gs.children:
if child._is_subplotspec_layoutbox():
for child2 in child.children:
# check for gridspec children...
if child2._is_gridspec_layoutbox():
_arrange_subplotspecs(child2, hspace=hspace, wspace=wspace)
sschildren += [child]
# now arrange the subplots...
for child0 in sschildren:
ss0 = child0.artist
nrows, ncols = ss0.get_gridspec().get_geometry()
if ss0.num2 is None:
ss0.num2 = ss0.num1
rowNum0min, colNum0min = divmod(ss0.num1, ncols)
rowNum0max, colNum0max = divmod(ss0.num2, ncols)
sschildren = sschildren[1:]
for childc in sschildren:
ssc = childc.artist
rowNumCmin, colNumCmin = divmod(ssc.num1, ncols)
if ssc.num2 is None:
ssc.num2 = ssc.num1
rowNumCmax, colNumCmax = divmod(ssc.num2, ncols)
# OK, this tells us the relative layout of ax
# with axc
thepad = wspace / ncols
if colNum0max < colNumCmin:
layoutbox.hstack([ss0._layoutbox, ssc._layoutbox],
padding=thepad)
if colNumCmax < colNum0min:
layoutbox.hstack([ssc._layoutbox, ss0._layoutbox],
padding=thepad)
####
# vertical alignment
thepad = hspace / nrows
if rowNum0max < rowNumCmin:
layoutbox.vstack([ss0._layoutbox,
ssc._layoutbox],
padding=thepad)
if rowNumCmax < rowNum0min:
layoutbox.vstack([ssc._layoutbox,
ss0._layoutbox],
padding=thepad)
def layoutcolorbarsingle(ax, cax, shrink, aspect, location, pad=0.05):
"""
Do the layout for a colorbar, to not oeverly pollute colorbar.py
`pad` is in fraction of the original axis size.
"""
axlb = ax._layoutbox
axpos = ax._poslayoutbox
axsslb = ax.get_subplotspec()._layoutbox
lb = layoutbox.LayoutBox(
parent=axsslb,
name=axsslb.name + '.cbar',
artist=cax)
if location in ('left', 'right'):
lbpos = layoutbox.LayoutBox(
parent=lb,
name=lb.name + '.pos',
tightwidth=False,
pos=True,
subplot=False,
artist=cax)
if location == 'right':
# arrange to right of parent axis
layoutbox.hstack([axlb, lb], padding=pad * axlb.width,
strength='strong')
else:
layoutbox.hstack([lb, axlb], padding=pad * axlb.width)
# constrain the height and center...
layoutbox.match_heights([axpos, lbpos], [1, shrink])
layoutbox.align([axpos, lbpos], 'v_center')
# set the width of the pos box
lbpos.constrain_width(shrink * axpos.height * (1/aspect),
strength='strong')
elif location in ('bottom', 'top'):
lbpos = layoutbox.LayoutBox(
parent=lb,
name=lb.name + '.pos',
tightheight=True,
pos=True,
subplot=False,
artist=cax)
if location == 'bottom':
layoutbox.vstack([axlb, lb], padding=pad * axlb.height)
else:
layoutbox.vstack([lb, axlb], padding=pad * axlb.height)
# constrain the height and center...
layoutbox.match_widths([axpos, lbpos],
[1, shrink], strength='strong')
layoutbox.align([axpos, lbpos], 'h_center')
# set the height of the pos box
lbpos.constrain_height(axpos.width * aspect * shrink,
strength='medium')
return lb, lbpos
def _getmaxminrowcolumn(axs):
# helper to get the min/max rows and columns of a list of axes.
maxrow = -100000
minrow = 1000000
maxax = None
minax = None
maxcol = -100000
mincol = 1000000
maxax_col = None
minax_col = None
for ax in axs:
subspec = ax.get_subplotspec()
nrows, ncols, row_start, row_stop, col_start, col_stop = \
subspec.get_rows_columns()
if row_stop > maxrow:
maxrow = row_stop
maxax = ax
if row_start < minrow:
minrow = row_start
minax = ax
if col_stop > maxcol:
maxcol = col_stop
maxax_col = ax
if col_start < mincol:
mincol = col_start
minax_col = ax
return (minrow, maxrow, minax, maxax, mincol, maxcol, minax_col, maxax_col)
def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05):
"""
Do the layout for a colorbar, to not oeverly pollute colorbar.py
`pad` is in fraction of the original axis size.
"""
gs = parents[0].get_subplotspec().get_gridspec()
# parent layout box....
gslb = gs._layoutbox
lb = layoutbox.LayoutBox(parent=gslb.parent,
name=gslb.parent.name + '.cbar',
artist=cax)
# figure out the row and column extent of the parents.
(minrow, maxrow, minax_row, maxax_row,
mincol, maxcol, minax_col, maxax_col) = _getmaxminrowcolumn(parents)
if location in ('left', 'right'):
lbpos = layoutbox.LayoutBox(
parent=lb,
name=lb.name + '.pos',
tightwidth=False,
pos=True,
subplot=False,
artist=cax)
for ax in parents:
if location == 'right':
order = [ax._layoutbox, lb]
else:
order = [lb, ax._layoutbox]
layoutbox.hstack(order, padding=pad * gslb.width,
strength='strong')
# constrain the height and center...
# This isn't quite right. We'd like the colorbar
# pos to line up w/ the axes poss, not the size of the
# gs.
# Horizontal Layout: need to check all the axes in this gridspec
for ch in gslb.children:
subspec = ch.artist
nrows, ncols, row_start, row_stop, col_start, col_stop = \
subspec.get_rows_columns()
if location == 'right':
if col_stop <= maxcol:
order = [subspec._layoutbox, lb]
# arrange to right of the parents
if col_start > maxcol:
order = [lb, subspec._layoutbox]
elif location == 'left':
if col_start >= mincol:
order = [lb, subspec._layoutbox]
if col_stop < mincol:
order = [subspec._layoutbox, lb]
layoutbox.hstack(order, padding=pad * gslb.width,
strength='strong')
# Vertical layout:
maxposlb = minax_row._poslayoutbox
minposlb = maxax_row._poslayoutbox
# now we want the height of the colorbar pos to be
# set by the top and bottom of the min/max axes...
# bottom top
# b t
# h = (top-bottom)*shrink
# b = bottom + (top-bottom - h) / 2.
lbpos.constrain_height(
(maxposlb.top - minposlb.bottom) *
shrink, strength='strong')
lbpos.constrain_bottom(
(maxposlb.top - minposlb.bottom) *
(1 - shrink)/2 + minposlb.bottom,
strength='strong')
# set the width of the pos box
lbpos.constrain_width(lbpos.height * (shrink / aspect),
strength='strong')
elif location in ('bottom', 'top'):
lbpos = layoutbox.LayoutBox(
parent=lb,
name=lb.name + '.pos',
tightheight=True,
pos=True,
subplot=False,
artist=cax)
for ax in parents:
if location == 'bottom':
order = [ax._layoutbox, lb]
else:
order = [lb, ax._layoutbox]
layoutbox.vstack(order, padding=pad * gslb.width,
strength='strong')
# Vertical Layout: need to check all the axes in this gridspec
for ch in gslb.children:
subspec = ch.artist
nrows, ncols, row_start, row_stop, col_start, col_stop = \
subspec.get_rows_columns()
if location == 'bottom':
if row_stop <= minrow:
order = [subspec._layoutbox, lb]
if row_start > maxrow:
order = [lb, subspec._layoutbox]
elif location == 'top':
if row_stop < minrow:
order = [subspec._layoutbox, lb]
if row_start >= maxrow:
order = [lb, subspec._layoutbox]
layoutbox.vstack(order, padding=pad * gslb.width,
strength='strong')
# Do horizontal layout...
maxposlb = maxax_col._poslayoutbox
minposlb = minax_col._poslayoutbox
lbpos.constrain_width((maxposlb.right - minposlb.left) *
shrink)
lbpos.constrain_left(
(maxposlb.right - minposlb.left) *
(1-shrink)/2 + minposlb.left)
# set the height of the pos box
lbpos.constrain_height(lbpos.width * shrink * aspect,
strength='medium')
return lb, lbpos
@@ -1,735 +0,0 @@
"""
Conventions:
"constrain_x" means to constrain the variable with either
another kiwisolver variable, or a float. i.e. `constrain_width(0.2)`
will set a constraint that the width has to be 0.2 and this constraint is
permanent - i.e. it will not be removed if it becomes obsolete.
"edit_x" means to set x to a value (just a float), and that this value can
change. So `edit_width(0.2)` will set width to be 0.2, but `edit_width(0.3)`
will allow it to change to 0.3 later. Note that these values are still just
"suggestions" in `kiwisolver` parlance, and could be over-ridden by
other constrains.
"""
import itertools
import kiwisolver as kiwi
import logging
import numpy as np
import warnings
import matplotlib
_log = logging.getLogger(__name__)
# renderers can be complicated
def get_renderer(fig):
if fig._cachedRenderer:
renderer = fig._cachedRenderer
else:
canvas = fig.canvas
if canvas and hasattr(canvas, "get_renderer"):
renderer = canvas.get_renderer()
else:
# not sure if this can happen
# seems to with PDF...
_log.info("constrained_layout : falling back to Agg renderer")
from matplotlib.backends.backend_agg import FigureCanvasAgg
canvas = FigureCanvasAgg(fig)
renderer = canvas.get_renderer()
return renderer
class LayoutBox(object):
"""
Basic rectangle representation using kiwi solver variables
"""
def __init__(self, parent=None, name='', tightwidth=False,
tightheight=False, artist=None,
lower_left=(0, 0), upper_right=(1, 1), pos=False,
subplot=False, h_pad=None, w_pad=None):
Variable = kiwi.Variable
self.parent = parent
self.name = name
sn = self.name + '_'
if parent is None:
self.solver = kiwi.Solver()
self.constrained_layout_called = 0
else:
self.solver = parent.solver
self.constrained_layout_called = None
# parent wants to know about this child!
parent.add_child(self)
# keep track of artist associated w/ this layout. Can be none
self.artist = artist
# keep track if this box is supposed to be a pos that is constrained
# by the parent.
self.pos = pos
# keep track of whether we need to match this subplot up with others.
self.subplot = subplot
# we need the str below for Py 2 which complains the string is unicode
self.top = Variable(str(sn + 'top'))
self.bottom = Variable(str(sn + 'bottom'))
self.left = Variable(str(sn + 'left'))
self.right = Variable(str(sn + 'right'))
self.width = Variable(str(sn + 'width'))
self.height = Variable(str(sn + 'height'))
self.h_center = Variable(str(sn + 'h_center'))
self.v_center = Variable(str(sn + 'v_center'))
self.min_width = Variable(str(sn + 'min_width'))
self.min_height = Variable(str(sn + 'min_height'))
self.pref_width = Variable(str(sn + 'pref_width'))
self.pref_height = Variable(str(sn + 'pref_height'))
# margis are only used for axes-position layout boxes. maybe should
# be a separate subclass:
self.left_margin = Variable(str(sn + 'left_margin'))
self.right_margin = Variable(str(sn + 'right_margin'))
self.bottom_margin = Variable(str(sn + 'bottom_margin'))
self.top_margin = Variable(str(sn + 'top_margin'))
# mins
self.left_margin_min = Variable(str(sn + 'left_margin_min'))
self.right_margin_min = Variable(str(sn + 'right_margin_min'))
self.bottom_margin_min = Variable(str(sn + 'bottom_margin_min'))
self.top_margin_min = Variable(str(sn + 'top_margin_min'))
right, top = upper_right
left, bottom = lower_left
self.tightheight = tightheight
self.tightwidth = tightwidth
self.add_constraints()
self.children = []
self.subplotspec = None
if self.pos:
self.constrain_margins()
self.h_pad = h_pad
self.w_pad = w_pad
def constrain_margins(self):
"""
Only do this for pos. This sets a variable distance
margin between the position of the axes and the outer edge of
the axes.
Margins are variable because they change with the fogure size.
Margin minimums are set to make room for axes decorations. However,
the margins can be larger if we are mathicng the position size to
otehr axes.
"""
sol = self.solver
# left
if not sol.hasEditVariable(self.left_margin_min):
sol.addEditVariable(self.left_margin_min, 'strong')
sol.suggestValue(self.left_margin_min, 0.0001)
c = (self.left_margin == self.left - self.parent.left)
self.solver.addConstraint(c | 'required')
c = (self.left_margin >= self.left_margin_min)
self.solver.addConstraint(c | 'strong')
# right
if not sol.hasEditVariable(self.right_margin_min):
sol.addEditVariable(self.right_margin_min, 'strong')
sol.suggestValue(self.right_margin_min, 0.0001)
c = (self.right_margin == self.parent.right - self.right)
self.solver.addConstraint(c | 'required')
c = (self.right_margin >= self.right_margin_min)
self.solver.addConstraint(c | 'required')
# bottom
if not sol.hasEditVariable(self.bottom_margin_min):
sol.addEditVariable(self.bottom_margin_min, 'strong')
sol.suggestValue(self.bottom_margin_min, 0.0001)
c = (self.bottom_margin == self.bottom - self.parent.bottom)
self.solver.addConstraint(c | 'required')
c = (self.bottom_margin >= self.bottom_margin_min)
self.solver.addConstraint(c | 'required')
# top
if not sol.hasEditVariable(self.top_margin_min):
sol.addEditVariable(self.top_margin_min, 'strong')
sol.suggestValue(self.top_margin_min, 0.0001)
c = (self.top_margin == self.parent.top - self.top)
self.solver.addConstraint(c | 'required')
c = (self.top_margin >= self.top_margin_min)
self.solver.addConstraint(c | 'required')
def add_child(self, child):
self.children += [child]
def remove_child(self, child):
try:
self.children.remove(child)
except ValueError:
_log.info("Tried to remove child that doesn't belong to parent")
def add_constraints(self):
sol = self.solver
# never let width and height go negative.
for i in [self.min_width, self.min_height]:
sol.addEditVariable(i, 1e9)
sol.suggestValue(i, 0.0)
# define relation ships between things thing width and right and left
self.hard_constraints()
# self.soft_constraints()
if self.parent:
self.parent_constrain()
# sol.updateVariables()
def parent_constrain(self):
parent = self.parent
hc = [self.left >= parent.left,
self.bottom >= parent.bottom,
self.top <= parent.top,
self.right <= parent.right]
for c in hc:
self.solver.addConstraint(c | 'required')
def hard_constraints(self):
hc = [self.width == self.right - self.left,
self.height == self.top - self.bottom,
self.h_center == (self.left + self.right) * 0.5,
self.v_center == (self.top + self.bottom) * 0.5,
self.width >= self.min_width,
self.height >= self.min_height]
for c in hc:
self.solver.addConstraint(c | 'required')
def soft_constraints(self):
sol = self.solver
if self.tightwidth:
suggest = 0.
else:
suggest = 20.
c = (self.pref_width == suggest)
for i in c:
sol.addConstraint(i | 'required')
if self.tightheight:
suggest = 0.
else:
suggest = 20.
c = (self.pref_height == suggest)
for i in c:
sol.addConstraint(i | 'required')
c = [(self.width >= suggest),
(self.height >= suggest)]
for i in c:
sol.addConstraint(i | 150000)
def set_parent(self, parent):
''' replace the parent of this with the new parent
'''
self.parent = parent
self.parent_constrain()
def constrain_geometry(self, left, bottom, right, top, strength='strong'):
hc = [self.left == left,
self.right == right,
self.bottom == bottom,
self.top == top]
for c in hc:
self.solver.addConstraint((c | strength))
# self.solver.updateVariables()
def constrain_same(self, other, strength='strong'):
"""
Make the layoutbox have same position as other layoutbox
"""
hc = [self.left == other.left,
self.right == other.right,
self.bottom == other.bottom,
self.top == other.top]
for c in hc:
self.solver.addConstraint((c | strength))
def constrain_left_margin(self, margin, strength='strong'):
c = (self.left == self.parent.left + margin)
self.solver.addConstraint(c | strength)
def edit_left_margin_min(self, margin):
self.solver.suggestValue(self.left_margin_min, margin)
def constrain_right_margin(self, margin, strength='strong'):
c = (self.right == self.parent.right - margin)
self.solver.addConstraint(c | strength)
def edit_right_margin_min(self, margin):
self.solver.suggestValue(self.right_margin_min, margin)
def constrain_bottom_margin(self, margin, strength='strong'):
c = (self.bottom == self.parent.bottom + margin)
self.solver.addConstraint(c | strength)
def edit_bottom_margin_min(self, margin):
self.solver.suggestValue(self.bottom_margin_min, margin)
def constrain_top_margin(self, margin, strength='strong'):
c = (self.top == self.parent.top - margin)
self.solver.addConstraint(c | strength)
def edit_top_margin_min(self, margin):
self.solver.suggestValue(self.top_margin_min, margin)
def get_rect(self):
return (self.left.value(), self.bottom.value(),
self.width.value(), self.height.value())
def update_variables(self):
'''
Update *all* the variables that are part of the solver this LayoutBox
is created with
'''
self.solver.updateVariables()
def edit_height(self, height, strength='strong'):
'''
Set the height of the layout box.
This is done as an editable variable so that the value can change
due to resizing.
'''
sol = self.solver
for i in [self.height]:
if not sol.hasEditVariable(i):
sol.addEditVariable(i, strength)
sol.suggestValue(self.height, height)
def constrain_height(self, height, strength='strong'):
'''
Constrain the height of the layout box. height is
either a float or a layoutbox.height.
'''
c = (self.height == height)
self.solver.addConstraint(c | strength)
def constrain_height_min(self, height, strength='strong'):
c = (self.height >= height)
self.solver.addConstraint(c | strength)
def edit_width(self, width, strength='strong'):
sol = self.solver
for i in [self.width]:
if not sol.hasEditVariable(i):
sol.addEditVariable(i, strength)
sol.suggestValue(self.width, width)
def constrain_width(self, width, strength='strong'):
'''
Constrain the width of the layout box. `width` is
either a float or a layoutbox.width.
'''
c = (self.width == width)
self.solver.addConstraint(c | strength)
def constrain_width_min(self, width, strength='strong'):
c = (self.width >= width)
self.solver.addConstraint(c | strength)
def constrain_left(self, left, strength='strong'):
c = (self.left == left)
self.solver.addConstraint(c | strength)
def constrain_bottom(self, bottom, strength='strong'):
c = (self.bottom == bottom)
self.solver.addConstraint(c | strength)
def constrain_right(self, right, strength='strong'):
c = (self.right == right)
self.solver.addConstraint(c | strength)
def constrain_top(self, top, strength='strong'):
c = (self.top == top)
self.solver.addConstraint(c | strength)
def _is_subplotspec_layoutbox(self):
'''
Helper to check if this layoutbox is the layoutbox of a
subplotspec
'''
name = (self.name).split('.')[-1]
return name[:2] == 'ss'
def _is_gridspec_layoutbox(self):
'''
Helper to check if this layoutbox is the layoutbox of a
gridspec
'''
name = (self.name).split('.')[-1]
return name[:8] == 'gridspec'
def find_child_subplots(self):
'''
Find children of this layout box that are subplots. We want to line
poss up, and this is an easy way to find them all.
'''
if self.subplot:
subplots = [self]
else:
subplots = []
for child in self.children:
subplots += child.find_child_subplots()
return subplots
def layout_from_subplotspec(self, subspec,
name='', artist=None, pos=False):
''' Make a layout box from a subplotspec. The layout box is
constrained to be a fraction of the width/height of the parent,
and be a fraction of the parent width/height from the left/bottom
of the parent. Therefore the parent can move around and the
layout for the subplot spec should move with it.
The parent is *usually* the gridspec that made the subplotspec.??
'''
lb = LayoutBox(parent=self, name=name, artist=artist, pos=pos)
gs = subspec.get_gridspec()
nrows, ncols = gs.get_geometry()
parent = self.parent
# OK, now, we want to set the position of this subplotspec
# based on its subplotspec parameters. The new gridspec will inherit.
# from gridspec. prob should be new method in gridspec
left = 0.0
right = 1.0
bottom = 0.0
top = 1.0
totWidth = right-left
totHeight = top-bottom
hspace = 0.
wspace = 0.
# calculate accumulated heights of columns
cellH = totHeight / (nrows + hspace * (nrows - 1))
sepH = hspace*cellH
if gs._row_height_ratios is not None:
netHeight = cellH * nrows
tr = float(sum(gs._row_height_ratios))
cellHeights = [netHeight*r/tr for r in gs._row_height_ratios]
else:
cellHeights = [cellH] * nrows
sepHeights = [0] + ([sepH] * (nrows - 1))
cellHs = np.add.accumulate(np.ravel(
list(zip(sepHeights, cellHeights))))
# calculate accumulated widths of rows
cellW = totWidth/(ncols + wspace * (ncols - 1))
sepW = wspace*cellW
if gs._col_width_ratios is not None:
netWidth = cellW * ncols
tr = float(sum(gs._col_width_ratios))
cellWidths = [netWidth * r / tr for r in gs._col_width_ratios]
else:
cellWidths = [cellW] * ncols
sepWidths = [0] + ([sepW] * (ncols - 1))
cellWs = np.add.accumulate(np.ravel(list(zip(sepWidths, cellWidths))))
figTops = [top - cellHs[2 * rowNum] for rowNum in range(nrows)]
figBottoms = [top - cellHs[2 * rowNum + 1] for rowNum in range(nrows)]
figLefts = [left + cellWs[2 * colNum] for colNum in range(ncols)]
figRights = [left + cellWs[2 * colNum + 1] for colNum in range(ncols)]
rowNum, colNum = divmod(subspec.num1, ncols)
figBottom = figBottoms[rowNum]
figTop = figTops[rowNum]
figLeft = figLefts[colNum]
figRight = figRights[colNum]
if subspec.num2 is not None:
rowNum2, colNum2 = divmod(subspec.num2, ncols)
figBottom2 = figBottoms[rowNum2]
figTop2 = figTops[rowNum2]
figLeft2 = figLefts[colNum2]
figRight2 = figRights[colNum2]
figBottom = min(figBottom, figBottom2)
figLeft = min(figLeft, figLeft2)
figTop = max(figTop, figTop2)
figRight = max(figRight, figRight2)
# These are numbers relative to 0,0,1,1. Need to constrain
# relative to parent.
width = figRight - figLeft
height = figTop - figBottom
parent = self.parent
cs = [self.left == parent.left + parent.width * figLeft,
self.bottom == parent.bottom + parent.height * figBottom,
self.width == parent.width * width,
self.height == parent.height * height]
for c in cs:
self.solver.addConstraint((c | 'required'))
return lb
def __repr__(self):
args = (self.name, self.left.value(), self.bottom.value(),
self.right.value(), self.top.value())
return ('LayoutBox: %25s, (left: %1.3f) (bot: %1.3f) '
'(right: %1.3f) (top: %1.3f) ') % args
# Utility functions that act on layoutboxes...
def hstack(boxes, padding=0, strength='strong'):
'''
Stack LayoutBox instances from left to right.
`padding` is in figure-relative units.
'''
for i in range(1, len(boxes)):
c = (boxes[i-1].right + padding <= boxes[i].left)
boxes[i].solver.addConstraint(c | strength)
def hpack(boxes, padding=0, strength='strong'):
'''
Stack LayoutBox instances from left to right.
'''
for i in range(1, len(boxes)):
c = (boxes[i-1].right + padding == boxes[i].left)
boxes[i].solver.addConstraint(c | strength)
def vstack(boxes, padding=0, strength='strong'):
'''
Stack LayoutBox instances from top to bottom
'''
for i in range(1, len(boxes)):
c = (boxes[i-1].bottom - padding >= boxes[i].top)
boxes[i].solver.addConstraint(c | strength)
def vpack(boxes, padding=0, strength='strong'):
'''
Stack LayoutBox instances from top to bottom
'''
for i in range(1, len(boxes)):
c = (boxes[i-1].bottom - padding >= boxes[i].top)
boxes[i].solver.addConstraint(c | strength)
def match_heights(boxes, height_ratios=None, strength='medium'):
'''
Stack LayoutBox instances from top to bottom
'''
if height_ratios is None:
height_ratios = np.ones(len(boxes))
for i in range(1, len(boxes)):
c = (boxes[i-1].height ==
boxes[i].height*height_ratios[i-1]/height_ratios[i])
boxes[i].solver.addConstraint(c | strength)
def match_widths(boxes, width_ratios=None, strength='medium'):
'''
Stack LayoutBox instances from top to bottom
'''
if width_ratios is None:
width_ratios = np.ones(len(boxes))
for i in range(1, len(boxes)):
c = (boxes[i-1].width ==
boxes[i].width*width_ratios[i-1]/width_ratios[i])
boxes[i].solver.addConstraint(c | strength)
def vstackeq(boxes, padding=0, height_ratios=None):
vstack(boxes, padding=padding)
match_heights(boxes, height_ratios=height_ratios)
def hstackeq(boxes, padding=0, width_ratios=None):
hstack(boxes, padding=padding)
match_widths(boxes, width_ratios=width_ratios)
def align(boxes, attr, strength='strong'):
cons = []
for box in boxes[1:]:
cons = (getattr(boxes[0], attr) == getattr(box, attr))
boxes[0].solver.addConstraint(cons | strength)
def match_top_margins(boxes, levels=1):
box0 = boxes[0]
top0 = box0
for n in range(levels):
top0 = top0.parent
for box in boxes[1:]:
topb = box
for n in range(levels):
topb = topb.parent
c = (box0.top-top0.top == box.top-topb.top)
box0.solver.addConstraint(c | 'strong')
def match_bottom_margins(boxes, levels=1):
box0 = boxes[0]
top0 = box0
for n in range(levels):
top0 = top0.parent
for box in boxes[1:]:
topb = box
for n in range(levels):
topb = topb.parent
c = (box0.bottom-top0.bottom == box.bottom-topb.bottom)
box0.solver.addConstraint(c | 'strong')
def match_left_margins(boxes, levels=1):
box0 = boxes[0]
top0 = box0
for n in range(levels):
top0 = top0.parent
for box in boxes[1:]:
topb = box
for n in range(levels):
topb = topb.parent
c = (box0.left-top0.left == box.left-topb.left)
box0.solver.addConstraint(c | 'strong')
def match_right_margins(boxes, levels=1):
box0 = boxes[0]
top0 = box0
for n in range(levels):
top0 = top0.parent
for box in boxes[1:]:
topb = box
for n in range(levels):
topb = topb.parent
c = (box0.right-top0.right == box.right-topb.right)
box0.solver.addConstraint(c | 'strong')
def match_width_margins(boxes, levels=1):
match_left_margins(boxes, levels=levels)
match_right_margins(boxes, levels=levels)
def match_height_margins(boxes, levels=1):
match_top_margins(boxes, levels=levels)
match_bottom_margins(boxes, levels=levels)
def match_margins(boxes, levels=1):
match_width_margins(boxes, levels=levels)
match_height_margins(boxes, levels=levels)
_layoutboxobjnum = itertools.count()
def seq_id():
'''
Generate a short sequential id for layoutbox objects...
'''
global _layoutboxobjnum
return ('%06d' % (next(_layoutboxobjnum)))
def print_children(lb):
'''
Print the children of the layoutbox
'''
print(lb)
for child in lb.children:
print_children(child)
def nonetree(lb):
'''
Make all elements in this tree none... This signals not to do any more
layout.
'''
if lb is not None:
if lb.parent is None:
# Clear the solver. Hopefully this garbage collects.
lb.solver.reset()
nonechildren(lb)
else:
nonetree(lb.parent)
def nonechildren(lb):
for child in lb.children:
nonechildren(child)
lb.artist._layoutbox = None
lb = None
def print_tree(lb):
'''
Print the tree of layoutboxes
'''
if lb.parent is None:
print('LayoutBox Tree\n')
print('==============\n')
print_children(lb)
print('\n')
else:
print_tree(lb.parent)
def plot_children(fig, box, level=0, printit=True):
'''
Simple plotting to show where boxes are
'''
import matplotlib
import matplotlib.pyplot as plt
if isinstance(fig, matplotlib.figure.Figure):
ax = fig.add_axes([0., 0., 1., 1.])
ax.set_facecolor([1., 1., 1., 0.7])
ax.set_alpha(0.3)
fig.draw(fig.canvas.get_renderer())
else:
ax = fig
import matplotlib.patches as patches
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
if printit:
print("Level:", level)
for child in box.children:
rect = child.get_rect()
if printit:
print(child)
ax.add_patch(
patches.Rectangle(
(child.left.value(), child.bottom.value()), # (x,y)
child.width.value(), # width
child.height.value(), # height
fc='none',
alpha=0.8,
ec=colors[level]
)
)
if level > 0:
name = child.name.split('.')[-1]
if level % 2 == 0:
ax.text(child.left.value(), child.bottom.value(), name,
size=12-level, color=colors[level])
else:
ax.text(child.right.value(), child.top.value(), name,
ha='right', va='top', size=12-level,
color=colors[level])
plot_children(ax, child, level=level+1, printit=printit)
File diff suppressed because it is too large Load Diff
@@ -1,134 +0,0 @@
"""
Manage figures for pyplot interface.
"""
import atexit
import gc
import sys
class Gcf(object):
"""
Singleton to manage a set of integer-numbered figures.
This class is never instantiated; it consists of two class
attributes (a list and a dictionary), and a set of static
methods that operate on those attributes, accessing them
directly as class attributes.
Attributes:
*figs*:
dictionary of the form {*num*: *manager*, ...}
*_activeQue*:
list of *managers*, with active one at the end
"""
_activeQue = []
figs = {}
@classmethod
def get_fig_manager(cls, num):
"""
If figure manager *num* exists, make it the active
figure and return the manager; otherwise return *None*.
"""
manager = cls.figs.get(num, None)
if manager is not None:
cls.set_active(manager)
return manager
@classmethod
def destroy(cls, num):
"""
Try to remove all traces of figure *num*.
In the interactive backends, this is bound to the
window "destroy" and "delete" events.
"""
if not cls.has_fignum(num):
return
manager = cls.figs[num]
manager.canvas.mpl_disconnect(manager._cidgcf)
cls._activeQue.remove(manager)
del cls.figs[num]
manager.destroy()
gc.collect(1)
@classmethod
def destroy_fig(cls, fig):
"*fig* is a Figure instance"
num = next((manager.num for manager in cls.figs.values()
if manager.canvas.figure == fig), None)
if num is not None:
cls.destroy(num)
@classmethod
def destroy_all(cls):
# this is need to ensure that gc is available in corner cases
# where modules are being torn down after install with easy_install
import gc # noqa
for manager in list(cls.figs.values()):
manager.canvas.mpl_disconnect(manager._cidgcf)
manager.destroy()
cls._activeQue = []
cls.figs.clear()
gc.collect(1)
@classmethod
def has_fignum(cls, num):
"""
Return *True* if figure *num* exists.
"""
return num in cls.figs
@classmethod
def get_all_fig_managers(cls):
"""
Return a list of figure managers.
"""
return list(cls.figs.values())
@classmethod
def get_num_fig_managers(cls):
"""
Return the number of figures being managed.
"""
return len(cls.figs)
@classmethod
def get_active(cls):
"""
Return the manager of the active figure, or *None*.
"""
if len(cls._activeQue) == 0:
return None
else:
return cls._activeQue[-1]
@classmethod
def set_active(cls, manager):
"""
Make the figure corresponding to *manager* the active one.
"""
oldQue = cls._activeQue[:]
cls._activeQue = []
for m in oldQue:
if m != manager:
cls._activeQue.append(m)
cls._activeQue.append(manager)
cls.figs[manager.num] = manager
@classmethod
def draw_all(cls, force=False):
"""
Redraw all figures registered with the pyplot
state machine.
"""
for f_mgr in cls.get_all_fig_managers():
if force or f_mgr.canvas.figure.stale:
f_mgr.canvas.draw_idle()
atexit.register(Gcf.destroy_all)
@@ -1,21 +0,0 @@
# This file was generated by 'versioneer.py' (0.15) from
# revision-control system data, or from the parent directory name of an
# unpacked source archive. Distribution tarballs contain a pre-generated copy
# of this file.
import json
import sys
version_json = '''
{
"dirty": false,
"error": null,
"full-revisionid": "8858a0d1bdd149a0897789e8503ac586be14676d",
"version": "3.0.2"
}
''' # END VERSION_JSON
def get_versions():
return json.loads(version_json)
@@ -1,562 +0,0 @@
"""
This is a python interface to Adobe Font Metrics Files. Although a
number of other python implementations exist, and may be more complete
than this, it was decided not to go with them because they were
either:
1) copyrighted or used a non-BSD compatible license
2) had too many dependencies and a free standing lib was needed
3) Did more than needed and it was easier to write afresh rather than
figure out how to get just what was needed.
It is pretty easy to use, and requires only built-in python libs:
>>> from matplotlib import rcParams
>>> import os.path
>>> afm_fname = os.path.join(rcParams['datapath'],
... 'fonts', 'afm', 'ptmr8a.afm')
>>>
>>> from matplotlib.afm import AFM
>>> with open(afm_fname, 'rb') as fh:
... afm = AFM(fh)
>>> afm.string_width_height('What the heck?')
(6220.0, 694)
>>> afm.get_fontname()
'Times-Roman'
>>> afm.get_kern_dist('A', 'f')
0
>>> afm.get_kern_dist('A', 'y')
-92.0
>>> afm.get_bbox_char('!')
[130, -9, 238, 676]
As in the Adobe Font Metrics File Format Specification, all dimensions
are given in units of 1/1000 of the scale factor (point size) of the font
being used.
"""
from collections import namedtuple
import re
import sys
from ._mathtext_data import uni2type1
from matplotlib.cbook import deprecated
# some afm files have floats where we are expecting ints -- there is
# probably a better way to handle this (support floats, round rather
# than truncate). But I don't know what the best approach is now and
# this change to _to_int should at least prevent mpl from crashing on
# these JDH (2009-11-06)
def _to_int(x):
return int(float(x))
_to_float = float
def _to_str(x):
return x.decode('utf8')
def _to_list_of_ints(s):
s = s.replace(b',', b' ')
return [_to_int(val) for val in s.split()]
def _to_list_of_floats(s):
return [_to_float(val) for val in s.split()]
def _to_bool(s):
if s.lower().strip() in (b'false', b'0', b'no'):
return False
else:
return True
def _sanity_check(fh):
"""
Check if the file at least looks like AFM.
If not, raise :exc:`RuntimeError`.
"""
# Remember the file position in case the caller wants to
# do something else with the file.
pos = fh.tell()
try:
line = next(fh)
finally:
fh.seek(pos, 0)
# AFM spec, Section 4: The StartFontMetrics keyword [followed by a
# version number] must be the first line in the file, and the
# EndFontMetrics keyword must be the last non-empty line in the
# file. We just check the first line.
if not line.startswith(b'StartFontMetrics'):
raise RuntimeError('Not an AFM file')
def _parse_header(fh):
"""
Reads the font metrics header (up to the char metrics) and returns
a dictionary mapping *key* to *val*. *val* will be converted to the
appropriate python type as necessary; e.g.:
* 'False'->False
* '0'->0
* '-168 -218 1000 898'-> [-168, -218, 1000, 898]
Dictionary keys are
StartFontMetrics, FontName, FullName, FamilyName, Weight,
ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
XHeight, Ascender, Descender, StartCharMetrics
"""
headerConverters = {
b'StartFontMetrics': _to_float,
b'FontName': _to_str,
b'FullName': _to_str,
b'FamilyName': _to_str,
b'Weight': _to_str,
b'ItalicAngle': _to_float,
b'IsFixedPitch': _to_bool,
b'FontBBox': _to_list_of_ints,
b'UnderlinePosition': _to_int,
b'UnderlineThickness': _to_int,
b'Version': _to_str,
b'Notice': _to_str,
b'EncodingScheme': _to_str,
b'CapHeight': _to_float, # Is the second version a mistake, or
b'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
b'XHeight': _to_float,
b'Ascender': _to_float,
b'Descender': _to_float,
b'StdHW': _to_float,
b'StdVW': _to_float,
b'StartCharMetrics': _to_int,
b'CharacterSet': _to_str,
b'Characters': _to_int,
}
d = {}
for line in fh:
line = line.rstrip()
if line.startswith(b'Comment'):
continue
lst = line.split(b' ', 1)
key = lst[0]
if len(lst) == 2:
val = lst[1]
else:
val = b''
try:
d[key] = headerConverters[key](val)
except ValueError:
print('Value error parsing header in AFM:', key, val,
file=sys.stderr)
continue
except KeyError:
print('Found an unknown keyword in AFM header (was %r)' % key,
file=sys.stderr)
continue
if key == b'StartCharMetrics':
return d
raise RuntimeError('Bad parse')
CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
CharMetrics.__doc__ = """
Represents the character metrics of a single character.
Notes
-----
The fields do currently only describe a subset of character metrics
information defined in the AFM standard.
"""
CharMetrics.width.__doc__ = """The character width (WX)."""
CharMetrics.name.__doc__ = """The character name (N)."""
CharMetrics.bbox.__doc__ = """
The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""
def _parse_char_metrics(fh):
"""
Parse the given filehandle for character metrics information and return
the information as dicts.
It is assumed that the file cursor is on the line behind
'StartCharMetrics'.
Returns
-------
ascii_d : dict
A mapping "ASCII num of the character" to `.CharMetrics`.
name_d : dict
A mapping "character name" to `.CharMetrics`.
Notes
-----
This function is incomplete per the standard, but thus far parses
all the sample afm files tried.
"""
required_keys = {'C', 'WX', 'N', 'B'}
ascii_d = {}
name_d = {}
for line in fh:
# We are defensively letting values be utf8. The spec requires
# ascii, but there are non-compliant fonts in circulation
line = _to_str(line.rstrip()) # Convert from byte-literal
if line.startswith('EndCharMetrics'):
return ascii_d, name_d
# Split the metric line into a dictionary, keyed by metric identifiers
vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s)
# There may be other metrics present, but only these are needed
if not required_keys.issubset(vals):
raise RuntimeError('Bad char metrics line: %s' % line)
num = _to_int(vals['C'])
wx = _to_float(vals['WX'])
name = vals['N']
bbox = _to_list_of_floats(vals['B'])
bbox = list(map(int, bbox))
metrics = CharMetrics(wx, name, bbox)
# Workaround: If the character name is 'Euro', give it the
# corresponding character code, according to WinAnsiEncoding (see PDF
# Reference).
if name == 'Euro':
num = 128
if num != -1:
ascii_d[num] = metrics
name_d[name] = metrics
raise RuntimeError('Bad parse')
def _parse_kern_pairs(fh):
"""
Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
values are the kern pair value. For example, a kern pairs line like
``KPX A y -50``
will be represented as::
d[ ('A', 'y') ] = -50
"""
line = next(fh)
if not line.startswith(b'StartKernPairs'):
raise RuntimeError('Bad start of kern pairs data: %s' % line)
d = {}
for line in fh:
line = line.rstrip()
if not line:
continue
if line.startswith(b'EndKernPairs'):
next(fh) # EndKernData
return d
vals = line.split()
if len(vals) != 4 or vals[0] != b'KPX':
raise RuntimeError('Bad kern pairs line: %s' % line)
c1, c2, val = _to_str(vals[1]), _to_str(vals[2]), _to_float(vals[3])
d[(c1, c2)] = val
raise RuntimeError('Bad kern pairs parse')
CompositePart = namedtuple('CompositePart', 'name, dx, dy')
CompositePart.__doc__ = """
Represents the information on a composite element of a composite char."""
CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'."""
CompositePart.dx.__doc__ = """x-displacement of the part from the origin."""
CompositePart.dy.__doc__ = """y-displacement of the part from the origin."""
def _parse_composites(fh):
"""
Parse the given filehandle for composites information return them as a
dict.
It is assumed that the file cursor is on the line behind 'StartComposites'.
Returns
-------
composites : dict
A dict mapping composite character names to a parts list. The parts
list is a list of `.CompositePart` entries describing the parts of
the composite.
Example
-------
A composite definition line::
CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
will be represented as::
composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0),
CompositePart(name='acute', dx=160, dy=170)]
"""
composites = {}
for line in fh:
line = line.rstrip()
if not line:
continue
if line.startswith(b'EndComposites'):
return composites
vals = line.split(b';')
cc = vals[0].split()
name, numParts = cc[1], _to_int(cc[2])
pccParts = []
for s in vals[1:-1]:
pcc = s.split()
part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3]))
pccParts.append(part)
composites[name] = pccParts
raise RuntimeError('Bad composites parse')
def _parse_optional(fh):
"""
Parse the optional fields for kern pair data and composites.
Returns
-------
kern_data : dict
A dict containing kerning information. May be empty.
See `._parse_kern_pairs`.
composites : dict
A dict containing composite information. May be empty.
See `._parse_composites`.
"""
optional = {
b'StartKernData': _parse_kern_pairs,
b'StartComposites': _parse_composites,
}
d = {b'StartKernData': {},
b'StartComposites': {}}
for line in fh:
line = line.rstrip()
if not line:
continue
key = line.split()[0]
if key in optional:
d[key] = optional[key](fh)
return d[b'StartKernData'], d[b'StartComposites']
@deprecated("3.0", "Use the class AFM instead.")
def parse_afm(fh):
return _parse_afm(fh)
def _parse_afm(fh):
"""
Parse the Adobe Font Metrics file in file handle *fh*.
Returns
-------
header : dict
A header dict. See :func:`_parse_header`.
cmetrics_by_ascii : dict
From :func:`_parse_char_metrics`.
cmetrics_by_name : dict
From :func:`_parse_char_metrics`.
kernpairs : dict
From :func:`_parse_kern_pairs`.
composites : dict
From :func:`_parse_composites`
"""
_sanity_check(fh)
header = _parse_header(fh)
cmetrics_by_ascii, cmetrics_by_name = _parse_char_metrics(fh)
kernpairs, composites = _parse_optional(fh)
return header, cmetrics_by_ascii, cmetrics_by_name, kernpairs, composites
class AFM(object):
def __init__(self, fh):
"""Parse the AFM file in file object *fh*."""
(self._header,
self._metrics,
self._metrics_by_name,
self._kern,
self._composite) = _parse_afm(fh)
def get_bbox_char(self, c, isord=False):
if not isord:
c = ord(c)
return self._metrics[c].bbox
def string_width_height(self, s):
"""
Return the string width (including kerning) and string height
as a (*w*, *h*) tuple.
"""
if not len(s):
return 0, 0
total_width = 0
namelast = None
miny = 1e9
maxy = 0
for c in s:
if c == '\n':
continue
wx, name, bbox = self._metrics[ord(c)]
total_width += wx + self._kern.get((namelast, name), 0)
l, b, w, h = bbox
miny = min(miny, b)
maxy = max(maxy, b + h)
namelast = name
return total_width, maxy - miny
def get_str_bbox_and_descent(self, s):
"""Return the string bounding box and the maximal descent."""
if not len(s):
return 0, 0, 0, 0, 0
total_width = 0
namelast = None
miny = 1e9
maxy = 0
left = 0
if not isinstance(s, str):
s = _to_str(s)
for c in s:
if c == '\n':
continue
name = uni2type1.get(ord(c), 'question')
try:
wx, _, bbox = self._metrics_by_name[name]
except KeyError:
name = 'question'
wx, _, bbox = self._metrics_by_name[name]
total_width += wx + self._kern.get((namelast, name), 0)
l, b, w, h = bbox
left = min(left, l)
miny = min(miny, b)
maxy = max(maxy, b + h)
namelast = name
return left, miny, total_width, maxy - miny, -miny
def get_str_bbox(self, s):
"""Return the string bounding box."""
return self.get_str_bbox_and_descent(s)[:4]
def get_name_char(self, c, isord=False):
"""Get the name of the character, i.e., ';' is 'semicolon'."""
if not isord:
c = ord(c)
return self._metrics[c].name
def get_width_char(self, c, isord=False):
"""
Get the width of the character from the character metric WX field.
"""
if not isord:
c = ord(c)
return self._metrics[c].width
def get_width_from_char_name(self, name):
"""Get the width of the character from a type1 character name."""
return self._metrics_by_name[name].width
def get_height_char(self, c, isord=False):
"""Get the bounding box (ink) height of character *c* (space is 0)."""
if not isord:
c = ord(c)
return self._metrics[c].bbox[-1]
def get_kern_dist(self, c1, c2):
"""
Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
"""
name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
return self.get_kern_dist_from_name(name1, name2)
def get_kern_dist_from_name(self, name1, name2):
"""
Return the kerning pair distance (possibly 0) for chars
*name1* and *name2*.
"""
return self._kern.get((name1, name2), 0)
def get_fontname(self):
"""Return the font name, e.g., 'Times-Roman'."""
return self._header[b'FontName']
def get_fullname(self):
"""Return the font full name, e.g., 'Times-Roman'."""
name = self._header.get(b'FullName')
if name is None: # use FontName as a substitute
name = self._header[b'FontName']
return name
def get_familyname(self):
"""Return the font family name, e.g., 'Times'."""
name = self._header.get(b'FamilyName')
if name is not None:
return name
# FamilyName not specified so we'll make a guess
name = self.get_fullname()
extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
r'light|ultralight|extra|condensed))+$')
return re.sub(extras, '', name)
@property
def family_name(self):
"""The font family name, e.g., 'Times'."""
return self.get_familyname()
def get_weight(self):
"""Return the font weight, e.g., 'Bold' or 'Roman'."""
return self._header[b'Weight']
def get_angle(self):
"""Return the fontangle as float."""
return self._header[b'ItalicAngle']
def get_capheight(self):
"""Return the cap height as float."""
return self._header[b'CapHeight']
def get_xheight(self):
"""Return the xheight as float."""
return self._header[b'XHeight']
def get_underline_thickness(self):
"""Return the underline thickness as float."""
return self._header[b'UnderlineThickness']
def get_horizontal_stem_width(self):
"""
Return the standard horizontal stem width as float, or *None* if
not specified in AFM file.
"""
return self._header.get(b'StdHW', None)
def get_vertical_stem_width(self):
"""
Return the standard vertical stem width as float, or *None* if
not specified in AFM file.
"""
return self._header.get(b'StdVW', None)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,2 +0,0 @@
from ._subplots import *
from ._axes import *
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,242 +0,0 @@
import functools
import warnings
from matplotlib import docstring
import matplotlib.artist as martist
from matplotlib.axes._axes import Axes
from matplotlib.gridspec import GridSpec, SubplotSpec
import matplotlib._layoutbox as layoutbox
class SubplotBase(object):
"""
Base class for subplots, which are :class:`Axes` instances with
additional methods to facilitate generating and manipulating a set
of :class:`Axes` within a figure.
"""
def __init__(self, fig, *args, **kwargs):
"""
*fig* is a :class:`matplotlib.figure.Figure` instance.
*args* is the tuple (*numRows*, *numCols*, *plotNum*), where
the array of subplots in the figure has dimensions *numRows*,
*numCols*, and where *plotNum* is the number of the subplot
being created. *plotNum* starts at 1 in the upper left
corner and increases to the right.
If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
"""
self.figure = fig
if len(args) == 1:
if isinstance(args[0], SubplotSpec):
self._subplotspec = args[0]
else:
try:
s = str(int(args[0]))
rows, cols, num = map(int, s)
except ValueError:
raise ValueError('Single argument to subplot must be '
'a 3-digit integer')
self._subplotspec = GridSpec(rows, cols,
figure=self.figure)[num - 1]
# num - 1 for converting from MATLAB to python indexing
elif len(args) == 3:
rows, cols, num = args
rows = int(rows)
cols = int(cols)
if isinstance(num, tuple) and len(num) == 2:
num = [int(n) for n in num]
self._subplotspec = GridSpec(
rows, cols,
figure=self.figure)[(num[0] - 1):num[1]]
else:
if num < 1 or num > rows*cols:
raise ValueError(
("num must be 1 <= num <= {maxn}, not {num}"
).format(maxn=rows*cols, num=num))
self._subplotspec = GridSpec(
rows, cols, figure=self.figure)[int(num) - 1]
# num - 1 for converting from MATLAB to python indexing
else:
raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
self.update_params()
# _axes_class is set in the subplot_class_factory
self._axes_class.__init__(self, fig, self.figbox, **kwargs)
# add a layout box to this, for both the full axis, and the poss
# of the axis. We need both because the axes may become smaller
# due to parasitic axes and hence no longer fill the subplotspec.
if self._subplotspec._layoutbox is None:
self._layoutbox = None
self._poslayoutbox = None
else:
name = self._subplotspec._layoutbox.name + '.ax'
name = name + layoutbox.seq_id()
self._layoutbox = layoutbox.LayoutBox(
parent=self._subplotspec._layoutbox,
name=name,
artist=self)
self._poslayoutbox = layoutbox.LayoutBox(
parent=self._layoutbox,
name=self._layoutbox.name+'.pos',
pos=True, subplot=True, artist=self)
def __reduce__(self):
# get the first axes class which does not inherit from a subplotbase
axes_class = next(
c for c in type(self).__mro__
if issubclass(c, Axes) and not issubclass(c, SubplotBase))
return (_picklable_subplot_class_constructor,
(axes_class,),
self.__getstate__())
def get_geometry(self):
"""get the subplot geometry, e.g., 2,2,3"""
rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
return rows, cols, num1 + 1 # for compatibility
# COVERAGE NOTE: Never used internally or from examples
def change_geometry(self, numrows, numcols, num):
"""change subplot geometry, e.g., from 1,1,1 to 2,2,3"""
self._subplotspec = GridSpec(numrows, numcols,
figure=self.figure)[num - 1]
self.update_params()
self.set_position(self.figbox)
def get_subplotspec(self):
"""get the SubplotSpec instance associated with the subplot"""
return self._subplotspec
def set_subplotspec(self, subplotspec):
"""set the SubplotSpec instance associated with the subplot"""
self._subplotspec = subplotspec
def get_gridspec(self):
"""get the GridSpec instance associated with the subplot"""
return self._subplotspec.get_gridspec()
def update_params(self):
"""update the subplot position from fig.subplotpars"""
self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \
self.get_subplotspec().get_position(self.figure,
return_all=True)
def is_first_col(self):
return self.colNum == 0
def is_first_row(self):
return self.rowNum == 0
def is_last_row(self):
return self.rowNum == self.numRows - 1
def is_last_col(self):
return self.colNum == self.numCols - 1
# COVERAGE NOTE: Never used internally.
def label_outer(self):
"""Only show "outer" labels and tick labels.
x-labels are only kept for subplots on the last row; y-labels only for
subplots on the first column.
"""
lastrow = self.is_last_row()
firstcol = self.is_first_col()
if not lastrow:
for label in self.get_xticklabels(which="both"):
label.set_visible(False)
self.get_xaxis().get_offset_text().set_visible(False)
self.set_xlabel("")
if not firstcol:
for label in self.get_yticklabels(which="both"):
label.set_visible(False)
self.get_yaxis().get_offset_text().set_visible(False)
self.set_ylabel("")
def _make_twin_axes(self, *kl, **kwargs):
"""
Make a twinx axes of self. This is used for twinx and twiny.
"""
from matplotlib.projections import process_projection_requirements
if 'sharex' in kwargs and 'sharey' in kwargs:
# The following line is added in v2.2 to avoid breaking Seaborn,
# which currently uses this internal API.
if kwargs["sharex"] is not self and kwargs["sharey"] is not self:
raise ValueError("Twinned Axes may share only one axis.")
kl = (self.get_subplotspec(),) + kl
projection_class, kwargs, key = process_projection_requirements(
self.figure, *kl, **kwargs)
ax2 = subplot_class_factory(projection_class)(self.figure,
*kl, **kwargs)
self.figure.add_subplot(ax2)
self.set_adjustable('datalim')
ax2.set_adjustable('datalim')
if self._layoutbox is not None and ax2._layoutbox is not None:
# make the layout boxes be explicitly the same
ax2._layoutbox.constrain_same(self._layoutbox)
ax2._poslayoutbox.constrain_same(self._poslayoutbox)
self._twinned_axes.join(self, ax2)
return ax2
# this here to support cartopy which was using a private part of the
# API to register their Axes subclasses.
# In 3.1 this should be changed to a dict subclass that warns on use
# In 3.3 to a dict subclass that raises a useful exception on use
# In 3.4 should be removed
# The slow timeline is to give cartopy enough time to get several
# release out before we break them.
_subplot_classes = {}
@functools.lru_cache(None)
def subplot_class_factory(axes_class=None):
"""
This makes a new class that inherits from `.SubplotBase` and the
given axes_class (which is assumed to be a subclass of `.axes.Axes`).
This is perhaps a little bit roundabout to make a new class on
the fly like this, but it means that a new Subplot class does
not have to be created for every type of Axes.
"""
if axes_class is None:
axes_class = Axes
try:
# Avoid creating two different instances of GeoAxesSubplot...
# Only a temporary backcompat fix. This should be removed in
# 3.4
return next(cls for cls in SubplotBase.__subclasses__()
if cls.__bases__ == (SubplotBase, axes_class))
except StopIteration:
return type("%sSubplot" % axes_class.__name__,
(SubplotBase, axes_class),
{'_axes_class': axes_class})
# This is provided for backward compatibility
Subplot = subplot_class_factory()
def _picklable_subplot_class_constructor(axes_class):
"""
This stub class exists to return the appropriate subplot class when called
with an axes class. This is purely to allow pickling of Axes and Subplots.
"""
subplot_class = subplot_class_factory(axes_class)
return subplot_class.__new__(subplot_class)
docstring.interpd.update(Axes=martist.kwdoc(Axes))
docstring.dedent_interpd(Axes.__init__)
docstring.interpd.update(Subplot=martist.kwdoc(Axes))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,433 +0,0 @@
"""
`ToolManager`
Class that makes the bridge between user interaction (key press,
toolbar clicks, ..) and the actions in response to the user inputs.
"""
import warnings
import matplotlib.cbook as cbook
import matplotlib.widgets as widgets
from matplotlib.rcsetup import validate_stringlist
import matplotlib.backend_tools as tools
class ToolEvent(object):
"""Event for tool manipulation (add/remove)"""
def __init__(self, name, sender, tool, data=None):
self.name = name
self.sender = sender
self.tool = tool
self.data = data
class ToolTriggerEvent(ToolEvent):
"""Event to inform that a tool has been triggered"""
def __init__(self, name, sender, tool, canvasevent=None, data=None):
ToolEvent.__init__(self, name, sender, tool, data)
self.canvasevent = canvasevent
class ToolManagerMessageEvent(object):
"""
Event carrying messages from toolmanager
Messages usually get displayed to the user by the toolbar
"""
def __init__(self, name, sender, message):
self.name = name
self.sender = sender
self.message = message
class ToolManager(object):
"""
Helper class that groups all the user interactions for a Figure.
Attributes
----------
figure: `Figure`
keypresslock: `widgets.LockDraw`
`LockDraw` object to know if the `canvas` key_press_event is locked
messagelock: `widgets.LockDraw`
`LockDraw` object to know if the message is available to write
"""
def __init__(self, figure=None):
warnings.warn('Treat the new Tool classes introduced in v1.5 as ' +
'experimental for now, the API will likely change in ' +
'version 2.1 and perhaps the rcParam as well')
self._key_press_handler_id = None
self._tools = {}
self._keys = {}
self._toggled = {}
self._callbacks = cbook.CallbackRegistry()
# to process keypress event
self.keypresslock = widgets.LockDraw()
self.messagelock = widgets.LockDraw()
self._figure = None
self.set_figure(figure)
@property
def canvas(self):
"""Canvas managed by FigureManager"""
if not self._figure:
return None
return self._figure.canvas
@property
def figure(self):
"""Figure that holds the canvas"""
return self._figure
@figure.setter
def figure(self, figure):
self.set_figure(figure)
def set_figure(self, figure, update_tools=True):
"""
Bind the given figure to the tools.
Parameters
----------
figure : `.Figure`
update_tools : bool
Force tools to update figure
"""
if self._key_press_handler_id:
self.canvas.mpl_disconnect(self._key_press_handler_id)
self._figure = figure
if figure:
self._key_press_handler_id = self.canvas.mpl_connect(
'key_press_event', self._key_press)
if update_tools:
for tool in self._tools.values():
tool.figure = figure
def toolmanager_connect(self, s, func):
"""
Connect event with string *s* to *func*.
Parameters
----------
s : String
Name of the event
The following events are recognized
- 'tool_message_event'
- 'tool_removed_event'
- 'tool_added_event'
For every tool added a new event is created
- 'tool_trigger_TOOLNAME`
Where TOOLNAME is the id of the tool.
func : function
Function to be called with signature
def func(event)
"""
return self._callbacks.connect(s, func)
def toolmanager_disconnect(self, cid):
"""
Disconnect callback id *cid*
Example usage::
cid = toolmanager.toolmanager_connect('tool_trigger_zoom',
on_press)
#...later
toolmanager.toolmanager_disconnect(cid)
"""
return self._callbacks.disconnect(cid)
def message_event(self, message, sender=None):
""" Emit a `ToolManagerMessageEvent`"""
if sender is None:
sender = self
s = 'tool_message_event'
event = ToolManagerMessageEvent(s, sender, message)
self._callbacks.process(s, event)
@property
def active_toggle(self):
"""Currently toggled tools"""
return self._toggled
def get_tool_keymap(self, name):
"""
Get the keymap associated with the specified tool
Parameters
----------
name : string
Name of the Tool
Returns
-------
list : list of keys associated with the Tool
"""
keys = [k for k, i in self._keys.items() if i == name]
return keys
def _remove_keys(self, name):
for k in self.get_tool_keymap(name):
del self._keys[k]
def update_keymap(self, name, *keys):
"""
Set the keymap to associate with the specified tool
Parameters
----------
name : string
Name of the Tool
keys : keys to associate with the Tool
"""
if name not in self._tools:
raise KeyError('%s not in Tools' % name)
self._remove_keys(name)
for key in keys:
for k in validate_stringlist(key):
if k in self._keys:
warnings.warn('Key %s changed from %s to %s' %
(k, self._keys[k], name))
self._keys[k] = name
def remove_tool(self, name):
"""
Remove tool from `ToolManager`
Parameters
----------
name : string
Name of the Tool
"""
tool = self.get_tool(name)
tool.destroy()
# If is a toggle tool and toggled, untoggle
if getattr(tool, 'toggled', False):
self.trigger_tool(tool, 'toolmanager')
self._remove_keys(name)
s = 'tool_removed_event'
event = ToolEvent(s, self, tool)
self._callbacks.process(s, event)
del self._tools[name]
def add_tool(self, name, tool, *args, **kwargs):
"""
Add *tool* to `ToolManager`
If successful adds a new event `tool_trigger_name` where **name** is
the **name** of the tool, this event is fired everytime
the tool is triggered.
Parameters
----------
name : str
Name of the tool, treated as the ID, has to be unique
tool : class_like, i.e. str or type
Reference to find the class of the Tool to added.
Notes
-----
args and kwargs get passed directly to the tools constructor.
See Also
--------
matplotlib.backend_tools.ToolBase : The base class for tools.
"""
tool_cls = self._get_cls_to_instantiate(tool)
if not tool_cls:
raise ValueError('Impossible to find class for %s' % str(tool))
if name in self._tools:
warnings.warn('A "Tool class" with the same name already exists, '
'not added')
return self._tools[name]
tool_obj = tool_cls(self, name, *args, **kwargs)
self._tools[name] = tool_obj
if tool_cls.default_keymap is not None:
self.update_keymap(name, tool_cls.default_keymap)
# For toggle tools init the radio_group in self._toggled
if isinstance(tool_obj, tools.ToolToggleBase):
# None group is not mutually exclusive, a set is used to keep track
# of all toggled tools in this group
if tool_obj.radio_group is None:
self._toggled.setdefault(None, set())
else:
self._toggled.setdefault(tool_obj.radio_group, None)
# If initially toggled
if tool_obj.toggled:
self._handle_toggle(tool_obj, None, None, None)
tool_obj.set_figure(self.figure)
self._tool_added_event(tool_obj)
return tool_obj
def _tool_added_event(self, tool):
s = 'tool_added_event'
event = ToolEvent(s, self, tool)
self._callbacks.process(s, event)
def _handle_toggle(self, tool, sender, canvasevent, data):
"""
Toggle tools, need to untoggle prior to using other Toggle tool
Called from trigger_tool
Parameters
----------
tool: Tool object
sender: object
Object that wishes to trigger the tool
canvasevent : Event
Original Canvas event or None
data : Object
Extra data to pass to the tool when triggering
"""
radio_group = tool.radio_group
# radio_group None is not mutually exclusive
# just keep track of toggled tools in this group
if radio_group is None:
if tool.name in self._toggled[None]:
self._toggled[None].remove(tool.name)
else:
self._toggled[None].add(tool.name)
return
# If the tool already has a toggled state, untoggle it
if self._toggled[radio_group] == tool.name:
toggled = None
# If no tool was toggled in the radio_group
# toggle it
elif self._toggled[radio_group] is None:
toggled = tool.name
# Other tool in the radio_group is toggled
else:
# Untoggle previously toggled tool
self.trigger_tool(self._toggled[radio_group],
self,
canvasevent,
data)
toggled = tool.name
# Keep track of the toggled tool in the radio_group
self._toggled[radio_group] = toggled
def _get_cls_to_instantiate(self, callback_class):
# Find the class that corresponds to the tool
if isinstance(callback_class, str):
# FIXME: make more complete searching structure
if callback_class in globals():
callback_class = globals()[callback_class]
else:
mod = 'backend_tools'
current_module = __import__(mod,
globals(), locals(), [mod], 1)
callback_class = getattr(current_module, callback_class, False)
if callable(callback_class):
return callback_class
else:
return None
def trigger_tool(self, name, sender=None, canvasevent=None,
data=None):
"""
Trigger a tool and emit the tool_trigger_[name] event
Parameters
----------
name : string
Name of the tool
sender: object
Object that wishes to trigger the tool
canvasevent : Event
Original Canvas event or None
data : Object
Extra data to pass to the tool when triggering
"""
tool = self.get_tool(name)
if tool is None:
return
if sender is None:
sender = self
self._trigger_tool(name, sender, canvasevent, data)
s = 'tool_trigger_%s' % name
event = ToolTriggerEvent(s, sender, tool, canvasevent, data)
self._callbacks.process(s, event)
def _trigger_tool(self, name, sender=None, canvasevent=None, data=None):
"""
Trigger on a tool
Method to actually trigger the tool
"""
tool = self.get_tool(name)
if isinstance(tool, tools.ToolToggleBase):
self._handle_toggle(tool, sender, canvasevent, data)
# Important!!!
# This is where the Tool object gets triggered
tool.trigger(sender, canvasevent, data)
def _key_press(self, event):
if event.key is None or self.keypresslock.locked():
return
name = self._keys.get(event.key, None)
if name is None:
return
self.trigger_tool(name, canvasevent=event)
@property
def tools(self):
"""Return the tools controlled by `ToolManager`"""
return self._tools
def get_tool(self, name, warn=True):
"""
Return the tool object, also accepts the actual tool for convenience
Parameters
----------
name : str, ToolBase
Name of the tool, or the tool itself
warn : bool, optional
If this method should give warnings.
"""
if isinstance(name, tools.ToolBase) and name.name in self._tools:
return name
if name not in self._tools:
if warn:
warnings.warn("ToolManager does not control tool %s" % name)
return None
return self._tools[name]
File diff suppressed because it is too large Load Diff
@@ -1,107 +0,0 @@
import importlib
import logging
import os
import sys
import traceback
import matplotlib
from matplotlib import cbook
from matplotlib.backend_bases import _Backend
_log = logging.getLogger(__name__)
# NOTE: plt.switch_backend() (called at import time) will add a "backend"
# attribute here for backcompat.
def _get_running_interactive_framework():
"""
Return the interactive framework whose event loop is currently running, if
any, or "headless" if no event loop can be started, or None.
Returns
-------
Optional[str]
One of the following values: "qt5", "qt4", "gtk3", "wx", "tk",
"macosx", "headless", ``None``.
"""
QtWidgets = (sys.modules.get("PyQt5.QtWidgets")
or sys.modules.get("PySide2.QtWidgets"))
if QtWidgets and QtWidgets.QApplication.instance():
return "qt5"
QtGui = (sys.modules.get("PyQt4.QtGui")
or sys.modules.get("PySide.QtGui"))
if QtGui and QtGui.QApplication.instance():
return "qt4"
Gtk = (sys.modules.get("gi.repository.Gtk")
or sys.modules.get("pgi.repository.Gtk"))
if Gtk and Gtk.main_level():
return "gtk3"
wx = sys.modules.get("wx")
if wx and wx.GetApp():
return "wx"
tkinter = sys.modules.get("tkinter")
if tkinter:
for frame in sys._current_frames().values():
while frame:
if frame.f_code == tkinter.mainloop.__code__:
return "tk"
frame = frame.f_back
if 'matplotlib.backends._macosx' in sys.modules:
if sys.modules["matplotlib.backends._macosx"].event_loop_is_running():
return "macosx"
if sys.platform.startswith("linux") and not os.environ.get("DISPLAY"):
return "headless"
return None
@cbook.deprecated("3.0")
def pylab_setup(name=None):
"""
Return new_figure_manager, draw_if_interactive and show for pyplot.
This provides the backend-specific functions that are used by pyplot to
abstract away the difference between backends.
Parameters
----------
name : str, optional
The name of the backend to use. If `None`, falls back to
``matplotlib.get_backend()`` (which return :rc:`backend`).
Returns
-------
backend_mod : module
The module which contains the backend of choice
new_figure_manager : function
Create a new figure manager (roughly maps to GUI window)
draw_if_interactive : function
Redraw the current figure if pyplot is interactive
show : function
Show (and possibly block) any unshown figures.
"""
# Import the requested backend into a generic module object.
if name is None:
name = matplotlib.get_backend()
backend_name = (name[9:] if name.startswith("module://")
else "matplotlib.backends.backend_{}".format(name.lower()))
backend_mod = importlib.import_module(backend_name)
# Create a local Backend class whose body corresponds to the contents of
# the backend module. This allows the Backend class to fill in the missing
# methods through inheritance.
Backend = type("Backend", (_Backend,), vars(backend_mod))
# Need to keep a global reference to the backend for compatibility reasons.
# See https://github.com/matplotlib/matplotlib/issues/6092
global backend
backend = name
_log.debug('backend %s version %s', name, Backend.backend_version)
return (backend_mod,
Backend.new_figure_manager,
Backend.draw_if_interactive,
Backend.show)

Some files were not shown because too many files have changed in this diff Show More