Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec8d3ca94c | |||
| 88687124f2 | |||
| 97233f27c3 | |||
| b35aacad3f | |||
| 2136406a28 | |||
| ff13d8eeeb | |||
| fece181c6f | |||
| 85d7be4117 | |||
| 56e8282a82 | |||
| 1c4949020b | |||
| a71d8565c4 | |||
| 7e09a1e51f | |||
| 92fac02a9e | |||
| 320bcae78e | |||
| d6a0efd69b | |||
| 40a79af9af | |||
| 229719cc5c | |||
| ac6345c5b5 | |||
| 7773788e20 | |||
| ac154e6770 | |||
| a4ec93f412 | |||
| 16d7624154 | |||
| 54b93cb05c | |||
| 3d3254c4a7 | |||
| cfe6e3c286 | |||
| 7a3877b34b | |||
| f20dd30292 | |||
| ef5c87d8d2 | |||
| 4878df9ca8 | |||
| 89ea9564c8 | |||
| 00c619ce34 | |||
| 8f22a6baee | |||
| fa2c1cccb1 | |||
| 7058512ee0 | |||
| 325f1176c3 | |||
| 0a9e610dd7 | |||
| 43b5277923 | |||
| 5265ba9ae9 | |||
| 16e9c52194 | |||
| c5db357cc0 | |||
| b7eb29f78b | |||
| 1457655f16 | |||
| a3f3558f7c | |||
| bc72207187 | |||
| 7837f605cf | |||
| 3d1a925ba6 | |||
| 682fe20db2 | |||
| 949dd0bef2 | |||
| 58cb777935 | |||
| 6ffc4f8aa2 | |||
| 87bd420d31 | |||
| 3e1a43c10f | |||
| ed0b21a496 | |||
| b93358dc32 | |||
| 7f8e69fcf3 | |||
| 751095a5a1 | |||
| 665cf6050b | |||
| 8e7435ff8f | |||
| 08310e074d | |||
| 598352f526 | |||
| e88171b31c | |||
| a5b393a879 | |||
| 9b4e733209 | |||
| 4cf102946e | |||
| c72c0800fc | |||
| 095a95c1eb | |||
| fd5e482de9 | |||
| ccbeb20f70 | |||
| a4dca60892 | |||
| 95e22b5d1f |
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
parserOptions:
|
||||||
|
ecmaVersion: 2019
|
||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
jquery: true
|
||||||
+132
-1
@@ -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
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
language: python
|
||||||
@@ -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
@@ -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": {}
|
||||||
|
}
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
# TeraHz
|
|
||||||
|
|
||||||
[](https://terahz.readthedocs.io/en/latest/?badge=latest)
|
[](https://terahz.readthedocs.io/en/latest/?badge=latest)
|
||||||
|
[](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.
@@ -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
|
|
||||||
Binary file not shown.
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# run.sh - run the backend server
|
|
||||||
sudo gunicorn app:app -b 0.0.0.0:5000 &
|
|
||||||
disown
|
|
||||||
Binary file not shown.
@@ -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
|
|
||||||
@@ -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
@@ -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
|
|
||||||
```
|
|
||||||

|
|
||||||
|
|
||||||
Configure the network hostname to something specific. If setting up multiple
|
|
||||||
TeraHz machines, make their hostnames unique so you can tell them apart.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Enable SSH and I2C interfaces.
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Expand the root filesystem along the entire SD card.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Set the Wi-Fi country to the country you'll be using TeraHz in.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
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
@@ -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
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
@@ -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]);
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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.
@@ -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: "Comic Sans MS"; 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: "Comic Sans MS"; 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: "Comic Sans MS"; 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: "Comic Sans MS"; 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: "Comic Sans MS"; 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: "Comic Sans MS"; 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: "Liberation Sans", 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: "Comic Sans MS"; 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: "Liberation Sans", 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: "Liberation Sans", 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: "Liberation Sans", 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"><span xmlns="http://www.w3.org/1999/xhtml" style="font-family: &quot;Comic Sans MS&quot;;">Računalnik na tiskanem vezju (SBC = single board computer)<br xmlns="http://www.w3.org/1999/xhtml" />Opravlja prevzem in obdelavo podatkov s senzorja, in jih preda telefonu, ki upravlja spektrmeter</span></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: "Liberation Sans", 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: "Comic Sans MS";">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: "Liberation Sans", 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: "Liberation Sans", 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: "Comic Sans MS"; 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 +0,0 @@
|
|||||||
# sysprep - How to prepare a Raspberry Pi 3 B+ to run TeraHz
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 97 KiB |
@@ -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',
|
||||||
|
)
|
||||||
@@ -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)
|
||||||
@@ -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 |
@@ -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>
|
||||||
@@ -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')
|
||||||
@@ -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)
|
|
||||||
@@ -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))
|
|
||||||
@@ -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.
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
|
||||||
@@ -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())
|
|
||||||
@@ -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.
@@ -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,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,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())
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +0,0 @@
|
|||||||
UNKNOWN
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
cycler
|
|
||||||
@@ -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']
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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)
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
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."""
|
|
||||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -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 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -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 +0,0 @@
|
|||||||
kiwisolver
|
|
||||||
Binary file not shown.
@@ -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 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
mpl_toolkits
|
|
||||||
mpl_toolkits
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
matplotlib
|
|
||||||
mpl_toolkits
|
|
||||||
pylab
|
|
||||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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)
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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 *
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
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)
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user